Merge branch '44354-27' of git://github.com/samhemelryk/moodle
[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',
353473aa
DW
35 EDITTEMPLATE = '' +
36 '<form class="{{CSS.FORM}}">' +
37 '<label for="{{elementid}}_atto_table_caption">{{get_string "caption" component}}</label>' +
38 '<input class="{{CSS.CAPTION}} fullwidth" id="{{elementid}}_atto_table_caption" required />' +
39 '<br/>' +
40 '<br/>' +
41 '<label for="{{elementid}}_atto_table_headers" class="sameline">{{get_string "headers" component}}</label>' +
42 '<select class="{{CSS.HEADERS}}" id="{{elementid}}_atto_table_headers">' +
43 '<option value="columns">{{get_string "columns" component}}' + '</option>' +
44 '<option value="rows">{{get_string "rows" component}}' + '</option>' +
45 '<option value="both">{{get_string "both" component}}' + '</option>' +
46 '</select>' +
47 '<br/>' +
48 '<div class="mdl-align">' +
49 '<br/>' +
50 '<button class="submit" type="submit">{{get_string "updatetable" component}}</button>' +
51 '</div>' +
52 '</form>',
62467795 53 TEMPLATE = '' +
353473aa 54 '<form class="{{CSS.FORM}}">' +
62467795 55 '<label for="{{elementid}}_atto_table_caption">{{get_string "caption" component}}</label>' +
353473aa
DW
56 '<input class="{{CSS.CAPTION}} fullwidth" id="{{elementid}}_atto_table_caption" required />' +
57 '<br/>' +
62467795
AN
58 '<br/>' +
59 '<label for="{{elementid}}_atto_table_headers" class="sameline">{{get_string "headers" component}}</label>' +
353473aa 60 '<select class="{{CSS.HEADERS}}" id="{{elementid}}_atto_table_headers">' +
62467795
AN
61 '<option value="columns">{{get_string "columns" component}}' + '</option>' +
62 '<option value="rows">{{get_string "rows" component}}' + '</option>' +
63 '<option value="both">{{get_string "both" component}}' + '</option>' +
64 '</select>' +
65 '<br/>' +
66 '<label for="{{elementid}}_atto_table_rows" class="sameline">{{get_string "numberofrows" component}}</label>' +
353473aa 67 '<input class="{{CSS.ROWS}}" type="number" value="3" id="{{elementid}}_atto_table_rows" size="8" min="1" max="50"/>' +
62467795
AN
68 '<br/>' +
69 '<label for="{{elementid}}_atto_table_columns" class="sameline">{{get_string "numberofcolumns" component}}</label>' +
353473aa 70 '<input class="{{CSS.COLUMNS}}" type="number" value="3" id="{{elementid}}_atto_table_columns" size="8" min="1" max="20"/>' +
62467795
AN
71 '<br/>' +
72 '<div class="mdl-align">' +
73 '<br/>' +
353473aa 74 '<button class="{{CSS.SUBMIT}}" type="submit">{{get_string "createtable" component}}</button>' +
62467795 75 '</div>' +
23cead68 76 '</form>',
62467795 77 CSS = {
353473aa
DW
78 CAPTION: 'caption',
79 HEADERS: 'headers',
80 ROWS: 'rows',
81 COLUMNS: 'columns',
82 SUBMIT: 'submit',
83 FORM: 'atto_form'
84 },
85 SELECTORS = {
86 CAPTION: '.' + CSS.CAPTION,
87 HEADERS: '.' + CSS.HEADERS,
88 ROWS: '.' + CSS.ROWS,
89 COLUMNS: '.' + CSS.COLUMNS,
90 SUBMIT: '.' + CSS.SUBMIT,
91 FORM: '.atto_form'
62467795
AN
92 };
93
94Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
adca7326
DW
95
96 /**
62467795
AN
97 * A reference to the current selection at the time that the dialogue
98 * was opened.
adca7326 99 *
62467795
AN
100 * @property _currentSelection
101 * @type Range
102 * @private
adca7326 103 */
62467795 104 _currentSelection: null,
adca7326
DW
105
106 /**
62467795 107 * The contextual menu that we can open.
adca7326 108 *
62467795
AN
109 * @property _contextMenu
110 * @type M.editor_atto.Menu
111 * @private
112 */
113 _contextMenu: null,
114
115 /**
116 * The last modified target.
117 *
118 * @property _lastTarget
119 * @type Node
120 * @private
adca7326 121 */
62467795
AN
122 _lastTarget: null,
123
ee616cff
AN
124 /**
125 * The list of menu items.
126 *
127 * @property _menuOptions
128 * @type Object
129 * @private
130 */
131 _menuOptions: null,
132
62467795
AN
133 initializer: function() {
134 this.addButton({
135 icon: 'e/table',
136 callback: this._displayTableEditor,
137 tags: 'table'
138 });
139
140 // Disable mozilla table controls.
141 if (Y.UA.gecko) {
142 document.execCommand("enableInlineTableEditing", false, false);
143 document.execCommand("enableObjectResizing", false, false);
144 }
145 },
adca7326
DW
146
147 /**
62467795 148 * Display the table tool.
adca7326 149 *
62467795
AN
150 * @method _displayDialogue
151 * @private
adca7326 152 */
62467795
AN
153 _displayDialogue: function() {
154 // Store the current cursor position.
155 this._currentSelection = this.get('host').getSelection();
156
157 if (this._currentSelection !== false && (!this._currentSelection.collapsed)) {
158 var dialogue = this.getDialogue({
159 headerContent: M.util.get_string('createtable', COMPONENT),
160 focusAfterHide: true
161 });
162
163 // Set the dialogue content, and then show the dialogue.
164 dialogue.set('bodyContent', this._getDialogueContent())
165 .show();
166 }
167 },
adca7326
DW
168
169 /**
62467795 170 * Display the appropriate table editor.
adca7326 171 *
62467795
AN
172 * If the current selection includes a table, then we show the
173 * contextual menu, otherwise show the table creation dialogue.
174 *
175 * @method _displayTableEditor
176 * @param {EventFacade} e
177 * @private
adca7326 178 */
62467795 179 _displayTableEditor: function(e) {
7b280674
DW
180 var selection = this.get('host').getSelectionParentNode(),
181 cell;
182
183 if (!selection) {
184 // We don't have a current selection at all, so show the standard dialogue.
185 return this._displayDialogue(e);
186 }
187
188 // Check all of the table cells found in the selection.
189 Y.one(selection).ancestors('th, td', true).each(function(node) {
190 if (this.editor.contains(node)) {
191 cell = node;
192 }
193 }, this);
194
62467795
AN
195 if (cell) {
196 // Add the cell to the EventFacade to save duplication in when showing the menu.
197 e.tableCell = cell;
198 return this._showTableMenu(e);
199 }
adca7326 200
7b280674 201 return this._displayDialogue(e);
3a6511a5
JC
202 },
203
353473aa
DW
204 /**
205 * Return the edit table dialogue content, attaching any required
206 * events.
207 *
208 * @method _getEditDialogueContent
209 * @private
210 * @return {Node} The content to place in the dialogue.
211 */
212 _getEditDialogueContent: function() {
213 var template = Y.Handlebars.compile(EDITTEMPLATE);
214 this._content = Y.Node.create(template({
215 CSS: CSS,
216 elementid: this.get('host').get('elementid'),
217 component: COMPONENT
218 }));
219
220 // Handle table setting.
221 this._content.one('.submit').on('click', this._updateTable, this);
222
223 return this._content;
224 },
225
adca7326 226 /**
62467795
AN
227 * Return the dialogue content for the tool, attaching any required
228 * events.
adca7326 229 *
62467795
AN
230 * @method _getDialogueContent
231 * @private
232 * @return {Node} The content to place in the dialogue.
adca7326 233 */
62467795
AN
234 _getDialogueContent: function() {
235 var template = Y.Handlebars.compile(TEMPLATE);
236 this._content = Y.Node.create(template({
237 CSS: CSS,
238 elementid: this.get('host').get('elementid'),
239 component: COMPONENT
240 }));
241
242 // Handle table setting.
243 this._content.one('.submit').on('click', this._setTable, this);
244
245 return this._content;
246 },
adca7326 247
353473aa
DW
248 /**
249 * Change a node from one type to another, copying all attributes and children.
250 *
251 * @method _changeNodeType
252 * @param {Y.Node} node
253 * @param {String} new node type
254 * @private
255 * @chainable
256 */
257 _changeNodeType: function(node, newType) {
258 var newNode = Y.Node.create('<' + newType + '></' + newType + '>');
259 newNode.setAttrs(node.getAttrs());
260 node.get('childNodes').each(function(child) {
261 newNode.append(child.remove());
262 });
263 node.replace(newNode);
264 return newNode;
265 },
266
267 /**
268 * Handle updating an existing table.
269 *
270 * @method _updateTable
271 * @param {EventFacade} e
272 * @private
273 */
274 _updateTable: function(e) {
275 var caption,
276 headers,
277 table,
278 captionnode;
279
280 e.preventDefault();
281 // Hide the dialogue.
282 this.getDialogue({
283 focusAfterHide: null
284 }).hide();
285
286 // Add/update the caption.
287 caption = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTION);
288 headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
289
290 table = this._lastTarget.ancestor('table');
291
292 captionnode = table.one('caption');
293 if (!captionnode) {
294 captionnode = Y.Node.create('<caption></caption');
295 table.insert(captionnode, 0);
296 }
297 captionnode.setHTML(caption.get('value'));
298
299 // Add the row headers.
300 if (headers.get('value') === 'rows' || headers.get('value') === 'both') {
301 table.all('tr').each(function (row) {
302 var cells = row.all('th, td'),
303 firstCell = cells.shift(),
304 newCell;
305
306 if (firstCell.get('tagName') === 'TD') {
307 // Cell is a td but should be a th - change it.
308 newCell = this._changeNodeType(firstCell, 'th');
309 newCell.setAttribute('scope', 'row');
310 } else {
311 firstCell.setAttribute('scope', 'row');
312 }
313
314 // Now make sure all other cells in the row are td.
315 cells.each(function (cell) {
316 if (cell.get('tagName') === 'TH') {
317 newCell = this._changeNodeType(cell, 'td');
318 newCell.removeAttribute('scope');
319 }
320 }, this);
321
322 }, this);
323 }
324 // Add the col headers. These may overrule the row headers in the first cell.
325 if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
326 var rows = table.all('tr'),
327 firstRow = rows.shift(),
328 newCell;
329
330 firstRow.all('td, th').each(function (cell) {
331 if (cell.get('tagName') === 'TD') {
332 // Cell is a td but should be a th - change it.
333 newCell = this._changeNodeType(cell, 'th');
334 newCell.setAttribute('scope', 'col');
335 } else {
336 cell.setAttribute('scope', 'col');
337 }
338 }, this);
339 // Change all the cells in the rest of the table to tds (unless they are row headers).
340 rows.each(function(row) {
341 var cells = row.all('th, td');
342
343 if (headers.get('value') === 'both') {
344 // Ignore the first cell because it's a row header.
345 cells.shift();
346 }
347 cells.each(function(cell) {
348 if (cell.get('tagName') === 'TH') {
349 newCell = this._changeNodeType(cell, 'td');
350 newCell.removeAttribute('scope');
351 }
352 }, this);
353
354 }, this);
355 }
356 },
357
adca7326 358 /**
62467795 359 * Handle creation of a new table.
adca7326 360 *
62467795
AN
361 * @method _setTable
362 * @param {EventFacade} e
363 * @private
adca7326 364 */
62467795
AN
365 _setTable: function(e) {
366 var caption,
367 rows,
368 cols,
369 headers,
370 tablehtml,
371 i, j;
372
adca7326 373 e.preventDefault();
adca7326 374
62467795
AN
375 // Hide the dialogue.
376 this.getDialogue({
377 focusAfterHide: null
378 }).hide();
379
48dc9f01 380 caption = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTION);
353473aa
DW
381 rows = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.ROWS);
382 cols = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.COLUMNS);
383 headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
62467795
AN
384
385 // Set the selection.
386 this.get('host').setSelection(this._currentSelection);
387
388 // Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
389 var nl = "\n";
390 tablehtml = '<br/>' + nl + '<table>' + nl;
391 tablehtml += '<caption>' + Y.Escape.html(caption.get('value')) + '</caption>' + nl;
adca7326 392
62467795
AN
393 i = 0;
394 if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
395 i = 1;
396 tablehtml += '<thead>' + nl + '<tr>' + nl;
397 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
398 tablehtml += '<th scope="col"></th>' + nl;
399 }
400 tablehtml += '</tr>' + nl + '</thead>' + nl;
401 }
402 tablehtml += '<tbody>' + nl;
403 for (; i < parseInt(rows.get('value'), 10); i++) {
404 tablehtml += '<tr>' + nl;
405 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
406 if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
407 tablehtml += '<th scope="row"></th>' + nl;
408 } else {
409 tablehtml += '<td></td>' + nl;
410 }
411 }
412 tablehtml += '</tr>' + nl;
adca7326 413 }
62467795
AN
414 tablehtml += '</tbody>' + nl;
415 tablehtml += '</table>' + nl + '<br/>';
adca7326 416
62467795
AN
417 this.get('host').insertContentAtFocusPoint(tablehtml);
418
419 // Mark the content as updated.
420 this.markUpdated();
adca7326
DW
421 },
422
353473aa
DW
423 /**
424 * Search for all the cells in the current, next and previous columns.
425 *
426 * @method _findColumnCells
427 * @private
428 * @return {Object} containing current, prev and next {Y.NodeList}s
429 */
430 _findColumnCells: function() {
431 var columnindex = this._getColumnIndex(this._lastTarget),
432 rows = this._lastTarget.ancestor('table').all('tr'),
433 currentcells = new Y.NodeList(),
434 prevcells = new Y.NodeList(),
435 nextcells = new Y.NodeList();
436
437 rows.each(function(row) {
438 var cells = row.all('td, th'),
439 cell = cells.item(columnindex),
440 cellprev = cells.item(columnindex-1),
441 cellnext = cells.item(columnindex+1);
442 currentcells.push(cell);
443 if (cellprev) {
444 prevcells.push(cellprev);
445 }
446 if (cellnext) {
447 nextcells.push(cellnext);
448 }
449 });
450
451 return {
452 current: currentcells,
453 prev: prevcells,
454 next: nextcells
455 };
456 },
457
458 /**
459 * Hide the entries in the context menu that don't make sense with the
460 * current selection.
461 *
462 * @method _hideInvalidEntries
463 * @param {Y.Node} node - The node containing the menu.
464 * @private
465 */
466 _hideInvalidEntries: function(node) {
467 // Moving rows.
468 var table = this._lastTarget.ancestor('table'),
469 row = this._lastTarget.ancestor('tr'),
470 rows = table.all('tr'),
471 rowindex = rows.indexOf(row),
472 prevrow = rows.item(rowindex - 1),
473 prevrowhascells = prevrow ? prevrow.one('td') : null;
474
475 if (!row || !prevrowhascells) {
476 node.one('[data-change="moverowup"]').hide();
477 } else {
478 node.one('[data-change="moverowup"]').show();
479 }
480
481 var nextrow = rows.item(rowindex + 1),
482 rowhascell = row ? row.one('td') : false;
483
484 if (!row || !nextrow || !rowhascell) {
485 node.one('[data-change="moverowdown"]').hide();
486 } else {
487 node.one('[data-change="moverowdown"]').show();
488 }
489
490 // Moving columns.
491 var cells = this._findColumnCells();
492 if (cells.prev.filter('td').size() > 0) {
493 node.one('[data-change="movecolumnleft"]').show();
494 } else {
495 node.one('[data-change="movecolumnleft"]').hide();
496 }
497
498 var colhascell = cells.current.filter('td').size() > 0;
499 if ((cells.next.size() > 0) && colhascell) {
500 node.one('[data-change="movecolumnright"]').show();
501 } else {
502 node.one('[data-change="movecolumnright"]').hide();
503 }
504
505 // Delete col
506 if (cells.current.filter('td').size() > 0) {
507 node.one('[data-change="deletecolumn"]').show();
508 } else {
509 node.one('[data-change="deletecolumn"]').hide();
510 }
511 // Delete row
512 if (!row || !row.one('td')) {
513 node.one('[data-change="deleterow"]').hide();
514 } else {
515 node.one('[data-change="deleterow"]').show();
516 }
517 },
518
adca7326 519 /**
62467795 520 * Display the table menu.
adca7326 521 *
62467795
AN
522 * @method _showTableMenu
523 * @param {EventFacade} e
524 * @private
adca7326 525 */
62467795 526 _showTableMenu: function(e) {
adca7326
DW
527 e.preventDefault();
528
62467795
AN
529 var boundingBox;
530
531 if (!this._contextMenu) {
ee616cff
AN
532 this._menuOptions = [
533 {
534 text: M.util.get_string("addcolumnafter", COMPONENT),
535 data: {
536 change: "addcolumnafter"
537 }
538 }, {
539 text: M.util.get_string("addrowafter", COMPONENT),
540 data: {
541 change: "addrowafter"
542 }
543 }, {
544 text: M.util.get_string("moverowup", COMPONENT),
545 data: {
546 change: "moverowup"
547 }
548 }, {
549 text: M.util.get_string("moverowdown", COMPONENT),
550 data: {
551 change: "moverowdown"
552 }
553 }, {
554 text: M.util.get_string("movecolumnleft", COMPONENT),
555 data: {
556 change: "movecolumnleft"
557 }
558 }, {
559 text: M.util.get_string("movecolumnright", COMPONENT),
560 data: {
561 change: "movecolumnright"
562 }
563 }, {
564 text: M.util.get_string("deleterow", COMPONENT),
565 data: {
566 change: "deleterow"
567 }
568 }, {
569 text: M.util.get_string("deletecolumn", COMPONENT),
570 data: {
571 change: "deletecolumn"
572 }
92810c07
DW
573 }, {
574 text: M.util.get_string("edittable", COMPONENT),
575 data: {
576 change: "edittable"
577 }
ee616cff
AN
578 }
579 ];
62467795
AN
580
581 this._contextMenu = new Y.M.editor_atto.Menu({
ee616cff 582 items: this._menuOptions
adca7326 583 });
62467795
AN
584
585 // Add event handlers for table control menus.
586 boundingBox = this._contextMenu.get('boundingBox');
587 boundingBox.delegate('click', this._handleTableChange, 'a', this);
adca7326 588 }
ee616cff 589
62467795
AN
590 boundingBox = this._contextMenu.get('boundingBox');
591
adca7326 592 // We store the cell of the last click (the control node is transient).
62467795
AN
593 this._lastTarget = e.tableCell.ancestor('.editor_atto_content td, .editor_atto_content th', true);
594
353473aa
DW
595 this._hideInvalidEntries(boundingBox);
596
c63f9053
AN
597 // Clear the focusAfterHide for any other menus which may be open.
598 Y.Array.each(this.get('host').openMenus, function(menu) {
599 menu.set('focusAfterHide', null);
600 });
601
602 // Ensure that we focus on the button in the toolbar when we tab back to the menu.
603 var creatorButton = this.buttons[this.name];
604 this.get('host')._setTabFocus(creatorButton);
605
62467795
AN
606 // Show the context menu, and align to the current position.
607 this._contextMenu.show();
f8c3af13 608 this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
c63f9053 609 this._contextMenu.set('focusAfterHide', creatorButton);
62467795
AN
610
611 // If there are any anchors in the bounding box, focus on the first.
612 if (boundingBox.one('a')) {
613 boundingBox.one('a').focus();
26f8822d 614 }
c63f9053
AN
615
616 // Add this menu to the list of open menus.
617 this.get('host').openMenus = [this._contextMenu];
62467795
AN
618 },
619
620 /**
621 * Handle a selection from the table control menu.
622 *
623 * @method _handleTableChange
624 * @param {EventFacade} e
625 * @private
626 */
627 _handleTableChange: function(e) {
628 e.preventDefault();
adca7326 629
ee616cff 630 this._contextMenu.set('focusAfterHide', this.get('host').editor);
62467795 631 // Hide the context menu.
ee616cff 632 this._contextMenu.hide(e);
62467795
AN
633
634 // Make our changes.
635 switch (e.target.getData('change')) {
636 case 'addcolumnafter':
637 this._addColumnAfter();
638 break;
639 case 'addrowafter':
640 this._addRowAfter();
641 break;
642 case 'deleterow':
643 this._deleteRow();
644 break;
645 case 'deletecolumn':
646 this._deleteColumn();
647 break;
353473aa
DW
648 case 'edittable':
649 this._editTable();
650 break;
62467795
AN
651 case 'moverowdown':
652 this._moveRowDown();
653 break;
654 case 'moverowup':
655 this._moveRowUp();
656 break;
657 case 'movecolumnleft':
658 this._moveColumnLeft();
659 break;
660 case 'movecolumnright':
661 this._moveColumnRight();
662 break;
adca7326
DW
663 }
664 },
665
666 /**
667 * Determine the index of a row in a table column.
668 *
62467795
AN
669 * @method _getRowIndex
670 * @param {Node} cell
671 * @private
adca7326 672 */
62467795 673 _getRowIndex: function(cell) {
adca7326
DW
674 var tablenode = cell.ancestor('table'),
675 rownode = cell.ancestor('tr');
676
677 if (!tablenode || !rownode) {
678 return;
679 }
680
681 var rows = tablenode.all('tr');
682
683 return rows.indexOf(rownode);
684 },
685
686 /**
687 * Determine the index of a column in a table row.
688 *
62467795
AN
689 * @method _getColumnIndex
690 * @param {Node} cellnode
691 * @private
adca7326 692 */
62467795 693 _getColumnIndex: function(cellnode) {
adca7326
DW
694 var rownode = cellnode.ancestor('tr');
695
696 if (!rownode) {
697 return;
698 }
699
700 var cells = rownode.all('td, th');
701
702 return cells.indexOf(cellnode);
703 },
704
705 /**
62467795 706 * Delete the current row.
adca7326 707 *
62467795
AN
708 * @method _deleteRow
709 * @private
adca7326 710 */
62467795
AN
711 _deleteRow: function() {
712 var row = this._lastTarget.ancestor('tr');
adca7326 713
353473aa
DW
714 if (row && row.one('td')) {
715 // Only delete rows with at least one non-header cell.
716 row.remove(true);
adca7326
DW
717 }
718
719 // Clean the HTML.
62467795 720 this.markUpdated();
adca7326
DW
721 },
722
723 /**
724 * Move row up
725 *
62467795
AN
726 * @method _moveRowUp
727 * @private
adca7326 728 */
62467795 729 _moveRowUp: function() {
353473aa
DW
730 var row = this._lastTarget.ancestor('tr'),
731 prevrow = row.previous('tr');
adca7326
DW
732 if (!row || !prevrow) {
733 return;
734 }
735
736 row.swap(prevrow);
737 // Clean the HTML.
62467795 738 this.markUpdated();
adca7326
DW
739 },
740
741 /**
742 * Move column left
743 *
62467795
AN
744 * @method _moveColumnLeft
745 * @private
adca7326 746 */
62467795 747 _moveColumnLeft: function() {
353473aa 748 var cells = this._findColumnCells();
adca7326 749
353473aa 750 if (cells.current.size() > 0 && cells.prev.size() > 0 && cells.current.size() === cells.prev.size()) {
adca7326 751 var i = 0;
353473aa
DW
752 for (i = 0; i < cells.current.size(); i++) {
753 var cell = cells.current.item(i),
754 prevcell = cells.prev.item(i);
adca7326
DW
755
756 cell.swap(prevcell);
757 }
758 }
759 // Cleanup.
62467795 760 this.markUpdated();
adca7326
DW
761 },
762
353473aa
DW
763 /**
764 * Add a caption to the table if it doesn't have one.
765 *
766 * @method _addCaption
767 * @private
768 */
769 _addCaption: function() {
770 var table = this._lastTarget.ancestor('table'),
771 caption = table.one('caption');
772
773 if (!caption) {
774 table.insert(Y.Node.create('<caption>&nbsp;</caption>'), 1);
775 }
776 },
777
778 /**
779 * Remove a caption from the table if has one.
780 *
781 * @method _removeCaption
782 * @private
783 */
784 _removeCaption: function() {
785 var table = this._lastTarget.ancestor('table'),
786 caption = table.one('caption');
787
788 if (caption) {
789 caption.remove(true);
790 }
791 },
792
adca7326 793 /**
62467795 794 * Move column right.
adca7326 795 *
62467795
AN
796 * @method _moveColumnRight
797 * @private
adca7326 798 */
62467795 799 _moveColumnRight: function() {
353473aa 800 var cells = this._findColumnCells();
adca7326 801
353473aa
DW
802 // Check we have some tds in this column, and one exists to the right.
803 if ( (cells.next.size() > 0) &&
804 (cells.current.size() === cells.next.size()) &&
805 (cells.current.filter('td').size() > 0)) {
adca7326 806 var i = 0;
353473aa
DW
807 for (i = 0; i < cells.current.size(); i++) {
808 var cell = cells.current.item(i),
809 nextcell = cells.next.item(i);
adca7326
DW
810
811 cell.swap(nextcell);
812 }
813 }
814 // Cleanup.
62467795 815 this.markUpdated();
adca7326
DW
816 },
817
818 /**
62467795 819 * Move row down.
adca7326 820 *
62467795
AN
821 * @method _moveRowDown
822 * @private
adca7326 823 */
62467795 824 _moveRowDown: function() {
353473aa
DW
825 var row = this._lastTarget.ancestor('tr'),
826 nextrow = row.next('tr');
827 if (!row || !nextrow || !row.one('td')) {
adca7326
DW
828 return;
829 }
830
831 row.swap(nextrow);
832 // Clean the HTML.
62467795 833 this.markUpdated();
adca7326
DW
834 },
835
353473aa
DW
836 /**
837 * Edit table (show the dialogue).
838 *
839 * @method _editTable
840 * @private
841 */
842 _editTable: function() {
843 var dialogue = this.getDialogue({
844 headerContent: M.util.get_string('edittable', COMPONENT),
845 focusAfterHide: false
846 });
847
848 // Set the dialogue content, and then show the dialogue.
849 var node = this._getEditDialogueContent(),
850 captioninput = node.one(SELECTORS.CAPTION),
851 headersinput = node.one(SELECTORS.HEADERS),
852 table = this._lastTarget.ancestor('table'),
853 captionnode = table.one('caption');
854
855 if (captionnode) {
856 captioninput.set('value', captionnode.getHTML());
857 } else {
858 captioninput.set('value', '');
859 }
860
861 var headersvalue = 'columns';
862 if (table.one('th[scope="row"]')) {
863 headersvalue = 'rows';
864 if (table.one('th[scope="col"]')) {
865 headersvalue = 'both';
866 }
867 }
868 headersinput.set('value', headersvalue);
869 dialogue.set('bodyContent', node).show();
870 },
871
872
adca7326 873 /**
62467795 874 * Delete the current column.
adca7326 875 *
62467795
AN
876 * @method _deleteColumn
877 * @private
adca7326 878 */
62467795 879 _deleteColumn: function() {
353473aa
DW
880 var columnindex = this._getColumnIndex(this._lastTarget),
881 table = this._lastTarget.ancestor('table'),
882 rows = table.all('tr'),
883 columncells = new Y.NodeList(),
884 hastd = false;
adca7326
DW
885
886 rows.each(function(row) {
887 var cells = row.all('td, th');
888 var cell = cells.item(columnindex);
889 if (cell.get('tagName') === 'TD') {
890 hastd = true;
891 }
892 columncells.push(cell);
893 });
894
353473aa 895 // Do not delete all the headers.
adca7326
DW
896 if (hastd) {
897 columncells.remove(true);
898 }
899
900 // Clean the HTML.
62467795 901 this.markUpdated();
adca7326
DW
902 },
903
904 /**
905 * Add a row after the current row.
906 *
62467795
AN
907 * @method _addRowAfter
908 * @private
adca7326 909 */
62467795
AN
910 _addRowAfter: function() {
911 var rowindex = this._getRowIndex(this._lastTarget);
adca7326 912
62467795 913 var tablebody = this._lastTarget.ancestor('table').one('tbody');
adca7326
DW
914 if (!tablebody) {
915 // Not all tables have tbody.
62467795 916 tablebody = this._lastTarget.ancestor('table');
adca7326
DW
917 rowindex += 1;
918 }
919
920 var firstrow = tablebody.one('tr');
921 if (!firstrow) {
62467795 922 firstrow = this._lastTarget.ancestor('table').one('tr');
adca7326
DW
923 }
924 if (!firstrow) {
925 // Table has no rows. Boo.
926 return;
927 }
928 newrow = firstrow.cloneNode(true);
929 newrow.all('th, td').each(function (tablecell) {
930 if (tablecell.get('tagName') === 'TH') {
931 if (tablecell.getAttribute('scope') !== 'row') {
932 var newcell = Y.Node.create('<td></td>');
933 tablecell.replace(newcell);
934 tablecell = newcell;
935 }
936 }
937 tablecell.setHTML('&nbsp;');
938 });
939
940 tablebody.insert(newrow, rowindex);
941
942 // Clean the HTML.
62467795 943 this.markUpdated();
adca7326
DW
944 },
945
946 /**
947 * Add a column after the current column.
948 *
62467795
AN
949 * @method _addColumnAfter
950 * @private
adca7326 951 */
62467795 952 _addColumnAfter: function() {
353473aa
DW
953 var cells = this._findColumnCells(),
954 before = true,
955 clonecells = cells.next;
956 if (cells.next.size() <= 0) {
957 before = false;
958 clonecells = cells.current;
959 }
adca7326 960
353473aa
DW
961 Y.each(clonecells, function(cell) {
962 var newcell = cell.cloneNode();
adca7326
DW
963 // Clear the content of the cell.
964 newcell.setHTML('&nbsp;');
965
353473aa
DW
966 if (before) {
967 cell.get('parentNode').insert(newcell, cell);
968 } else {
969 cell.get('parentNode').insert(newcell, cell);
970 cell.swap(newcell);
971 }
adca7326
DW
972 }, this);
973
974 // Clean the HTML.
62467795 975 this.markUpdated();
adca7326 976 }
353473aa 977
62467795 978});