weekly release 2.7dev
[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',
37 TEMPLATE = '' +
38 '<form class="atto_form">' +
39 '<label for="{{elementid}}_atto_table_caption">{{get_string "caption" component}}</label>' +
dfd51275 40 '<input class="caption fullwidth" id="{{elementid}}_atto_table_caption" required />' +
62467795
AN
41 '<br/>' +
42 '<label for="{{elementid}}_atto_table_headers" class="sameline">{{get_string "headers" component}}</label>' +
43 '<select class="headers" id="{{elementid}}_atto_table_headers">' +
44 '<option value="columns">{{get_string "columns" component}}' + '</option>' +
45 '<option value="rows">{{get_string "rows" component}}' + '</option>' +
46 '<option value="both">{{get_string "both" component}}' + '</option>' +
47 '</select>' +
48 '<br/>' +
49 '<label for="{{elementid}}_atto_table_rows" class="sameline">{{get_string "numberofrows" component}}</label>' +
50 '<input class="rows" type="number" value="3" id="{{elementid}}_atto_table_rows" size="8" min="1" max="50"/>' +
51 '<br/>' +
52 '<label for="{{elementid}}_atto_table_columns" class="sameline">{{get_string "numberofcolumns" component}}</label>' +
53 '<input class="columns" type="number" value="3" id="{{elementid}}_atto_table_columns" size="8" min="1" max="20"/>' +
54 '<br/>' +
55 '<div class="mdl-align">' +
56 '<br/>' +
57 '<button class="submit" type="submit">{{get_string "createtable" component}}</button>' +
58 '</div>' +
23cead68 59 '</form>',
62467795
AN
60 CONTEXTMENUTEMPLATE = '' +
61 '<ul>' +
62 '<li><a href="#" data-change="addcolumnafter">{{get_string "addcolumnafter" component}}</a></li>' +
63 '<li><a href="#" data-change="addrowafter">{{get_string "addrowafter" component}}</a></li>' +
64 '<li><a href="#" data-change="moverowup">{{get_string "moverowup" component}}</a></li>' +
65 '<li><a href="#" data-change="moverowdown">{{get_string "moverowdown" component}}</a></li>' +
66 '<li><a href="#" data-change="movecolumnleft">{{get_string "movecolumnleft" component}}</a></li>' +
67 '<li><a href="#" data-change="movecolumnright">{{get_string "movecolumnright" component}}</a></li>' +
68 '<li><a href="#" data-change="deleterow">{{get_string "deleterow" component}}</a></li>' +
69 '<li><a href="#" data-change="deletecolumn">{{get_string "deletecolumn" component}}</a></li>' +
70 '</ul>';
71
72 CSS = {
73 };
74
75Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
adca7326
DW
76
77 /**
62467795
AN
78 * A reference to the current selection at the time that the dialogue
79 * was opened.
adca7326 80 *
62467795
AN
81 * @property _currentSelection
82 * @type Range
83 * @private
adca7326 84 */
62467795 85 _currentSelection: null,
adca7326
DW
86
87 /**
62467795 88 * The contextual menu that we can open.
adca7326 89 *
62467795
AN
90 * @property _contextMenu
91 * @type M.editor_atto.Menu
92 * @private
93 */
94 _contextMenu: null,
95
96 /**
97 * The last modified target.
98 *
99 * @property _lastTarget
100 * @type Node
101 * @private
adca7326 102 */
62467795
AN
103 _lastTarget: null,
104
105 initializer: function() {
106 this.addButton({
107 icon: 'e/table',
108 callback: this._displayTableEditor,
109 tags: 'table'
110 });
111
112 // Disable mozilla table controls.
113 if (Y.UA.gecko) {
114 document.execCommand("enableInlineTableEditing", false, false);
115 document.execCommand("enableObjectResizing", false, false);
116 }
117 },
adca7326
DW
118
119 /**
62467795 120 * Display the table tool.
adca7326 121 *
62467795
AN
122 * @method _displayDialogue
123 * @private
adca7326 124 */
62467795
AN
125 _displayDialogue: function() {
126 // Store the current cursor position.
127 this._currentSelection = this.get('host').getSelection();
128
129 if (this._currentSelection !== false && (!this._currentSelection.collapsed)) {
130 var dialogue = this.getDialogue({
131 headerContent: M.util.get_string('createtable', COMPONENT),
132 focusAfterHide: true
133 });
134
135 // Set the dialogue content, and then show the dialogue.
136 dialogue.set('bodyContent', this._getDialogueContent())
137 .show();
138 }
139 },
adca7326
DW
140
141 /**
62467795 142 * Display the appropriate table editor.
adca7326 143 *
62467795
AN
144 * If the current selection includes a table, then we show the
145 * contextual menu, otherwise show the table creation dialogue.
146 *
147 * @method _displayTableEditor
148 * @param {EventFacade} e
149 * @private
adca7326 150 */
62467795
AN
151 _displayTableEditor: function(e) {
152 var selection = this.get('host').getSelectionParentNode(),
153 cell;
154
155 if (!selection) {
156 // We don't have a current selection at all, so show the standard dialogue.
157 return this._displayDialogue(e);
158 }
159
160 // Check all of the table cells found in the selection.
161 Y.one(selection).ancestors('th, td', true).each(function(node) {
162 if (this.editor.contains(node)) {
163 cell = node;
164 }
165 }, this);
166
167 if (cell) {
168 // Add the cell to the EventFacade to save duplication in when showing the menu.
169 e.tableCell = cell;
170 return this._showTableMenu(e);
171 }
172
173 return this._displayDialogue(e);
174 },
adca7326
DW
175
176 /**
62467795
AN
177 * Return the dialogue content for the tool, attaching any required
178 * events.
adca7326 179 *
62467795
AN
180 * @method _getDialogueContent
181 * @private
182 * @return {Node} The content to place in the dialogue.
adca7326 183 */
62467795
AN
184 _getDialogueContent: function() {
185 var template = Y.Handlebars.compile(TEMPLATE);
186 this._content = Y.Node.create(template({
187 CSS: CSS,
188 elementid: this.get('host').get('elementid'),
189 component: COMPONENT
190 }));
191
192 // Handle table setting.
193 this._content.one('.submit').on('click', this._setTable, this);
194
195 return this._content;
196 },
adca7326
DW
197
198 /**
62467795 199 * Handle creation of a new table.
adca7326 200 *
62467795
AN
201 * @method _setTable
202 * @param {EventFacade} e
203 * @private
adca7326 204 */
62467795
AN
205 _setTable: function(e) {
206 var caption,
207 rows,
208 cols,
209 headers,
210 tablehtml,
211 i, j;
212
adca7326 213 e.preventDefault();
adca7326 214
62467795
AN
215 // Hide the dialogue.
216 this.getDialogue({
217 focusAfterHide: null
218 }).hide();
219
220 caption = e.currentTarget.ancestor('.atto_form').one('.caption');
221 rows = e.currentTarget.ancestor('.atto_form').one('.rows');
222 cols = e.currentTarget.ancestor('.atto_form').one('.columns');
223 headers = e.currentTarget.ancestor('.atto_form').one('.headers');
224
225 // Set the selection.
226 this.get('host').setSelection(this._currentSelection);
227
228 // Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
229 var nl = "\n";
230 tablehtml = '<br/>' + nl + '<table>' + nl;
231 tablehtml += '<caption>' + Y.Escape.html(caption.get('value')) + '</caption>' + nl;
adca7326 232
62467795
AN
233 i = 0;
234 if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
235 i = 1;
236 tablehtml += '<thead>' + nl + '<tr>' + nl;
237 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
238 tablehtml += '<th scope="col"></th>' + nl;
239 }
240 tablehtml += '</tr>' + nl + '</thead>' + nl;
241 }
242 tablehtml += '<tbody>' + nl;
243 for (; i < parseInt(rows.get('value'), 10); i++) {
244 tablehtml += '<tr>' + nl;
245 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
246 if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
247 tablehtml += '<th scope="row"></th>' + nl;
248 } else {
249 tablehtml += '<td></td>' + nl;
250 }
251 }
252 tablehtml += '</tr>' + nl;
adca7326 253 }
62467795
AN
254 tablehtml += '</tbody>' + nl;
255 tablehtml += '</table>' + nl + '<br/>';
adca7326 256
62467795
AN
257 this.get('host').insertContentAtFocusPoint(tablehtml);
258
259 // Mark the content as updated.
260 this.markUpdated();
adca7326
DW
261 },
262
263 /**
62467795 264 * Display the table menu.
adca7326 265 *
62467795
AN
266 * @method _showTableMenu
267 * @param {EventFacade} e
268 * @private
adca7326 269 */
62467795 270 _showTableMenu: function(e) {
adca7326
DW
271 e.preventDefault();
272
62467795
AN
273 var boundingBox;
274
275 if (!this._contextMenu) {
276 var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE),
277 content = Y.Node.create(template({
278 elementid: this.get('host').get('elementid'),
279 component: COMPONENT
280 }));
281
282 this._contextMenu = new Y.M.editor_atto.Menu({
283 headerText: M.util.get_string('edittable', 'atto_table'),
284 bodyContent: content
adca7326 285 });
62467795
AN
286
287 // Add event handlers for table control menus.
288 boundingBox = this._contextMenu.get('boundingBox');
289 boundingBox.delegate('click', this._handleTableChange, 'a', this);
290 boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this);
adca7326 291 }
62467795
AN
292 boundingBox = this._contextMenu.get('boundingBox');
293
adca7326 294 // We store the cell of the last click (the control node is transient).
62467795
AN
295 this._lastTarget = e.tableCell.ancestor('.editor_atto_content td, .editor_atto_content th', true);
296
297 // Show the context menu, and align to the current position.
298 this._contextMenu.show();
f8c3af13 299 this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
62467795
AN
300
301 // If there are any anchors in the bounding box, focus on the first.
302 if (boundingBox.one('a')) {
303 boundingBox.one('a').focus();
26f8822d 304 }
62467795
AN
305 },
306
307 /**
308 * Handle a selection from the table control menu.
309 *
310 * @method _handleTableChange
311 * @param {EventFacade} e
312 * @private
313 */
314 _handleTableChange: function(e) {
315 e.preventDefault();
adca7326 316
62467795
AN
317 // Hide the context menu.
318 this._contextMenu.hide();
319
320 // Make our changes.
321 switch (e.target.getData('change')) {
322 case 'addcolumnafter':
323 this._addColumnAfter();
324 break;
325 case 'addrowafter':
326 this._addRowAfter();
327 break;
328 case 'deleterow':
329 this._deleteRow();
330 break;
331 case 'deletecolumn':
332 this._deleteColumn();
333 break;
334 case 'moverowdown':
335 this._moveRowDown();
336 break;
337 case 'moverowup':
338 this._moveRowUp();
339 break;
340 case 'movecolumnleft':
341 this._moveColumnLeft();
342 break;
343 case 'movecolumnright':
344 this._moveColumnRight();
345 break;
adca7326
DW
346 }
347 },
348
349 /**
350 * Determine the index of a row in a table column.
351 *
62467795
AN
352 * @method _getRowIndex
353 * @param {Node} cell
354 * @private
adca7326 355 */
62467795 356 _getRowIndex: function(cell) {
adca7326
DW
357 var tablenode = cell.ancestor('table'),
358 rownode = cell.ancestor('tr');
359
360 if (!tablenode || !rownode) {
361 return;
362 }
363
364 var rows = tablenode.all('tr');
365
366 return rows.indexOf(rownode);
367 },
368
369 /**
370 * Determine the index of a column in a table row.
371 *
62467795
AN
372 * @method _getColumnIndex
373 * @param {Node} cellnode
374 * @private
adca7326 375 */
62467795 376 _getColumnIndex: function(cellnode) {
adca7326
DW
377 var rownode = cellnode.ancestor('tr');
378
379 if (!rownode) {
380 return;
381 }
382
383 var cells = rownode.all('td, th');
384
385 return cells.indexOf(cellnode);
386 },
387
388 /**
62467795 389 * Delete the current row.
adca7326 390 *
62467795
AN
391 * @method _deleteRow
392 * @private
adca7326 393 */
62467795
AN
394 _deleteRow: function() {
395 var row = this._lastTarget.ancestor('tr');
adca7326
DW
396
397 if (row) {
398 // We do not remove rows with no cells (all headers).
399 if (row.one('td')) {
400 row.remove(true);
401 }
402 }
403
404 // Clean the HTML.
62467795 405 this.markUpdated();
adca7326
DW
406 },
407
408 /**
409 * Move row up
410 *
62467795
AN
411 * @method _moveRowUp
412 * @private
adca7326 413 */
62467795
AN
414 _moveRowUp: function() {
415 var row = this._lastTarget.ancestor('tr');
adca7326
DW
416 var prevrow = row.previous('tr');
417 if (!row || !prevrow) {
418 return;
419 }
420
421 row.swap(prevrow);
422 // Clean the HTML.
62467795 423 this.markUpdated();
adca7326
DW
424 },
425
426 /**
427 * Move column left
428 *
62467795
AN
429 * @method _moveColumnLeft
430 * @private
adca7326 431 */
62467795
AN
432 _moveColumnLeft: function() {
433 var columnindex = this._getColumnIndex(this._lastTarget);
434 var rows = this._lastTarget.ancestor('table').all('tr');
adca7326
DW
435 var columncells = new Y.NodeList();
436 var prevcells = new Y.NodeList();
437 var hastd = false;
438
439 rows.each(function(row) {
440 var cells = row.all('td, th');
441 var cell = cells.item(columnindex),
442 cellprev = cells.item(columnindex-1);
443 columncells.push(cell);
444 if (cellprev) {
445 if (cellprev.get('tagName') === 'TD') {
446 hastd = true;
447 }
448 prevcells.push(cellprev);
449 }
450 });
451
452 if (hastd && prevcells.size() > 0) {
453 var i = 0;
454 for (i = 0; i < columncells.size(); i++) {
455 var cell = columncells.item(i);
456 var prevcell = prevcells.item(i);
457
458 cell.swap(prevcell);
459 }
460 }
461 // Cleanup.
62467795 462 this.markUpdated();
adca7326
DW
463 },
464
465 /**
62467795 466 * Move column right.
adca7326 467 *
62467795
AN
468 * @method _moveColumnRight
469 * @private
adca7326 470 */
62467795
AN
471 _moveColumnRight: function() {
472 var columnindex = this._getColumnIndex(this._lastTarget);
473 var rows = this._lastTarget.ancestor('table').all('tr');
adca7326
DW
474 var columncells = new Y.NodeList();
475 var nextcells = new Y.NodeList();
476 var hastd = false;
477
478 rows.each(function(row) {
479 var cells = row.all('td, th');
480 var cell = cells.item(columnindex),
481 cellnext = cells.item(columnindex+1);
482 if (cell.get('tagName') === 'TD') {
483 hastd = true;
484 }
485 columncells.push(cell);
486 if (cellnext) {
487 nextcells.push(cellnext);
488 }
489 });
490
491 if (hastd && nextcells.size() > 0) {
492 var i = 0;
493 for (i = 0; i < columncells.size(); i++) {
494 var cell = columncells.item(i);
495 var nextcell = nextcells.item(i);
496
497 cell.swap(nextcell);
498 }
499 }
500 // Cleanup.
62467795 501 this.markUpdated();
adca7326
DW
502 },
503
504 /**
62467795 505 * Move row down.
adca7326 506 *
62467795
AN
507 * @method _moveRowDown
508 * @private
adca7326 509 */
62467795
AN
510 _moveRowDown: function() {
511 var row = this._lastTarget.ancestor('tr');
adca7326
DW
512 var nextrow = row.next('tr');
513 if (!row || !nextrow) {
514 return;
515 }
516
517 row.swap(nextrow);
518 // Clean the HTML.
62467795 519 this.markUpdated();
adca7326
DW
520 },
521
522 /**
62467795 523 * Delete the current column.
adca7326 524 *
62467795
AN
525 * @method _deleteColumn
526 * @private
adca7326 527 */
62467795
AN
528 _deleteColumn: function() {
529 var columnindex = this._getColumnIndex(this._lastTarget);
530 var rows = this._lastTarget.ancestor('table').all('tr');
adca7326
DW
531 var columncells = new Y.NodeList();
532 var hastd = false;
533
534 rows.each(function(row) {
535 var cells = row.all('td, th');
536 var cell = cells.item(columnindex);
537 if (cell.get('tagName') === 'TD') {
538 hastd = true;
539 }
540 columncells.push(cell);
541 });
542
543 if (hastd) {
544 columncells.remove(true);
545 }
546
547 // Clean the HTML.
62467795 548 this.markUpdated();
adca7326
DW
549 },
550
551 /**
552 * Add a row after the current row.
553 *
62467795
AN
554 * @method _addRowAfter
555 * @private
adca7326 556 */
62467795
AN
557 _addRowAfter: function() {
558 var rowindex = this._getRowIndex(this._lastTarget);
adca7326 559
62467795 560 var tablebody = this._lastTarget.ancestor('table').one('tbody');
adca7326
DW
561 if (!tablebody) {
562 // Not all tables have tbody.
62467795 563 tablebody = this._lastTarget.ancestor('table');
adca7326
DW
564 rowindex += 1;
565 }
566
567 var firstrow = tablebody.one('tr');
568 if (!firstrow) {
62467795 569 firstrow = this._lastTarget.ancestor('table').one('tr');
adca7326
DW
570 }
571 if (!firstrow) {
572 // Table has no rows. Boo.
573 return;
574 }
575 newrow = firstrow.cloneNode(true);
576 newrow.all('th, td').each(function (tablecell) {
577 if (tablecell.get('tagName') === 'TH') {
578 if (tablecell.getAttribute('scope') !== 'row') {
579 var newcell = Y.Node.create('<td></td>');
580 tablecell.replace(newcell);
581 tablecell = newcell;
582 }
583 }
584 tablecell.setHTML('&nbsp;');
585 });
586
587 tablebody.insert(newrow, rowindex);
588
589 // Clean the HTML.
62467795 590 this.markUpdated();
adca7326
DW
591 },
592
593 /**
594 * Add a column after the current column.
595 *
62467795
AN
596 * @method _addColumnAfter
597 * @private
adca7326 598 */
62467795
AN
599 _addColumnAfter: function() {
600 var columnindex = this._getColumnIndex(this._lastTarget);
adca7326 601
62467795 602 var tablecell = this._lastTarget.ancestor('table');
adca7326
DW
603 var rows = tablecell.all('tr');
604 Y.each(rows, function(row) {
605 // Clone the first cell from the row so it has the same type/attributes (e.g. scope).
606 var newcell = row.one('td, th').cloneNode(true);
607 // Clear the content of the cell.
608 newcell.setHTML('&nbsp;');
609
610 row.insert(newcell, columnindex + 1);
611 }, this);
612
613 // Clean the HTML.
62467795 614 this.markUpdated();
adca7326 615 }
62467795 616});
adca7326
DW
617
618
62467795 619}, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-editor_atto-menu", "event", "event-valuechange"]});