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
 * @module moodle-editor_atto-editor
18
 * @submodule toolbarnav
19
 */
20
 
21
/**
22
 * Toolbar Navigation functions for the Atto editor.
23
 *
24
 * See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details.
25
 *
26
 * @namespace M.editor_atto
27
 * @class EditorToolbarNav
28
 */
29
 
30
function EditorToolbarNav() {}
31
 
32
EditorToolbarNav.ATTRS = {
33
};
34
 
35
EditorToolbarNav.prototype = {
36
    /**
37
     * The current focal point for tabbing.
38
     *
39
     * @property _tabFocus
40
     * @type Node
41
     * @default null
42
     * @private
43
     */
44
    _tabFocus: null,
45
 
46
    /**
47
     * Set up the watchers for toolbar navigation.
48
     *
49
     * @method setupToolbarNavigation
50
     * @chainable
51
     */
52
    setupToolbarNavigation: function() {
53
        // Listen for Arrow left and Arrow right keys.
54
        this._wrapper.delegate('key',
55
                this.toolbarKeyboardNavigation,
56
                'down:37,39',
57
                '.' + CSS.TOOLBAR,
58
                this);
59
        this._wrapper.delegate('focus',
60
                function(e) {
61
                    this._setTabFocus(e.currentTarget);
62
                }, '.' + CSS.TOOLBAR + ' button', this);
63
 
64
        return this;
65
    },
66
 
67
    /**
68
     * Implement arrow key navigation for the buttons in the toolbar.
69
     *
70
     * @method toolbarKeyboardNavigation
71
     * @param {EventFacade} e - the keyboard event.
72
     */
73
    toolbarKeyboardNavigation: function(e) {
74
        // Prevent the default browser behaviour.
75
        e.preventDefault();
76
 
77
        // On cursor moves we loops through the buttons.
78
        var buttons = this.toolbar.all('button'),
79
            direction = 1,
80
            button,
81
            current = e.target.ancestor('button', true),
82
            innerButtons = e.target.all('button');
83
 
84
        // If we are not on a button and the element we are on contains some buttons, then move between the inner buttons.
85
        if (!current && innerButtons) {
86
            buttons = innerButtons;
87
        }
88
 
89
        if (e.keyCode === 37) {
90
            // Moving left so reverse the direction.
91
            direction = -1;
92
        }
93
 
94
        button = this._findFirstFocusable(buttons, current, direction);
95
        if (button) {
96
            button.focus();
97
            this._setTabFocus(button);
98
        } else {
99
            Y.log("Unable to find a button to focus on", 'debug', LOGNAME);
100
        }
101
    },
102
 
103
    /**
104
     * Find the first focusable button.
105
     *
106
     * @param {NodeList} buttons A list of nodes.
107
     * @param {Node} startAt The node in the list to start the search from.
108
     * @param {Number} direction The direction in which to search (1 or -1).
109
     * @return {Node | Undefined} The Node or undefined.
110
     * @method _findFirstFocusable
111
     * @private
112
     */
113
    _findFirstFocusable: function(buttons, startAt, direction) {
114
        var checkCount = 0,
115
            candidate,
116
            button,
117
            index;
118
 
119
        // Determine which button to start the search from.
120
        index = buttons.indexOf(startAt);
121
        if (index < -1) {
122
            Y.log("Unable to find the button in the list of buttons", 'debug', LOGNAME);
123
            index = 0;
124
        }
125
 
126
        // Try to find the next.
127
        while (checkCount < buttons.size()) {
128
            index += direction;
129
            if (index < 0) {
130
                index = buttons.size() - 1;
131
            } else if (index >= buttons.size()) {
132
                // Handle wrapping.
133
                index = 0;
134
            }
135
 
136
            candidate = buttons.item(index);
137
 
138
            // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
139
            checkCount++;
140
 
141
            // Loop while:
142
            // * we haven't checked every button;
143
            // * the button is hidden or disabled;
144
            // * the button is inside a hidden wrapper element.
145
            if (candidate.hasAttribute('hidden') || candidate.hasAttribute('disabled') || candidate.ancestor('[hidden]')) {
146
                continue;
147
            }
148
 
149
            button = candidate;
150
            break;
151
        }
152
 
153
        return button;
154
    },
155
 
156
    /**
157
     * Check the tab focus.
158
     *
159
     * When we disable or hide a button, we should call this method to ensure that the
160
     * focus is not currently set on an inaccessible button, otherwise tabbing to the toolbar
161
     * would be impossible.
162
     *
163
     * @method checkTabFocus
164
     * @chainable
165
     */
166
    checkTabFocus: function() {
167
        if (this._tabFocus) {
168
            if (this._tabFocus.hasAttribute('disabled') || this._tabFocus.hasAttribute('hidden')
169
                    || this._tabFocus.ancestor('.atto_group').hasAttribute('hidden')) {
170
                // Find first available button.
171
                var button = this._findFirstFocusable(this.toolbar.all('button'), this._tabFocus, -1);
172
                if (button) {
173
                    if (this._tabFocus.compareTo(document.activeElement)) {
174
                        // We should also move the focus, because the inaccessible button also has the focus.
175
                        button.focus();
176
                    }
177
                    this._setTabFocus(button);
178
                }
179
            }
180
        }
181
        return this;
182
    },
183
 
184
    /**
185
     * Sets tab focus for the toolbar to the specified Node.
186
     *
187
     * @method _setTabFocus
188
     * @param {Node} button The node that focus should now be set to
189
     * @chainable
190
     * @private
191
     */
192
    _setTabFocus: function(button) {
193
        if (this._tabFocus) {
194
            // Unset the previous entry.
195
            this._tabFocus.setAttribute('tabindex', '-1');
196
        }
197
 
198
        // Set up the new entry.
199
        this._tabFocus = button;
200
        this._tabFocus.setAttribute('tabindex', 0);
201
 
202
        // And update the activedescendant to point at the currently selected button.
203
        this.toolbar.setAttribute('aria-activedescendant', this._tabFocus.generateID());
204
 
205
        return this;
206
    }
207
};
208
 
209
Y.Base.mix(Y.M.editor_atto.Editor, [EditorToolbarNav]);