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