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