Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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