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