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