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