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
 * A Menu for the Atto editor.
18
 *
19
 * @module     moodle-editor_atto-menu
20
 * @submodule  menu-base
21
 * @package    editor_atto
22
 * @copyright  2013 Damyon Wiese
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
var LOGNAME = 'moodle-editor_atto-menu';
27
var MENUDIALOGUE = '' +
28
        '<div class="open {{config.buttonClass}} atto_menu" ' +
29
            'style="min-width:{{config.innerOverlayWidth}};">' +
30
            '<ul class="dropdown-menu" role="menu" id="{{config.buttonId}}_menu" aria-labelledby="{{config.buttonId}}">' +
31
                '{{#each config.items}}' +
32
                    '<li role="none" class="atto_menuentry">' +
33
                        '<a href="#" role="menuitem" data-index="{{@index}}" {{#each data}}data-{{@key}}="{{this}}"{{/each}}>' +
34
                            '{{{text}}}' +
35
                        '</a>' +
36
                    '</li>' +
37
                '{{/each}}' +
38
            '</ul>' +
39
        '</div>';
40
 
41
/**
42
 * A Menu for the Atto editor used in Moodle.
43
 *
44
 * This is a drop down list of buttons triggered (and aligned to) a
45
 * location.
46
 *
47
 * @namespace M.editor_atto
48
 * @class Menu
49
 * @main
50
 * @constructor
51
 * @extends M.core.dialogue
52
 */
53
var Menu = function() {
54
    Menu.superclass.constructor.apply(this, arguments);
55
};
56
 
57
Y.extend(Menu, M.core.dialogue, {
58
 
59
    /**
60
     * A list of the menu handlers which have been attached here.
61
     *
62
     * @property _menuHandlers
63
     * @type Array
64
     * @private
65
     */
66
    _menuHandlers: null,
67
 
68
    /**
69
     * The menu button that controls this menu.
70
     */
71
    _menuButton: null,
72
 
73
    initializer: function(config) {
74
        var headerText,
75
            bb;
76
 
77
        this._menuHandlers = [];
78
 
79
        this._menuButton = document.getElementById(config.buttonId);
80
 
81
        // Create the actual button.
82
        var template = Y.Handlebars.compile(MENUDIALOGUE),
83
            menu = Y.Node.create(template({
84
                config: config
85
            }));
86
        this.set('bodyContent', menu);
87
 
88
        bb = this.get('boundingBox');
89
        bb.addClass('editor_atto_controlmenu');
90
        bb.addClass('editor_atto_menu');
91
 
92
        // Get the dialogue container for this menu.
93
        var content = bb.one('.moodle-dialogue-wrap');
94
        content.removeClass('moodle-dialogue-wrap')
95
            .addClass('moodle-dialogue-content');
96
        // Remove the dialog role attribute.
97
        content.removeAttribute('role');
98
        // Remove aria-labelledby in the container. The aria-labelledby attribute is properly set in the menu's template.
99
        content.removeAttribute('aria-labelledby');
100
 
101
        // Render heading if necessary.
102
        headerText = this.get('headerText').trim();
103
        if (headerText) {
104
            var heading = Y.Node.create('<h3/>')
105
                .addClass('accesshide')
106
                .setHTML(headerText);
107
            this.get('bodyContent').prepend(heading);
108
        }
109
 
110
        // Hide the header and footer node entirely.
111
        this.headerNode.hide();
112
        this.footerNode.hide();
113
 
114
        this._setupHandlers();
115
    },
116
 
117
    /**
118
     * Setup the Event handlers.
119
     *
120
     * @method _setupHandlers
121
     * @private
122
     */
123
    _setupHandlers: function() {
124
        var contentBox = this.get('contentBox');
125
        // Handle menu item selection.
126
        this._menuHandlers.push(
127
            // Select the menu item on space, and enter.
128
            contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this),
129
 
130
            // Move up and down the menu on up/down.
131
            contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this),
132
 
133
            // Hide the menu when clicking outside of it.
134
            contentBox.on('focusoutside', this.hide, this),
135
 
136
            // Hide the menu on left/right, and escape keys.
137
            contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this)
138
        );
139
    },
140
 
141
    /**
142
     * Simulate other types of menu selection.
143
     *
144
     * @method _chooseMenuItem
145
     * @param {EventFacade} e
146
     */
147
    _chooseMenuItem: function(e) {
148
        e.target.simulate('click');
149
        e.preventDefault();
150
    },
151
 
152
    /**
153
     * Hide a menu, removing all of the event handlers which trigger the hide.
154
     *
155
     * @method hide
156
     * @param {EventFacade} e
157
     */
158
    hide: function(e) {
159
        if (this.get('preventHideMenu') === true) {
160
            return;
161
        }
162
 
163
        // We must prevent the default action (left/right/escape) because
164
        // there are other listeners on the toolbar which will focus on the
165
        // editor.
166
        if (e) {
167
            e.preventDefault();
168
        }
169
 
170
        // Remove menu button's aria-expanded attribute when this menu is hidden.
171
        if (this._menuButton) {
172
            this._menuButton.removeAttribute('aria-expanded');
173
        }
174
 
175
        return Menu.superclass.hide.call(this, arguments);
176
    },
177
 
178
    /**
179
     * Implement arrow-key navigation for the items in a toolbar menu.
180
     *
181
     * @method _handleKeyboardEvent
182
     * @param {EventFacade} e The keyboard event.
183
     * @static
184
     */
185
    _handleKeyboardEvent: function(e) {
186
        // Prevent the default browser behaviour.
187
        e.preventDefault();
188
 
189
        // Get a list of all buttons in the menu.
190
        var buttons = e.currentTarget.all('a[role="menuitem"]');
191
 
192
        // On cursor moves we loops through the buttons.
193
        var found = false,
194
            index = 0,
195
            direction = 1,
196
            checkCount = 0,
197
            current = e.target.ancestor('a[role="menuitem"]', true),
198
            next;
199
 
200
        // Determine which button is currently selected.
201
        while (!found && index < buttons.size()) {
202
            if (buttons.item(index) === current) {
203
                found = true;
204
            } else {
205
                index++;
206
            }
207
        }
208
 
209
        if (!found) {
210
            Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME);
211
            return;
212
        }
213
 
214
        if (e.keyCode === 38) {
215
            // Moving up so reverse the direction.
216
            direction = -1;
217
        }
218
 
219
        // Try to find the next
220
        do {
221
            index += direction;
222
            if (index < 0) {
223
                index = buttons.size() - 1;
224
            } else if (index >= buttons.size()) {
225
                // Handle wrapping.
226
                index = 0;
227
            }
228
            next = buttons.item(index);
229
 
230
            // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
231
            checkCount++;
232
            // Loop while:
233
            // * we are not in a loop and have not already checked every button; and
234
            // * we are on a different button; and
235
            // * the next menu item is not hidden.
236
        } while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
237
 
238
        if (next) {
239
            next.focus();
240
        }
241
 
242
        e.preventDefault();
243
        e.stopImmediatePropagation();
244
    }
245
}, {
246
    NAME: "menu",
247
    ATTRS: {
248
        /**
249
         * The header for the drop down (only accessible to screen readers).
250
         *
251
         * @attribute headerText
252
         * @type String
253
         * @default ''
254
         */
255
        headerText: {
256
            value: ''
257
        }
258
 
259
    }
260
});
261
 
262
Y.Base.modifyAttrs(Menu, {
263
    /**
264
     * The width for this menu.
265
     *
266
     * @attribute width
267
     * @default 'auto'
268
     */
269
    width: {
270
        value: 'auto'
271
    },
272
 
273
    /**
274
     * When to hide this menu.
275
     *
276
     * By default, this attribute consists of:
277
     * <ul>
278
     * <li>an object which will cause the menu to hide when the user clicks outside of the menu</li>
279
     * </ul>
280
     *
281
     * @attribute hideOn
282
     */
283
    hideOn: {
284
        value: [
285
            {
286
                eventName: 'clickoutside'
287
            }
288
        ]
289
    },
290
 
291
    /**
292
     * The default list of extra classes for this menu.
293
     *
294
     * @attribute extraClasses
295
     * @type Array
296
     * @default editor_atto_menu
297
     */
298
    extraClasses: {
299
        value: [
300
            'editor_atto_menu'
301
        ]
302
    },
303
 
304
    /**
305
     * Override the responsive nature of the core dialogues.
306
     *
307
     * @attribute responsive
308
     * @type boolean
309
     * @default false
310
     */
311
    responsive: {
312
        value: false
313
    },
314
 
315
    /**
316
     * The default visibility of the menu.
317
     *
318
     * @attribute visible
319
     * @type boolean
320
     * @default false
321
     */
322
    visible: {
323
        value: false
324
    },
325
 
326
    /**
327
     * Whether to centre the menu.
328
     *
329
     * @attribute center
330
     * @type boolean
331
     * @default false
332
     */
333
    center: {
334
        value: false
335
    },
336
 
337
    /**
338
     * Hide the close button.
339
     * @attribute closeButton
340
     * @type boolean
341
     * @default false
342
     */
343
    closeButton: {
344
        value: false
345
    }
346
});
347
 
348
Y.namespace('M.editor_atto').Menu = Menu;