Moodle release 3.0rc2
[moodle.git] / lib / editor / atto / plugins / table / yui / build / moodle-atto_table-button / moodle-atto_table-button.js
CommitLineData
adca7326
DW
1YUI.add('moodle-atto_table-button', function (Y, NAME) {
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
3ee53a42 18/**
62467795
AN
19 * @package atto_table
20 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23
24/**
25 * @module moodle-atto_table-button
3ee53a42 26 */
3ee53a42 27
adca7326
DW
28/**
29 * Atto text editor table plugin.
30 *
62467795
AN
31 * @namespace M.atto_table
32 * @class Button
33 * @extends M.editor_atto.EditorPlugin
adca7326 34 */
62467795
AN
35
36var COMPONENT = 'atto_table',
b87e11c8 37 DEFAULT = {
f45937e1 38 BORDERSTYLE: 'inherit',
b87e11c8 39 BORDERWIDTH: '1'
40 },
41 DIALOGUE = {
1c53bc81 42 WIDTH: '480px'
b87e11c8 43 },
62467795 44 TEMPLATE = '' +
353473aa 45 '<form class="{{CSS.FORM}}">' +
62467795 46 '<label for="{{elementid}}_atto_table_caption">{{get_string "caption" component}}</label>' +
353473aa
DW
47 '<input class="{{CSS.CAPTION}} fullwidth" id="{{elementid}}_atto_table_caption" required />' +
48 '<br/>' +
62467795 49 '<br/>' +
b87e11c8 50 '<label for="{{elementid}}_atto_table_captionposition" class="sameline">' +
51 '{{get_string "captionposition" component}}</label>' +
556decf1
DM
52 '<select class="{{CSS.CAPTIONPOSITION}}" id="{{elementid}}_atto_table_captionposition">' +
53 '<option value=""></option>' +
54 '<option value="top">{{get_string "top" "editor"}}</option>' +
55 '<option value="bottom">{{get_string "bottom" "editor"}}</option>' +
56 '</select>' +
57 '<br/>' +
62467795 58 '<label for="{{elementid}}_atto_table_headers" class="sameline">{{get_string "headers" component}}</label>' +
353473aa 59 '<select class="{{CSS.HEADERS}}" id="{{elementid}}_atto_table_headers">' +
62467795
AN
60 '<option value="columns">{{get_string "columns" component}}' + '</option>' +
61 '<option value="rows">{{get_string "rows" component}}' + '</option>' +
62 '<option value="both">{{get_string "both" component}}' + '</option>' +
63 '</select>' +
64 '<br/>' +
b87e11c8 65 '{{#if nonedit}}' +
66 '<label for="{{elementid}}_atto_table_rows" class="sameline">{{get_string "numberofrows" component}}</label>' +
67 '<input class="{{CSS.ROWS}}" type="number" value="3" ' +
68 'id="{{elementid}}_atto_table_rows" size="8" min="1" max="50"/>' +
69 '<br/>' +
70 '<label for="{{elementid}}_atto_table_columns" ' +
71 'class="sameline">{{get_string "numberofcolumns" component}}</label>' +
72 '<input class="{{CSS.COLUMNS}}" type="number" value="3" id="{{elementid}}_atto_table_columns"' +
73 'size="8" min="1" max="20"/>' +
62467795 74 '<br/>' +
b87e11c8 75 '{{/if}}' +
76 '{{#if allowStyling}}' +
77 '<fieldset>' +
78 '<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
79 '{{#if allowBorders}}' +
80 '<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
f45937e1 81 '<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
b87e11c8 82 '<option value="default">{{get_string "themedefault" component}}' + '</option>' +
b87e11c8 83 '<option value="outer">{{get_string "outer" component}}' + '</option>' +
84 '<option value="all">{{get_string "all" component}}' + '</option>' +
85 '</select>' +
86 '<br>' +
87 '{{#if allowBorderStyles}}' +
88 '<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
89 '{{get_string "borderstyles" component}}</label>' +
f45937e1
RW
90 '<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
91 '<option value="inherit">{{get_string "inherit" component}}</option>' +
b87e11c8 92 '{{#each borderStyles}}' +
f45937e1 93 '<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
b87e11c8 94 '{{/each}}' +
95 '</select>' +
96 '<br>' +
97 '{{/if}}' +
98 '{{#if allowBorderSize}}' +
99 '<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
100 '{{get_string "bordersize" component}}</label>' +
f45937e1 101 '<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
b87e11c8 102 'type="number" value="1" size="8" min="1" max="50"/>' +
103 '<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
104 '<br>' +
105 '{{/if}}' +
106 '{{#if allowBorderColour}}' +
107 '<label for="{{elementid}}_atto_table_bordercolour" class="sameline">' +
108 '{{get_string "bordercolour" component}}</label>' +
109 '<div id="{{elementid}}_atto_table_bordercolour"' +
110 'class="{{CSS.BORDERCOLOUR}} {{CSS.AVAILABLECOLORS}}" size="1">' +
111 '<label class="hideborder" for="{{../elementid}}_atto_table_bordercolour_-1"' +
112 'style="background-color:transparent;color:transparent">' +
113
114 '<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
f45937e1
RW
115 'type="radio" name="borderColour" value="none" checked="checked"'+
116 'title="{{get_string "themedefault" component}}"></input>' +
b87e11c8 117
f45937e1 118 '{{get_string "themedefault" component}}' +
b87e11c8 119 '</label>' +
120 '{{#each availableColours}}' +
121 '<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
122 'style="background-color:{{this}};color:{{this}}">' +
123
124 '<input id="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
125 'type="radio" name="borderColour" value="' + '{{this}}' + '" title="{{this}}">' +
126
127 '{{this}}' +
128 '</label>' +
129 '{{/each}}' +
130 '</div>' +
131 '<br>' +
132 '{{/if}}' +
133 '{{/if}}' +
134 '{{#if allowBackgroundColour}}' +
135 '<label for="{{elementid}}_atto_table_backgroundcolour" class="sameline">' +
136 '{{get_string "backgroundcolour" component}}</label>' +
137 '<div id="{{elementid}}_atto_table_backgroundcolour"' +
138 'class="{{CSS.BACKGROUNDCOLOUR}} {{CSS.AVAILABLECOLORS}}" size="1">' +
139 '<label class="hideborder" for="{{../elementid}}_atto_table_backgroundcolour_-1"' +
140 'style="background-color:transparent;color:transparent">' +
141
142 '<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
f45937e1
RW
143 'type="radio" name="backgroundColour" value="none" checked="checked"'+
144 'title="{{get_string "themedefault" component}}"></input>' +
b87e11c8 145
f45937e1 146 '{{get_string "themedefault" component}}' +
b87e11c8 147 '</label>' +
148
149 '{{#each availableColours}}' +
150 '<label for="{{../elementid}}_atto_table_backgroundcolour_{{@index}}"' +
151 'style="background-color:{{this}};color:{{this}}">'+
152
153 '<input id="{{../elementid}}_atto_table_backgroundcolour_{{@index}}"' +
154 'type="radio" name="backgroundColour" value="' + '{{this}}' + '" title="{{this}}">' +
155
156 '{{this}}' +
157 '</label>' +
158 '{{/each}}' +
159 '</div>' +
160 '<br>' +
161 '{{/if}}' +
162 '{{#if allowWidth}}' +
163 '<label for="{{elementid}}_atto_table_width" class="sameline">' +
164 '{{get_string "width" component}}</label>' +
1c53bc81 165 '<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
b87e11c8 166 '<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
167 '<br>' +
168 '{{/if}}' +
169 '</fieldset>' +
170 '{{/if}}' +
171 '<div class="mdl-align">' +
172 '<br/>' +
173 '{{#if edit}}' +
174 '<button class="submit" type="submit">{{get_string "updatetable" component}}</button>' +
175 '{{/if}}' +
176 '{{#if nonedit}}' +
177 '<button class="submit" type="submit">{{get_string "createtable" component}}</button>' +
178 '{{/if}}' +
62467795 179 '</div>' +
23cead68 180 '</form>',
62467795 181 CSS = {
353473aa 182 CAPTION: 'caption',
556decf1 183 CAPTIONPOSITION: 'captionposition',
353473aa
DW
184 HEADERS: 'headers',
185 ROWS: 'rows',
186 COLUMNS: 'columns',
187 SUBMIT: 'submit',
b87e11c8 188 FORM: 'atto_form',
189 BORDERS: 'borders',
190 BORDERSIZE: 'bordersize',
191 BORDERSIZEUNIT: 'px',
192 BORDERCOLOUR: 'bordercolour',
193 BORDERSTYLE: 'borderstyle',
194 BACKGROUNDCOLOUR: 'backgroundcolour',
195 WIDTH: 'customwidth',
196 WIDTHUNIT: '%',
197 AVAILABLECOLORS: 'availablecolors',
198 COLOURROW: 'colourrow'
353473aa
DW
199 },
200 SELECTORS = {
201 CAPTION: '.' + CSS.CAPTION,
556decf1 202 CAPTIONPOSITION: '.' + CSS.CAPTIONPOSITION,
353473aa
DW
203 HEADERS: '.' + CSS.HEADERS,
204 ROWS: '.' + CSS.ROWS,
205 COLUMNS: '.' + CSS.COLUMNS,
206 SUBMIT: '.' + CSS.SUBMIT,
b87e11c8 207 BORDERS: '.' + CSS.BORDERS,
208 BORDERSIZE: '.' + CSS.BORDERSIZE,
209 BORDERCOLOURS: '.' + CSS.BORDERCOLOUR + ' input[name="borderColour"]',
210 SELECTEDBORDERCOLOUR: '.' + CSS.BORDERCOLOUR + ' input[name="borderColour"]:checked',
211 BORDERSTYLE: '.' + CSS.BORDERSTYLE,
212 BACKGROUNDCOLOURS: '.' + CSS.BACKGROUNDCOLOUR + ' input[name="backgroundColour"]',
213 SELECTEDBACKGROUNDCOLOUR: '.' + CSS.BACKGROUNDCOLOUR + ' input[name="backgroundColour"]:checked',
214 FORM: '.atto_form',
215 WIDTH: '.' + CSS.WIDTH,
216 AVAILABLECOLORS: '.' + CSS.AVAILABLECOLORS
62467795
AN
217 };
218
219Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
adca7326
DW
220
221 /**
62467795
AN
222 * A reference to the current selection at the time that the dialogue
223 * was opened.
adca7326 224 *
62467795
AN
225 * @property _currentSelection
226 * @type Range
227 * @private
adca7326 228 */
62467795 229 _currentSelection: null,
adca7326
DW
230
231 /**
62467795 232 * The contextual menu that we can open.
adca7326 233 *
62467795
AN
234 * @property _contextMenu
235 * @type M.editor_atto.Menu
236 * @private
237 */
238 _contextMenu: null,
239
240 /**
241 * The last modified target.
242 *
243 * @property _lastTarget
244 * @type Node
245 * @private
adca7326 246 */
62467795
AN
247 _lastTarget: null,
248
ee616cff
AN
249 /**
250 * The list of menu items.
251 *
252 * @property _menuOptions
253 * @type Object
254 * @private
255 */
256 _menuOptions: null,
257
62467795
AN
258 initializer: function() {
259 this.addButton({
260 icon: 'e/table',
261 callback: this._displayTableEditor,
262 tags: 'table'
263 });
62467795
AN
264 // Disable mozilla table controls.
265 if (Y.UA.gecko) {
266 document.execCommand("enableInlineTableEditing", false, false);
267 document.execCommand("enableObjectResizing", false, false);
268 }
269 },
adca7326
DW
270
271 /**
62467795 272 * Display the table tool.
adca7326 273 *
62467795
AN
274 * @method _displayDialogue
275 * @private
adca7326 276 */
62467795
AN
277 _displayDialogue: function() {
278 // Store the current cursor position.
279 this._currentSelection = this.get('host').getSelection();
280
281 if (this._currentSelection !== false && (!this._currentSelection.collapsed)) {
282 var dialogue = this.getDialogue({
283 headerContent: M.util.get_string('createtable', COMPONENT),
e5ddec38 284 focusAfterHide: true,
b87e11c8 285 focusOnShowSelector: SELECTORS.CAPTION,
286 width: DIALOGUE.WIDTH
62467795
AN
287 });
288
289 // Set the dialogue content, and then show the dialogue.
b87e11c8 290 dialogue.set('bodyContent', this._getDialogueContent(false))
62467795 291 .show();
f45937e1
RW
292
293 this._updateAvailableSettings();
62467795
AN
294 }
295 },
adca7326
DW
296
297 /**
62467795 298 * Display the appropriate table editor.
adca7326 299 *
62467795
AN
300 * If the current selection includes a table, then we show the
301 * contextual menu, otherwise show the table creation dialogue.
302 *
303 * @method _displayTableEditor
304 * @param {EventFacade} e
305 * @private
adca7326 306 */
62467795 307 _displayTableEditor: function(e) {
f0ddce4d 308 var cell = this._getSuitableTableCell();
62467795
AN
309 if (cell) {
310 // Add the cell to the EventFacade to save duplication in when showing the menu.
311 e.tableCell = cell;
312 return this._showTableMenu(e);
313 }
7b280674 314 return this._displayDialogue(e);
3a6511a5
JC
315 },
316
f0ddce4d
JC
317 /**
318 * Returns whether or not the parameter node exists within the editor.
319 *
320 * @method _stopAtContentEditableFilter
321 * @param {Node} node
322 * @private
323 * @return {boolean} whether or not the parameter node exists within the editor.
324 */
325 _stopAtContentEditableFilter: function(node) {
326 this.editor.contains(node);
327 },
328
adca7326 329 /**
62467795
AN
330 * Return the dialogue content for the tool, attaching any required
331 * events.
adca7326 332 *
62467795
AN
333 * @method _getDialogueContent
334 * @private
335 * @return {Node} The content to place in the dialogue.
adca7326 336 */
b87e11c8 337 _getDialogueContent: function(edit) {
62467795 338 var template = Y.Handlebars.compile(TEMPLATE);
f45937e1 339 var allowBorders = this.get('allowBorders');
b87e11c8 340
62467795
AN
341 this._content = Y.Node.create(template({
342 CSS: CSS,
343 elementid: this.get('host').get('elementid'),
b87e11c8 344 component: COMPONENT,
345 edit: edit,
346 nonedit: !edit,
347 allowStyling: this.get('allowStyling'),
f45937e1 348 allowBorders: allowBorders,
b87e11c8 349 allowBorderStyles: this.get('allowBorderStyles'),
350 borderStyles: this.get('borderStyles'),
351 allowBorderSize: this.get('allowBorderSize'),
352 allowBorderColour: this.get('allowBorderColour'),
353 allowBackgroundColour: this.get('allowBackgroundColour'),
f45937e1 354 availableColours: this.get('availableColors'),
b87e11c8 355 allowWidth: this.get('allowWidth')
62467795
AN
356 }));
357
358 // Handle table setting.
b87e11c8 359 if (edit) {
360 this._content.one('.submit').on('click', this._updateTable, this);
361 } else {
362 this._content.one('.submit').on('click', this._setTable, this);
363 }
62467795 364
f45937e1
RW
365 if (allowBorders) {
366 this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
367 }
368
62467795
AN
369 return this._content;
370 },
adca7326 371
f45937e1
RW
372 /**
373 * Disables options within the dialogue if they shouldn't be available.
374 * E.g.
375 * If borders are set to "Theme default" then the border size, style and
376 * colour options are disabled.
377 *
378 * @method _updateAvailableSettings
379 * @private
380 */
381 _updateAvailableSettings: function() {
382 var tableForm = this._content,
383 enableBorders = tableForm.one('[name="borders"]'),
384 borderStyle = tableForm.one('[name="borderstyles"]'),
385 borderSize = tableForm.one('[name="bordersize"]'),
386 borderColour = tableForm.all('[name="borderColour"]'),
387 disabledValue = 'removeAttribute';
388
389 if (enableBorders.get('value') === 'default') {
390 disabledValue = 'setAttribute';
391 }
392
393 if (borderStyle) {
394 borderStyle[disabledValue]('disabled');
395 }
396
397 if (borderSize) {
398 borderSize[disabledValue]('disabled');
399 }
400
401 if (borderColour) {
402 borderColour[disabledValue]('disabled');
403 }
404
405 },
406
f0ddce4d
JC
407 /**
408 * Given the current selection, return a table cell suitable for table editing
409 * purposes, i.e. the first table cell selected, or the first cell in the table
410 * that the selection exists in, or null if not within a table.
411 *
412 * @method _getSuitableTableCell
413 * @private
414 * @return {Node} suitable target cell, or null if not within a table
415 */
416 _getSuitableTableCell: function() {
417 var targetcell = null,
418 host = this.get('host');
419
420 host.getSelectedNodes().some(function (node) {
421 if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) {
422 targetcell = node;
423
424 var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter);
425 if (caption) {
426 var table = caption.get('parentNode');
427 if (table) {
428 targetcell = table.one('td, th');
429 }
430 }
431
432 // Once we've found a cell to target, we shouldn't need to keep looking.
433 return true;
434 }
435 });
436
437 if (targetcell) {
438 var selection = host.getSelectionFromNode(targetcell);
439 host.setSelection(selection);
440 }
441
442 return targetcell;
443 },
444
353473aa
DW
445 /**
446 * Change a node from one type to another, copying all attributes and children.
447 *
448 * @method _changeNodeType
449 * @param {Y.Node} node
450 * @param {String} new node type
451 * @private
452 * @chainable
453 */
454 _changeNodeType: function(node, newType) {
455 var newNode = Y.Node.create('<' + newType + '></' + newType + '>');
456 newNode.setAttrs(node.getAttrs());
457 node.get('childNodes').each(function(child) {
458 newNode.append(child.remove());
459 });
460 node.replace(newNode);
461 return newNode;
462 },
463
464 /**
465 * Handle updating an existing table.
466 *
467 * @method _updateTable
468 * @param {EventFacade} e
469 * @private
470 */
471 _updateTable: function(e) {
472 var caption,
556decf1 473 captionposition,
353473aa 474 headers,
b87e11c8 475 borders,
476 bordersize,
477 borderstyle,
478 bordercolour,
479 backgroundcolour,
353473aa 480 table,
b87e11c8 481 width,
353473aa
DW
482 captionnode;
483
484 e.preventDefault();
485 // Hide the dialogue.
486 this.getDialogue({
487 focusAfterHide: null
488 }).hide();
489
490 // Add/update the caption.
491 caption = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTION);
556decf1 492 captionposition = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTIONPOSITION);
353473aa 493 headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
b87e11c8 494 borders = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERS);
495 bordersize = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSIZE);
496 bordercolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBORDERCOLOUR);
497 borderstyle = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSTYLE);
498 backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
499 width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
500
353473aa 501 table = this._lastTarget.ancestor('table');
f45937e1
RW
502 this._setAppearance(table, {
503 width: width,
504 borders: borders,
505 borderColour: bordercolour,
506 borderSize: bordersize,
507 borderStyle: borderstyle,
508 backgroundColour: backgroundcolour
b87e11c8 509 });
510
353473aa
DW
511 captionnode = table.one('caption');
512 if (!captionnode) {
556decf1 513 captionnode = Y.Node.create('<caption></caption>');
353473aa
DW
514 table.insert(captionnode, 0);
515 }
516 captionnode.setHTML(caption.get('value'));
556decf1
DM
517 captionnode.setStyle('caption-side', captionposition.get('value'));
518 if (!captionnode.getAttribute('style')) {
519 captionnode.removeAttribute('style');
520 }
353473aa
DW
521
522 // Add the row headers.
523 if (headers.get('value') === 'rows' || headers.get('value') === 'both') {
524 table.all('tr').each(function (row) {
525 var cells = row.all('th, td'),
526 firstCell = cells.shift(),
527 newCell;
528
529 if (firstCell.get('tagName') === 'TD') {
530 // Cell is a td but should be a th - change it.
531 newCell = this._changeNodeType(firstCell, 'th');
532 newCell.setAttribute('scope', 'row');
533 } else {
534 firstCell.setAttribute('scope', 'row');
535 }
536
537 // Now make sure all other cells in the row are td.
538 cells.each(function (cell) {
539 if (cell.get('tagName') === 'TH') {
540 newCell = this._changeNodeType(cell, 'td');
541 newCell.removeAttribute('scope');
542 }
543 }, this);
544
545 }, this);
546 }
547 // Add the col headers. These may overrule the row headers in the first cell.
548 if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
549 var rows = table.all('tr'),
550 firstRow = rows.shift(),
551 newCell;
552
553 firstRow.all('td, th').each(function (cell) {
554 if (cell.get('tagName') === 'TD') {
555 // Cell is a td but should be a th - change it.
556 newCell = this._changeNodeType(cell, 'th');
557 newCell.setAttribute('scope', 'col');
558 } else {
559 cell.setAttribute('scope', 'col');
560 }
561 }, this);
562 // Change all the cells in the rest of the table to tds (unless they are row headers).
563 rows.each(function(row) {
564 var cells = row.all('th, td');
565
566 if (headers.get('value') === 'both') {
567 // Ignore the first cell because it's a row header.
568 cells.shift();
569 }
570 cells.each(function(cell) {
571 if (cell.get('tagName') === 'TH') {
572 newCell = this._changeNodeType(cell, 'td');
573 newCell.removeAttribute('scope');
574 }
575 }, this);
576
577 }, this);
578 }
7d8f825b
DW
579 // Clean the HTML.
580 this.markUpdated();
353473aa
DW
581 },
582
adca7326 583 /**
62467795 584 * Handle creation of a new table.
adca7326 585 *
62467795
AN
586 * @method _setTable
587 * @param {EventFacade} e
588 * @private
adca7326 589 */
62467795
AN
590 _setTable: function(e) {
591 var caption,
556decf1 592 captionposition,
b87e11c8 593 borders,
b87e11c8 594 bordersize,
595 borderstyle,
596 bordercolour,
62467795
AN
597 rows,
598 cols,
599 headers,
600 tablehtml,
b87e11c8 601 backgroundcolour,
602 width,
62467795
AN
603 i, j;
604
adca7326 605 e.preventDefault();
adca7326 606
62467795
AN
607 // Hide the dialogue.
608 this.getDialogue({
609 focusAfterHide: null
610 }).hide();
611
48dc9f01 612 caption = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTION);
556decf1 613 captionposition = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTIONPOSITION);
b87e11c8 614 borders = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERS);
615 bordersize = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSIZE);
616 bordercolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBORDERCOLOUR);
617 borderstyle = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSTYLE);
618 backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
353473aa
DW
619 rows = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.ROWS);
620 cols = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.COLUMNS);
621 headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
b87e11c8 622 width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
623
62467795
AN
624 // Set the selection.
625 this.get('host').setSelection(this._currentSelection);
626
627 // Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
628 var nl = "\n";
f45937e1
RW
629 var tableId = Y.guid();
630 tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
adca7326 631
556decf1
DM
632 var captionstyle = '';
633 if (captionposition.get('value')) {
634 captionstyle = ' style="caption-side: ' + captionposition.get('value') + '"';
635 }
636 tablehtml += '<caption' + captionstyle + '>' + Y.Escape.html(caption.get('value')) + '</caption>' + nl;
62467795
AN
637 i = 0;
638 if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
639 i = 1;
640 tablehtml += '<thead>' + nl + '<tr>' + nl;
641 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
f45937e1 642 tablehtml += '<th scope="col"></th>' + nl;
62467795
AN
643 }
644 tablehtml += '</tr>' + nl + '</thead>' + nl;
645 }
646 tablehtml += '<tbody>' + nl;
647 for (; i < parseInt(rows.get('value'), 10); i++) {
648 tablehtml += '<tr>' + nl;
649 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
650 if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
f45937e1 651 tablehtml += '<th scope="row"></th>' + nl;
62467795 652 } else {
f45937e1 653 tablehtml += '<td ></td>' + nl;
62467795
AN
654 }
655 }
656 tablehtml += '</tr>' + nl;
adca7326 657 }
62467795
AN
658 tablehtml += '</tbody>' + nl;
659 tablehtml += '</table>' + nl + '<br/>';
adca7326 660
62467795
AN
661 this.get('host').insertContentAtFocusPoint(tablehtml);
662
f45937e1
RW
663 var tableNode = Y.one('#' + tableId);
664 this._setAppearance(tableNode, {
665 width: width,
666 borders: borders,
667 borderColour: bordercolour,
668 borderSize: bordersize,
669 borderStyle: borderstyle,
670 backgroundColour: backgroundcolour
671 });
672 tableNode.removeAttribute('id');
673
62467795
AN
674 // Mark the content as updated.
675 this.markUpdated();
adca7326
DW
676 },
677
353473aa
DW
678 /**
679 * Search for all the cells in the current, next and previous columns.
680 *
681 * @method _findColumnCells
682 * @private
683 * @return {Object} containing current, prev and next {Y.NodeList}s
684 */
685 _findColumnCells: function() {
686 var columnindex = this._getColumnIndex(this._lastTarget),
687 rows = this._lastTarget.ancestor('table').all('tr'),
688 currentcells = new Y.NodeList(),
689 prevcells = new Y.NodeList(),
690 nextcells = new Y.NodeList();
691
692 rows.each(function(row) {
693 var cells = row.all('td, th'),
694 cell = cells.item(columnindex),
695 cellprev = cells.item(columnindex-1),
696 cellnext = cells.item(columnindex+1);
697 currentcells.push(cell);
698 if (cellprev) {
699 prevcells.push(cellprev);
700 }
701 if (cellnext) {
702 nextcells.push(cellnext);
703 }
704 });
705
706 return {
707 current: currentcells,
708 prev: prevcells,
709 next: nextcells
710 };
711 },
712
713 /**
714 * Hide the entries in the context menu that don't make sense with the
715 * current selection.
716 *
717 * @method _hideInvalidEntries
718 * @param {Y.Node} node - The node containing the menu.
719 * @private
720 */
721 _hideInvalidEntries: function(node) {
722 // Moving rows.
723 var table = this._lastTarget.ancestor('table'),
724 row = this._lastTarget.ancestor('tr'),
725 rows = table.all('tr'),
726 rowindex = rows.indexOf(row),
727 prevrow = rows.item(rowindex - 1),
728 prevrowhascells = prevrow ? prevrow.one('td') : null;
729
730 if (!row || !prevrowhascells) {
731 node.one('[data-change="moverowup"]').hide();
732 } else {
733 node.one('[data-change="moverowup"]').show();
734 }
735
736 var nextrow = rows.item(rowindex + 1),
737 rowhascell = row ? row.one('td') : false;
738
739 if (!row || !nextrow || !rowhascell) {
740 node.one('[data-change="moverowdown"]').hide();
741 } else {
742 node.one('[data-change="moverowdown"]').show();
743 }
744
745 // Moving columns.
746 var cells = this._findColumnCells();
747 if (cells.prev.filter('td').size() > 0) {
748 node.one('[data-change="movecolumnleft"]').show();
749 } else {
750 node.one('[data-change="movecolumnleft"]').hide();
751 }
752
753 var colhascell = cells.current.filter('td').size() > 0;
754 if ((cells.next.size() > 0) && colhascell) {
755 node.one('[data-change="movecolumnright"]').show();
756 } else {
757 node.one('[data-change="movecolumnright"]').hide();
758 }
759
760 // Delete col
761 if (cells.current.filter('td').size() > 0) {
762 node.one('[data-change="deletecolumn"]').show();
763 } else {
764 node.one('[data-change="deletecolumn"]').hide();
765 }
766 // Delete row
767 if (!row || !row.one('td')) {
768 node.one('[data-change="deleterow"]').hide();
769 } else {
770 node.one('[data-change="deleterow"]').show();
771 }
772 },
773
adca7326 774 /**
62467795 775 * Display the table menu.
adca7326 776 *
62467795
AN
777 * @method _showTableMenu
778 * @param {EventFacade} e
779 * @private
adca7326 780 */
62467795 781 _showTableMenu: function(e) {
adca7326
DW
782 e.preventDefault();
783
62467795
AN
784 var boundingBox;
785
786 if (!this._contextMenu) {
ee616cff
AN
787 this._menuOptions = [
788 {
789 text: M.util.get_string("addcolumnafter", COMPONENT),
790 data: {
791 change: "addcolumnafter"
792 }
793 }, {
794 text: M.util.get_string("addrowafter", COMPONENT),
795 data: {
796 change: "addrowafter"
797 }
798 }, {
799 text: M.util.get_string("moverowup", COMPONENT),
800 data: {
801 change: "moverowup"
802 }
803 }, {
804 text: M.util.get_string("moverowdown", COMPONENT),
805 data: {
806 change: "moverowdown"
807 }
808 }, {
809 text: M.util.get_string("movecolumnleft", COMPONENT),
810 data: {
811 change: "movecolumnleft"
812 }
813 }, {
814 text: M.util.get_string("movecolumnright", COMPONENT),
815 data: {
816 change: "movecolumnright"
817 }
818 }, {
819 text: M.util.get_string("deleterow", COMPONENT),
820 data: {
821 change: "deleterow"
822 }
823 }, {
824 text: M.util.get_string("deletecolumn", COMPONENT),
825 data: {
826 change: "deletecolumn"
827 }
92810c07
DW
828 }, {
829 text: M.util.get_string("edittable", COMPONENT),
830 data: {
831 change: "edittable"
832 }
ee616cff
AN
833 }
834 ];
62467795
AN
835
836 this._contextMenu = new Y.M.editor_atto.Menu({
ee616cff 837 items: this._menuOptions
adca7326 838 });
62467795
AN
839
840 // Add event handlers for table control menus.
841 boundingBox = this._contextMenu.get('boundingBox');
842 boundingBox.delegate('click', this._handleTableChange, 'a', this);
adca7326 843 }
ee616cff 844
62467795
AN
845 boundingBox = this._contextMenu.get('boundingBox');
846
adca7326 847 // We store the cell of the last click (the control node is transient).
62467795
AN
848 this._lastTarget = e.tableCell.ancestor('.editor_atto_content td, .editor_atto_content th', true);
849
353473aa
DW
850 this._hideInvalidEntries(boundingBox);
851
c63f9053
AN
852 // Clear the focusAfterHide for any other menus which may be open.
853 Y.Array.each(this.get('host').openMenus, function(menu) {
854 menu.set('focusAfterHide', null);
855 });
856
857 // Ensure that we focus on the button in the toolbar when we tab back to the menu.
858 var creatorButton = this.buttons[this.name];
859 this.get('host')._setTabFocus(creatorButton);
860
62467795
AN
861 // Show the context menu, and align to the current position.
862 this._contextMenu.show();
f8c3af13 863 this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
c63f9053 864 this._contextMenu.set('focusAfterHide', creatorButton);
62467795
AN
865
866 // If there are any anchors in the bounding box, focus on the first.
867 if (boundingBox.one('a')) {
868 boundingBox.one('a').focus();
26f8822d 869 }
c63f9053
AN
870
871 // Add this menu to the list of open menus.
872 this.get('host').openMenus = [this._contextMenu];
62467795
AN
873 },
874
875 /**
876 * Handle a selection from the table control menu.
877 *
878 * @method _handleTableChange
879 * @param {EventFacade} e
880 * @private
881 */
882 _handleTableChange: function(e) {
883 e.preventDefault();
adca7326 884
ee616cff 885 this._contextMenu.set('focusAfterHide', this.get('host').editor);
62467795 886 // Hide the context menu.
ee616cff 887 this._contextMenu.hide(e);
62467795
AN
888
889 // Make our changes.
890 switch (e.target.getData('change')) {
891 case 'addcolumnafter':
892 this._addColumnAfter();
893 break;
894 case 'addrowafter':
895 this._addRowAfter();
896 break;
897 case 'deleterow':
898 this._deleteRow();
899 break;
900 case 'deletecolumn':
901 this._deleteColumn();
902 break;
353473aa
DW
903 case 'edittable':
904 this._editTable();
905 break;
62467795
AN
906 case 'moverowdown':
907 this._moveRowDown();
908 break;
909 case 'moverowup':
910 this._moveRowUp();
911 break;
912 case 'movecolumnleft':
913 this._moveColumnLeft();
914 break;
915 case 'movecolumnright':
916 this._moveColumnRight();
917 break;
adca7326
DW
918 }
919 },
920
921 /**
922 * Determine the index of a row in a table column.
923 *
62467795
AN
924 * @method _getRowIndex
925 * @param {Node} cell
926 * @private
adca7326 927 */
62467795 928 _getRowIndex: function(cell) {
adca7326
DW
929 var tablenode = cell.ancestor('table'),
930 rownode = cell.ancestor('tr');
931
932 if (!tablenode || !rownode) {
933 return;
934 }
935
936 var rows = tablenode.all('tr');
937
938 return rows.indexOf(rownode);
939 },
940
941 /**
942 * Determine the index of a column in a table row.
943 *
62467795
AN
944 * @method _getColumnIndex
945 * @param {Node} cellnode
946 * @private
adca7326 947 */
62467795 948 _getColumnIndex: function(cellnode) {
adca7326
DW
949 var rownode = cellnode.ancestor('tr');
950
951 if (!rownode) {
952 return;
953 }
954
955 var cells = rownode.all('td, th');
956
957 return cells.indexOf(cellnode);
958 },
959
960 /**
62467795 961 * Delete the current row.
adca7326 962 *
62467795
AN
963 * @method _deleteRow
964 * @private
adca7326 965 */
62467795
AN
966 _deleteRow: function() {
967 var row = this._lastTarget.ancestor('tr');
adca7326 968
353473aa
DW
969 if (row && row.one('td')) {
970 // Only delete rows with at least one non-header cell.
971 row.remove(true);
adca7326
DW
972 }
973
974 // Clean the HTML.
62467795 975 this.markUpdated();
adca7326
DW
976 },
977
978 /**
979 * Move row up
980 *
62467795
AN
981 * @method _moveRowUp
982 * @private
adca7326 983 */
62467795 984 _moveRowUp: function() {
353473aa
DW
985 var row = this._lastTarget.ancestor('tr'),
986 prevrow = row.previous('tr');
adca7326
DW
987 if (!row || !prevrow) {
988 return;
989 }
990
991 row.swap(prevrow);
992 // Clean the HTML.
62467795 993 this.markUpdated();
adca7326
DW
994 },
995
996 /**
997 * Move column left
998 *
62467795
AN
999 * @method _moveColumnLeft
1000 * @private
adca7326 1001 */
62467795 1002 _moveColumnLeft: function() {
353473aa 1003 var cells = this._findColumnCells();
adca7326 1004
353473aa 1005 if (cells.current.size() > 0 && cells.prev.size() > 0 && cells.current.size() === cells.prev.size()) {
adca7326 1006 var i = 0;
353473aa
DW
1007 for (i = 0; i < cells.current.size(); i++) {
1008 var cell = cells.current.item(i),
1009 prevcell = cells.prev.item(i);
adca7326
DW
1010
1011 cell.swap(prevcell);
1012 }
1013 }
1014 // Cleanup.
62467795 1015 this.markUpdated();
adca7326
DW
1016 },
1017
353473aa
DW
1018 /**
1019 * Add a caption to the table if it doesn't have one.
1020 *
1021 * @method _addCaption
1022 * @private
1023 */
1024 _addCaption: function() {
1025 var table = this._lastTarget.ancestor('table'),
1026 caption = table.one('caption');
1027
1028 if (!caption) {
1029 table.insert(Y.Node.create('<caption>&nbsp;</caption>'), 1);
1030 }
1031 },
1032
1033 /**
1034 * Remove a caption from the table if has one.
1035 *
1036 * @method _removeCaption
1037 * @private
1038 */
1039 _removeCaption: function() {
1040 var table = this._lastTarget.ancestor('table'),
1041 caption = table.one('caption');
1042
1043 if (caption) {
1044 caption.remove(true);
1045 }
1046 },
1047
adca7326 1048 /**
62467795 1049 * Move column right.
adca7326 1050 *
62467795
AN
1051 * @method _moveColumnRight
1052 * @private
adca7326 1053 */
62467795 1054 _moveColumnRight: function() {
353473aa 1055 var cells = this._findColumnCells();
adca7326 1056
353473aa
DW
1057 // Check we have some tds in this column, and one exists to the right.
1058 if ( (cells.next.size() > 0) &&
1059 (cells.current.size() === cells.next.size()) &&
1060 (cells.current.filter('td').size() > 0)) {
adca7326 1061 var i = 0;
353473aa
DW
1062 for (i = 0; i < cells.current.size(); i++) {
1063 var cell = cells.current.item(i),
1064 nextcell = cells.next.item(i);
adca7326
DW
1065
1066 cell.swap(nextcell);
1067 }
1068 }
1069 // Cleanup.
62467795 1070 this.markUpdated();
adca7326
DW
1071 },
1072
1073 /**
62467795 1074 * Move row down.
adca7326 1075 *
62467795
AN
1076 * @method _moveRowDown
1077 * @private
adca7326 1078 */
62467795 1079 _moveRowDown: function() {
353473aa
DW
1080 var row = this._lastTarget.ancestor('tr'),
1081 nextrow = row.next('tr');
1082 if (!row || !nextrow || !row.one('td')) {
adca7326
DW
1083 return;
1084 }
1085
1086 row.swap(nextrow);
1087 // Clean the HTML.
62467795 1088 this.markUpdated();
adca7326
DW
1089 },
1090
b87e11c8 1091 /**
1092 * Obtain values for the table borders
1093 *
1094 * @method _getBorderConfiguration
1095 * @param {Node} node
1096 * @private
1097 * @return {Array} or {Boolean} Returns the settings, if presents, or else returns false
1098 */
1099 _getBorderConfiguration: function(node) {
1100 // We need to make a clone of the node in order to avoid grabbing any
1101 // of the computed styles from the DOM. We only want inline styles set by us.
1102 var shadowNode = node.cloneNode(true);
1103 var borderStyle = shadowNode.getStyle('borderStyle'),
1104 borderColor = shadowNode.getStyle('borderColor'),
1105 borderWidth = shadowNode.getStyle('borderWidth');
1106
1107 if (borderStyle || borderColor || borderWidth) {
1108 var hexColour = Y.Color.toHex(borderColor);
1109 var width = parseInt(borderWidth, 10);
1110 return {
1111 borderStyle: borderStyle,
1112 borderColor: hexColour === "#" ? null : hexColour,
1113 borderWidth: isNaN(width) ? null : width
1114 };
1115 }
1116
1117 return false;
1118 },
1119
f45937e1
RW
1120 /**
1121 * Set the appropriate styles on the given table node according to
1122 * the provided configuration.
1123 *
1124 * @method _setAppearance
1125 * @param {Node} The table node to be modified.
1126 * @param {Object} Configuration object (associative array) containing the form nodes for
1127 * border styling.
1128 * @private
1129 */
1130 _setAppearance: function(tableNode, configuration) {
1131 var borderhex,
1132 borderSizeValue,
1133 borderStyleValue,
1134 backgroundcolourvalue;
1135
1136 if (configuration.borderColour) {
1137 borderhex = configuration.borderColour.get('value');
1138 }
1139
1140 if (configuration.borderSize) {
1141 borderSizeValue = configuration.borderSize.get('value');
1142 }
1143
1144 if (configuration.borderStyle) {
1145 borderStyleValue = configuration.borderStyle.get('value');
1146 }
1147
1148 if (configuration.backgroundColour) {
1149 backgroundcolourvalue = configuration.backgroundColour.get('value');
1150 }
1151
1152 // Clear the inline border styling
1153 tableNode.removeAttribute('style');
1154 tableNode.all('td, th').each(function(cell) {
1155 cell.removeAttribute('style');
1156 }, this);
1157
1158 if (configuration.borders) {
1159 if (configuration.borders.get('value') === 'outer') {
1160 tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
1161 tableNode.setStyle('borderStyle', borderStyleValue);
1162
1163 if (borderhex !== 'none') {
1164 tableNode.setStyle('borderColor', borderhex);
1165 }
1166 } else if (configuration.borders.get('value') === 'all') {
1167 tableNode.all('td, th').each(function(cell) {
1168 cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
1169 cell.setStyle('borderStyle', borderStyleValue);
1170
1171 if (borderhex !== 'none') {
1172 cell.setStyle('borderColor', borderhex);
1173 }
1174 }, this);
1175 }
1176 }
1177
1178 if (backgroundcolourvalue !== 'none') {
1c53bc81 1179 tableNode.setStyle('backgroundColor', backgroundcolourvalue);
f45937e1
RW
1180 }
1181
1182 if (configuration.width && configuration.width.get('value')) {
1183 tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
1184 }
1185 },
1186
353473aa
DW
1187 /**
1188 * Edit table (show the dialogue).
1189 *
1190 * @method _editTable
1191 * @private
1192 */
1193 _editTable: function() {
1194 var dialogue = this.getDialogue({
1195 headerContent: M.util.get_string('edittable', COMPONENT),
e5ddec38 1196 focusAfterHide: false,
b87e11c8 1197 focusOnShowSelector: SELECTORS.CAPTION,
1198 width: DIALOGUE.WIDTH
353473aa
DW
1199 });
1200
1201 // Set the dialogue content, and then show the dialogue.
b87e11c8 1202 var node = this._getDialogueContent(true),
353473aa 1203 captioninput = node.one(SELECTORS.CAPTION),
556decf1 1204 captionpositioninput = node.one(SELECTORS.CAPTIONPOSITION),
353473aa 1205 headersinput = node.one(SELECTORS.HEADERS),
b87e11c8 1206 borderinput = node.one(SELECTORS.BORDERS),
1207 borderstyle = node.one(SELECTORS.BORDERSTYLE),
1208 bordercolours = node.all(SELECTORS.BORDERCOLOURS),
1209 bordersize = node.one(SELECTORS.BORDERSIZE),
1210 backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
1211 width = node.one(SELECTORS.WIDTH),
353473aa 1212 table = this._lastTarget.ancestor('table'),
f45937e1
RW
1213 captionnode = table.one('caption'),
1214 hexColour,
1215 matchedInput;
353473aa
DW
1216
1217 if (captionnode) {
1218 captioninput.set('value', captionnode.getHTML());
1219 } else {
1220 captioninput.set('value', '');
1221 }
1222
b87e11c8 1223 if (width && table.getStyle('width').indexOf('px') === -1) {
1224 width.set('value', parseInt(table.getStyle('width'), 10));
1225 }
1226
bcd58d9f 1227 if (captionpositioninput && captionnode && captionnode.getAttribute('style')) {
556decf1
DM
1228 captionpositioninput.set('value', captionnode.getStyle('caption-side'));
1229 } else {
1230 // Default to none.
1231 captionpositioninput.set('value', '');
1232 }
1233
1c53bc81
RW
1234 if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
1235 hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
f45937e1 1236 matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
b87e11c8 1237
1238 if (matchedInput) {
1239 matchedInput.set("checked", true);
1240 }
1241 }
1242
1243 if (this.get('allowBorders')) {
1244 var borderValue = 'default',
1245 borderConfiguration = this._getBorderConfiguration(table);
1246
1247 if (borderConfiguration) {
f45937e1 1248 borderValue = 'outer';
b87e11c8 1249 } else {
1250 borderConfiguration = this._getBorderConfiguration(table.one('td'));
1251 if (borderConfiguration) {
1252 borderValue = 'all';
1253 }
1254 }
1255
1256 if (borderConfiguration) {
1257 var borderStyle = borderConfiguration.borderStyle || DEFAULT.BORDERSTYLE;
1258 var borderSize = borderConfiguration.borderWidth || DEFAULT.BORDERWIDTH;
1259 borderstyle.set('value', borderStyle);
1260 bordersize.set('value', borderSize);
1261 borderinput.set('value', borderValue);
1262
f45937e1
RW
1263 hexColour = borderConfiguration.borderColor;
1264 matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
b87e11c8 1265
1266 if (matchedInput) {
1267 matchedInput.set("checked", true);
1268 }
1269 }
1270 }
1271
353473aa
DW
1272 var headersvalue = 'columns';
1273 if (table.one('th[scope="row"]')) {
1274 headersvalue = 'rows';
1275 if (table.one('th[scope="col"]')) {
1276 headersvalue = 'both';
1277 }
1278 }
1279 headersinput.set('value', headersvalue);
1280 dialogue.set('bodyContent', node).show();
f45937e1 1281 this._updateAvailableSettings();
353473aa
DW
1282 },
1283
1284
adca7326 1285 /**
62467795 1286 * Delete the current column.
adca7326 1287 *
62467795
AN
1288 * @method _deleteColumn
1289 * @private
adca7326 1290 */
62467795 1291 _deleteColumn: function() {
353473aa
DW
1292 var columnindex = this._getColumnIndex(this._lastTarget),
1293 table = this._lastTarget.ancestor('table'),
1294 rows = table.all('tr'),
1295 columncells = new Y.NodeList(),
1296 hastd = false;
adca7326
DW
1297
1298 rows.each(function(row) {
1299 var cells = row.all('td, th');
1300 var cell = cells.item(columnindex);
1301 if (cell.get('tagName') === 'TD') {
1302 hastd = true;
1303 }
1304 columncells.push(cell);
1305 });
1306
353473aa 1307 // Do not delete all the headers.
adca7326
DW
1308 if (hastd) {
1309 columncells.remove(true);
1310 }
1311
1312 // Clean the HTML.
62467795 1313 this.markUpdated();
adca7326
DW
1314 },
1315
1316 /**
1317 * Add a row after the current row.
1318 *
62467795
AN
1319 * @method _addRowAfter
1320 * @private
adca7326 1321 */
62467795 1322 _addRowAfter: function() {
aa65ecea 1323 var target = this._lastTarget.ancestor('tr'),
1324 tablebody = this._lastTarget.ancestor('table').one('tbody');
adca7326
DW
1325 if (!tablebody) {
1326 // Not all tables have tbody.
62467795 1327 tablebody = this._lastTarget.ancestor('table');
adca7326
DW
1328 }
1329
1330 var firstrow = tablebody.one('tr');
1331 if (!firstrow) {
62467795 1332 firstrow = this._lastTarget.ancestor('table').one('tr');
adca7326
DW
1333 }
1334 if (!firstrow) {
1335 // Table has no rows. Boo.
1336 return;
1337 }
557f44d9 1338 var newrow = firstrow.cloneNode(true);
adca7326
DW
1339 newrow.all('th, td').each(function (tablecell) {
1340 if (tablecell.get('tagName') === 'TH') {
1341 if (tablecell.getAttribute('scope') !== 'row') {
1342 var newcell = Y.Node.create('<td></td>');
1343 tablecell.replace(newcell);
1344 tablecell = newcell;
1345 }
1346 }
1347 tablecell.setHTML('&nbsp;');
1348 });
1349
aa65ecea 1350 if (target.ancestor('thead')) {
1351 target = firstrow;
1352 tablebody.insert(newrow, target);
1353 } else {
1354 target.insert(newrow, 'after');
1355 }
adca7326
DW
1356
1357 // Clean the HTML.
62467795 1358 this.markUpdated();
adca7326
DW
1359 },
1360
1361 /**
1362 * Add a column after the current column.
1363 *
62467795
AN
1364 * @method _addColumnAfter
1365 * @private
adca7326 1366 */
62467795 1367 _addColumnAfter: function() {
353473aa
DW
1368 var cells = this._findColumnCells(),
1369 before = true,
1370 clonecells = cells.next;
1371 if (cells.next.size() <= 0) {
1372 before = false;
1373 clonecells = cells.current;
1374 }
adca7326 1375
353473aa
DW
1376 Y.each(clonecells, function(cell) {
1377 var newcell = cell.cloneNode();
adca7326
DW
1378 // Clear the content of the cell.
1379 newcell.setHTML('&nbsp;');
1380
353473aa
DW
1381 if (before) {
1382 cell.get('parentNode').insert(newcell, cell);
1383 } else {
1384 cell.get('parentNode').insert(newcell, cell);
1385 cell.swap(newcell);
1386 }
adca7326
DW
1387 }, this);
1388
1389 // Clean the HTML.
62467795 1390 this.markUpdated();
adca7326 1391 }
353473aa 1392
b87e11c8 1393}, {
1394 ATTRS: {
1395 /**
1396 * Whether or not to allow borders
1397 *
1398 * @attribute allowBorder
1399 * @type Boolean
1400 */
1401 allowBorders: {
1402 value: true
1403 },
1404
1405 /**
1406 * Whether or not to allow style of borders
1407 *
1408 * @attribute allowBorderStyle
1409 * @type Boolean
1410 */
1411 allowBorderStyles: {
1412 value: true
1413 },
1414
1415 /**
1416 * What border styles to allow
1417 *
1418 * @attribute borderStyles
1419 * @type Array
1420 */
1421 borderStyles: {
1422 value: [
1423 'solid',
1424 'dashed',
1425 'dotted'
1426 ],
1427 setter: function(value) {
1428 if (value) {
1429 return value.replace(/ /g,'').split(',');
1430 } else {
1431 // Not a valid value - revert to default value.
1432 return Y.Attribute.INVALID_VALUE;
1433 }
1434 }
1435 },
1436
1437 /**
1438 * Whether or not to allow border size
1439 *
1440 * @attribute allowBorderSize
1441 * @type Boolean
1442 */
1443 allowBorderSize: {
1444 value: true
1445 },
1446
1447 /**
1448 * Whether or not to allow colourizing borders
1449 *
1450 * @attribute allowBorderColours
1451 * @type Boolean
1452 */
1453 allowBorderColour: {
1454 value: true
1455 },
1456
1457 /**
1458 * Whether or not to allow colourizing the background
1459 *
1460 * @attribute allowBackgroundColour
1461 * @type Boolean
1462 */
1463 allowBackgroundColour: {
1464 value: true
1465 },
1466
1467 /**
1468 * Whether or not to allow setting the table width
1469 *
1470 * @attribute allowWidth
1471 * @type Boolean
1472 */
1473 allowWidth: {
1474 value: true
1475 },
1476
1477 /**
1478 * Whether we allow styling
1479 * @attribute allowStyling
1480 * @type Boolean
1481 */
1482 allowStyling: {
1483 readOnly: true,
1484 getter: function() {
1485 return this.get('allowBorders') || this.get('allowBackgroundColour') || this.get('allowWidth');
1486 }
1487 },
1488
1489 /**
1490 * Available colors
1491 * @attribute availableColors
1492 * @type Array
1493 */
1494 availableColors: {
1495 value: [
1496 '#FFFFFF',
1497 '#EF4540',
1498 '#FFCF35',
1499 '#98CA3E',
1500 '#7D9FD3',
1501 '#333333'
1502 ],
1503 readOnly: true
1504 }
1505 }
62467795 1506});
adca7326
DW
1507
1508
62467795 1509}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-editor_atto-menu", "event", "event-valuechange"]});