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