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