MDL-44032: Atto - move bootstrap changes into overrides file.
[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
18/**
19 * Atto text editor table plugin.
20 *
21 * @package editor-atto
22 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25M.atto_table = M.atto_table || {
26
27 /**
28 * The window used to get the table details.
29 *
30 * @property dialogue
31 * @type M.core.dialogue
32 * @default null
33 */
34 dialogue : null,
35
36 /**
37 * The selection object returned by the browser.
38 *
39 * @property selection
40 * @type Range
41 * @default null
42 */
43 selection : null,
44
45 /**
46 * Yui image for table editing controls.
47 *
48 * @property menunode
49 * @type Y.Node
50 * @default null
51 */
52 menunode : null,
53
54 /**
55 * Popup menu for table controls
56 *
57 * @property controlmenu
58 * @type M.editor_atto.controlmenu
59 * @default null
60 */
61 controlmenu : null,
62
63 /**
64 * Last clicked cell that opened the context menu.
65 *
66 * @property lasttarget
67 * @type Y.Node
68 * @default null
69 */
70 lasttarget : null,
71
72 /**
73 * Display the chooser dialogue.
74 *
75 * @method init
76 * @param Event e
77 * @param string elementid
78 */
79 display_chooser : function(e, elementid) {
80 e.preventDefault();
81 if (!M.editor_atto.is_active(elementid)) {
82 M.editor_atto.focus(elementid);
83 }
84 M.atto_table.selection = M.editor_atto.get_selection();
85 if (M.atto_table.selection !== false && (!M.atto_table.selection.collapsed)) {
86 var dialogue;
87 if (!M.atto_table.dialogue) {
88 dialogue = new M.core.dialogue({
89 visible: false,
90 modal: true,
91 close: true,
92 draggable: true
93 });
94 } else {
95 dialogue = M.atto_table.dialogue;
96 }
97
98 dialogue.render();
99 dialogue.set('bodyContent', M.atto_table.get_form_content(elementid));
100 dialogue.set('headerContent', M.util.get_string('createtable', 'atto_table'));
101
102 dialogue.show();
103 M.atto_table.dialogue = dialogue;
104 }
105
106 },
107
108 /**
109 * Show the context menu
110 *
111 * @method show_menu
112 * @param Event e
113 * @param string elementid
114 */
115 show_menu : function(e, elementid) {
116 var addhandlers = false;
117
118 e.preventDefault();
119
120 if (this.controlmenu === null) {
121 addhandlers = true;
122 // Add event handlers for table control menus.
123 var bodycontent = '<ul>';
124 bodycontent += '<li><a href="#" id="addcolumnafter">' + M.util.get_string('addcolumnafter', 'atto_table') + '</a></li>';
125 bodycontent += '<li><a href="#" id="addrowafter">' + M.util.get_string('addrowafter', 'atto_table') + '</a></li>';
126 bodycontent += '<li><a href="#" id="moverowup">' + M.util.get_string('moverowup', 'atto_table') + '</a></li>';
127 bodycontent += '<li><a href="#" id="moverowdown">' + M.util.get_string('moverowdown', 'atto_table') + '</a></li>';
128 bodycontent += '<li><a href="#" id="movecolumnleft">' + M.util.get_string('movecolumnleft', 'atto_table') + '</a></li>';
129 bodycontent += '<li><a href="#" id="movecolumnright">' + M.util.get_string('movecolumnright', 'atto_table') + '</a></li>';
130 bodycontent += '<li><a href="#" id="deleterow">' + M.util.get_string('deleterow', 'atto_table') + '</a></li>';
131 bodycontent += '<li><a href="#" id="deletecolumn">' + M.util.get_string('deletecolumn', 'atto_table') + '</a></li>';
132 bodycontent += '</ul>';
133
134 this.controlmenu = new M.editor_atto.controlmenu({
135 headerText : M.util.get_string('edittable', 'atto_table'),
136 bodyContent : bodycontent
137 });
138 }
139 // We store the cell of the last click (the control node is transient).
140 this.lasttarget = e.target.ancestor('td, th');
141 this.controlmenu.show();
142 this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
26f8822d
DW
143 var bodynode = this.controlmenu.get('boundingBox');
144 if (bodynode.one('a')) {
145 bodynode.one('a').focus();
146 }
adca7326
DW
147
148 if (addhandlers) {
adca7326
DW
149 bodynode.delegate('click', this.handle_table_control, 'a', this, elementid);
150 bodynode.delegate('key', this.handle_table_control, 'down:enter,space', 'a', this, elementid);
151 }
152 },
153
154 /**
155 * Determine the index of a row in a table column.
156 *
157 * @method get_row_index
158 * @param Y.Node node
159 */
160 get_row_index : function(cell) {
161 var tablenode = cell.ancestor('table'),
162 rownode = cell.ancestor('tr');
163
164 if (!tablenode || !rownode) {
165 return;
166 }
167
168 var rows = tablenode.all('tr');
169
170 return rows.indexOf(rownode);
171 },
172
173 /**
174 * Determine the index of a column in a table row.
175 *
176 * @method get_column_index
177 * @param Y.Node node
178 */
179 get_column_index : function(cellnode) {
180 var rownode = cellnode.ancestor('tr');
181
182 if (!rownode) {
183 return;
184 }
185
186 var cells = rownode.all('td, th');
187
188 return cells.indexOf(cellnode);
189 },
190
191 /**
192 * Delete the current row
193 *
194 * @method delete_row
195 * @param string elementid
196 */
197 delete_row : function(elementid) {
198 var row = this.lasttarget.ancestor('tr');
199
200 if (row) {
201 // We do not remove rows with no cells (all headers).
202 if (row.one('td')) {
203 row.remove(true);
204 }
205 }
206
207 // Clean the HTML.
208 M.editor_atto.text_updated(elementid);
209 },
210
211 /**
212 * Move row up
213 *
214 * @method move_row_up
215 * @param string elementid
216 */
217 move_row_up : function(elementid) {
218 var row = this.lasttarget.ancestor('tr');
219 var prevrow = row.previous('tr');
220 if (!row || !prevrow) {
221 return;
222 }
223
224 row.swap(prevrow);
225 // Clean the HTML.
226 M.editor_atto.text_updated(elementid);
227 },
228
229 /**
230 * Move column left
231 *
232 * @method move_column_left
233 * @param string elementid
234 */
235 move_column_left : function(elementid) {
236 var columnindex = this.get_column_index(this.lasttarget);
237 var rows = this.lasttarget.ancestor('table').all('tr');
238 var columncells = new Y.NodeList();
239 var prevcells = new Y.NodeList();
240 var hastd = false;
241
242 rows.each(function(row) {
243 var cells = row.all('td, th');
244 var cell = cells.item(columnindex),
245 cellprev = cells.item(columnindex-1);
246 columncells.push(cell);
247 if (cellprev) {
248 if (cellprev.get('tagName') === 'TD') {
249 hastd = true;
250 }
251 prevcells.push(cellprev);
252 }
253 });
254
255 if (hastd && prevcells.size() > 0) {
256 var i = 0;
257 for (i = 0; i < columncells.size(); i++) {
258 var cell = columncells.item(i);
259 var prevcell = prevcells.item(i);
260
261 cell.swap(prevcell);
262 }
263 }
264 // Cleanup.
265 M.editor_atto.text_updated(elementid);
266 },
267
268 /**
269 * Move column right
270 *
271 * @method move_column_right
272 * @param string elementid
273 */
274 move_column_right : function(elementid) {
275 var columnindex = this.get_column_index(this.lasttarget);
276 var rows = this.lasttarget.ancestor('table').all('tr');
277 var columncells = new Y.NodeList();
278 var nextcells = new Y.NodeList();
279 var hastd = false;
280
281 rows.each(function(row) {
282 var cells = row.all('td, th');
283 var cell = cells.item(columnindex),
284 cellnext = cells.item(columnindex+1);
285 if (cell.get('tagName') === 'TD') {
286 hastd = true;
287 }
288 columncells.push(cell);
289 if (cellnext) {
290 nextcells.push(cellnext);
291 }
292 });
293
294 if (hastd && nextcells.size() > 0) {
295 var i = 0;
296 for (i = 0; i < columncells.size(); i++) {
297 var cell = columncells.item(i);
298 var nextcell = nextcells.item(i);
299
300 cell.swap(nextcell);
301 }
302 }
303 // Cleanup.
304 M.editor_atto.text_updated(elementid);
305 },
306
307 /**
308 * Move row down
309 *
310 * @method move_row_down
311 * @param string elementid
312 */
313 move_row_down : function(elementid) {
314 var row = this.lasttarget.ancestor('tr');
315 var nextrow = row.next('tr');
316 if (!row || !nextrow) {
317 return;
318 }
319
320 row.swap(nextrow);
321 // Clean the HTML.
322 M.editor_atto.text_updated(elementid);
323 },
324
325 /**
326 * Delete the current column
327 *
328 * @method delete_column
329 * @param string elementid
330 */
331 delete_column : function(elementid) {
332 var columnindex = this.get_column_index(this.lasttarget);
333 var rows = this.lasttarget.ancestor('table').all('tr');
334 var columncells = new Y.NodeList();
335 var hastd = false;
336
337 rows.each(function(row) {
338 var cells = row.all('td, th');
339 var cell = cells.item(columnindex);
340 if (cell.get('tagName') === 'TD') {
341 hastd = true;
342 }
343 columncells.push(cell);
344 });
345
346 if (hastd) {
347 columncells.remove(true);
348 }
349
350 // Clean the HTML.
351 M.editor_atto.text_updated(elementid);
352 },
353
354 /**
355 * Add a row after the current row.
356 *
357 * @method add_row_after
358 * @param string elementid
359 */
360 add_row_after : function(elementid) {
361 var rowindex = this.get_row_index(this.lasttarget);
362
363 var tablebody = this.lasttarget.ancestor('table').one('tbody');
364 if (!tablebody) {
365 // Not all tables have tbody.
366 tablebody = this.lasttarget.ancestor('table');
367 rowindex += 1;
368 }
369
370 var firstrow = tablebody.one('tr');
371 if (!firstrow) {
372 firstrow = this.lasttarget.ancestor('table').one('tr');
373 }
374 if (!firstrow) {
375 // Table has no rows. Boo.
376 return;
377 }
378 newrow = firstrow.cloneNode(true);
379 newrow.all('th, td').each(function (tablecell) {
380 if (tablecell.get('tagName') === 'TH') {
381 if (tablecell.getAttribute('scope') !== 'row') {
382 var newcell = Y.Node.create('<td></td>');
383 tablecell.replace(newcell);
384 tablecell = newcell;
385 }
386 }
387 tablecell.setHTML('&nbsp;');
388 });
389
390 tablebody.insert(newrow, rowindex);
391
392 // Clean the HTML.
393 M.editor_atto.text_updated(elementid);
394 },
395
396 /**
397 * Add a column after the current column.
398 *
399 * @method add_column_after
400 * @param string elementid
401 */
402 add_column_after : function(elementid) {
403 var columnindex = this.get_column_index(this.lasttarget);
404
405 var tablecell = this.lasttarget.ancestor('table');
406 var rows = tablecell.all('tr');
407 Y.each(rows, function(row) {
408 // Clone the first cell from the row so it has the same type/attributes (e.g. scope).
409 var newcell = row.one('td, th').cloneNode(true);
410 // Clear the content of the cell.
411 newcell.setHTML('&nbsp;');
412
413 row.insert(newcell, columnindex + 1);
414 }, this);
415
416 // Clean the HTML.
417 M.editor_atto.text_updated(elementid);
418 },
419
420 /**
421 * Handle a selection from the table control menu.
422 *
423 * @method handle_table_control
424 * @param Y.Event event
425 * @param string elementid
426 */
427 handle_table_control : function(event, elementid) {
428 event.preventDefault();
429
430 this.controlmenu.hide();
431
432 switch (event.target.get('id')) {
433 case 'addcolumnafter':
434 this.add_column_after(elementid);
435 break;
436 case 'addrowafter':
437 this.add_row_after(elementid);
438 break;
439 case 'deleterow':
440 this.delete_row(elementid);
441 break;
442 case 'deletecolumn':
443 this.delete_column(elementid);
444 break;
445 case 'moverowdown':
446 this.move_row_down(elementid);
447 break;
448 case 'moverowup':
449 this.move_row_up(elementid);
450 break;
451 case 'movecolumnleft':
452 this.move_column_left(elementid);
453 break;
454 case 'movecolumnright':
455 this.move_column_right(elementid);
456 break;
457 }
458 },
459
460 /**
461 * Add this button to the form.
462 *
463 * @method init
464 * @param {Object} params
465 */
466 init : function(params) {
467
468 if (!M.atto_table.menunode) {
469 // Used for inline table editing controls.
470 var img = Y.Node.create('<img/>');
471 img.setAttrs({
472 alt : M.util.get_string('edittable', 'atto_table'),
473 src : M.util.image_url('t/contextmenu', 'core'),
474 width : '12',
475 height : '12'
476 });
477 var anchor = Y.Node.create('<a href="#" contenteditable="false"/>');
478 anchor.appendChild(img);
479 anchor.addClass('atto_control');
480 M.atto_table.menunode = anchor;
481 }
482
55c0403c
DW
483 var iconurl = M.util.image_url('e/table', 'core');
484 M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.display_chooser, this);
adca7326 485
48bdf86f 486 var contenteditable = M.editor_atto.get_editable_node(params.elementid);
adca7326
DW
487 contenteditable.delegate('click', this.show_menu, 'td > .atto_control, th > .atto_control', this, params.elementid);
488 contenteditable.delegate('key', this.show_menu, 'down:enter,space', 'td > .atto_control, th > .atto_control', this, params.elementid);
489 // Disable mozilla table controls.
490 if (Y.UA.gecko) {
491 document.execCommand("enableInlineTableEditing", false, "false");
492 document.execCommand("enableObjectResizing", false, false);
493 }
494
495 this.insert_table_controls(params.elementid);
496
497 // Re-add the table controls whenever the content is updated.
498 M.editor_atto.add_text_updated_handler(params.elementid, this.insert_table_controls);
499 },
500
501 /**
502 * Add the table editing controls to the content area.
503 *
504 * @method insert_table_controls
505 * @param String elementid - The id of the text area backed by the content editable field.
506 */
507 insert_table_controls : function(elementid) {
48bdf86f 508 var contenteditable = M.editor_atto.get_editable_node(elementid),
adca7326
DW
509 allcells = contenteditable.all('td .atto_control,th .atto_control'),
510 cells = contenteditable.all('td:last-child,th:last-child,tbody tr:last-child > td, tbody tr:last-child > th');
511
512 allcells.each(function(node) {
513 if (cells.indexOf(node) === -1) {
514 node.remove(true);
515 }
516 });
517
518 cells.each(function(node) {
519 if (!node.one('.atto_control')) {
520 node.append(M.atto_table.menunode.cloneNode(true));
521 }
522 }, this);
523 },
524
525 /**
526 * The OK button has been pressed - make the changes to the source.
527 *
528 * @method set_table
529 * @param Event e
530 */
531 set_table : function(e, elementid) {
532 var caption,
533 rows,
534 cols,
535 headers,
536 tablehtml,
537 i, j;
538
539 e.preventDefault();
540 M.atto_table.dialogue.hide();
541
542 caption = e.currentTarget.ancestor('.atto_form').one('#atto_table_caption');
543 rows = e.currentTarget.ancestor('.atto_form').one('#atto_table_rows');
544 cols = e.currentTarget.ancestor('.atto_form').one('#atto_table_columns');
545 headers = e.currentTarget.ancestor('.atto_form').one('#atto_table_headers');
546
547 M.editor_atto.set_selection(M.atto_table.selection);
548
549 // Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
550 tablehtml = '<br/><table>';
551 tablehtml += '<caption>' + Y.Escape.html(caption.get('value')) + '</caption>';
552
553 i = 0;
554 if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
555 i = 1;
556 tablehtml += '<thead><tr>';
557 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
558 tablehtml += '<th scope="col"></th>';
559 }
560 tablehtml += '</tr></thead>';
561 }
562 tablehtml += '<tbody>';
563 for (; i < parseInt(rows.get('value'), 10); i++) {
564 tablehtml += '<tr>';
565 for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
566 if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
567 tablehtml += '<th scope="row"></th>';
568 } else {
569 tablehtml += '<td></td>';
570 }
571 }
572 tablehtml += '</tr>';
573 }
574 tablehtml += '</tbody>';
575 tablehtml += '</table><br/>';
576
577 document.execCommand('insertHTML', false, tablehtml);
578
579 // Clean the YUI ids from the HTML.
580 M.editor_atto.text_updated(elementid);
581 },
582
583 /**
584 * Return the HTML of the form to show in the dialogue.
585 *
586 * @method get_form_content
587 * @param string elementid
588 * @return string
589 */
590 get_form_content : function(elementid) {
591 var content = Y.Node.create('<form class="atto_form">' +
592 '<label for="atto_table_caption">' + M.util.get_string('caption', 'atto_table') +
593 '</label>' +
594 '<textarea id="atto_table_caption" rows="4" class="fullwidth" required></textarea>' +
595 '<br/>' +
596 '<label for="atto_table_headers" class="sameline">' + M.util.get_string('headers', 'atto_table') +
597 '</label>' +
598 '<select id="atto_table_headers">' +
599 '<option value="columns">' + M.util.get_string('columns', 'atto_table') + '</option>' +
600 '<option value="rows">' + M.util.get_string('rows', 'atto_table') + '</option>' +
601 '<option value="both">' + M.util.get_string('both', 'atto_table') + '</option>' +
602 '</select>' +
603 '<br/>' +
604 '<label for="atto_table_rows" class="sameline">' + M.util.get_string('numberofrows', 'atto_table') +
605 '</label>' +
606 '<input type="number" value="3" id="atto_table_rows" size="8" min="1" max="50"/>' +
607 '<br/>' +
608 '<label for="atto_table_columns" class="sameline">' + M.util.get_string('numberofcolumns', 'atto_table') +
609 '</label>' +
610 '<input type="number" value="3" id="atto_table_columns" size="8" min="1" max="20"/>' +
611 '<br/>' +
612 '<div class="mdl-align">' +
613 '<br/>' +
d19cf39e 614 '<button id="atto_table_submit" type="submit">' +
adca7326
DW
615 M.util.get_string('createtable', 'atto_table') +
616 '</button>' +
617 '</div>' +
618 '</form>' +
619 '<hr/>' + M.util.get_string('accessibilityhint', 'atto_table'));
620
621 content.one('#atto_table_submit').on('click', M.atto_table.set_table, this, elementid);
622 return content;
623 }
624};
625
626
627}, '@VERSION@', {"requires": ["node", "escape", "event", "event-valuechange"]});