Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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