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