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