Commit | Line | Data |
---|---|---|
e845b96b RW |
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 | * This module provides a wrapper to encapsulate a lot of the common combinations of | |
18 | * user interaction we use in Moodle. | |
19 | * | |
20 | * @module core/custom_interaction_events | |
21 | * @class custom_interaction_events | |
22 | * @package core | |
23 | * @copyright 2016 Ryan Wyllie <ryan@moodle.com> | |
24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
25 | * @since 3.2 | |
26 | */ | |
27 | define(['jquery', 'core/key_codes'], function($, keyCodes) { | |
28 | // The list of events provided by this module. Namespaced to avoid clashes. | |
29 | var events = { | |
30 | activate: 'cie:activate', | |
31 | keyboardActivate: 'cie:keyboardactivate', | |
32 | escape: 'cie:escape', | |
33 | down: 'cie:down', | |
34 | up: 'cie:up', | |
35 | home: 'cie:home', | |
36 | end: 'cie:end', | |
37 | next: 'cie:next', | |
38 | previous: 'cie:previous', | |
39 | asterix: 'cie:asterix', | |
40 | scrollTop: 'cie:scrollTop', | |
41 | scrollBottom: 'cie:scrollBottom', | |
42 | ctrlPageUp: 'cie:ctrlPageUp', | |
43 | ctrlPageDown: 'cie:ctrlPageDown', | |
44 | enter: 'cie:enter', | |
45 | }; | |
46 | ||
47 | /** | |
48 | * Check if the caller has asked for the given event type to be | |
49 | * registered. | |
50 | * | |
51 | * @method shouldAddEvent | |
52 | * @private | |
53 | * @param {string} eventType name of the event (see events above) | |
54 | * @param {array} include the list of events to be added | |
2bcef559 | 55 | * @return {bool} true if the event should be added, false otherwise. |
e845b96b RW |
56 | */ |
57 | var shouldAddEvent = function(eventType, include) { | |
58 | include = include || []; | |
59 | ||
60 | if (include.length && include.indexOf(eventType) !== -1) { | |
61 | return true; | |
62 | } | |
63 | ||
64 | return false; | |
65 | }; | |
66 | ||
67 | /** | |
68 | * Check if any of the modifier keys have been pressed on the event. | |
69 | * | |
70 | * @method isModifierPressed | |
71 | * @private | |
72 | * @param {event} e jQuery event | |
2bcef559 | 73 | * @return {bool} true if shift, meta (command on Mac), alt or ctrl are pressed |
e845b96b RW |
74 | */ |
75 | var isModifierPressed = function(e) { | |
76 | return (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey); | |
77 | }; | |
78 | ||
79 | /** | |
80 | * Register a keyboard event that ignores modifier keys. | |
81 | * | |
82 | * @method addKeyboardEvent | |
83 | * @private | |
2bcef559 | 84 | * @param {object} element A jQuery object of the element to bind events to |
e845b96b RW |
85 | * @param {string} event The custom interaction event name |
86 | * @param {int} keyCode The key code. | |
87 | */ | |
88 | var addKeyboardEvent = function(element, event, keyCode) { | |
89 | element.off('keydown.' + event).on('keydown.' + event, function(e) { | |
90 | if (!isModifierPressed(e)) { | |
91 | if (e.keyCode == keyCode) { | |
92 | $(e.target).trigger(event, [{originalEvent: e}]); | |
93 | } | |
94 | } | |
95 | }); | |
96 | }; | |
97 | ||
98 | /** | |
99 | * Trigger the activate event on the given element if it is clicked or the enter | |
100 | * or space key are pressed without a modifier key. | |
101 | * | |
102 | * @method addActivateListener | |
103 | * @private | |
2bcef559 | 104 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
105 | */ |
106 | var addActivateListener = function(element) { | |
107 | element.off('click.cie.activate').on('click.cie.activate', function(e) { | |
108 | $(e.target).trigger(events.activate, [{originalEvent: e}]); | |
109 | }); | |
110 | element.off('keydown.cie.activate').on('keydown.cie.activate', function(e) { | |
111 | if (!isModifierPressed(e)) { | |
112 | if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { | |
113 | $(e.target).trigger(events.activate, [{originalEvent: e}]); | |
114 | } | |
115 | } | |
116 | }); | |
117 | }; | |
118 | ||
119 | /** | |
120 | * Trigger the keyboard activate event on the given element if the enter | |
121 | * or space key are pressed without a modifier key. | |
122 | * | |
123 | * @method addKeyboardActivateListener | |
124 | * @private | |
2bcef559 | 125 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
126 | */ |
127 | var addKeyboardActivateListener = function(element) { | |
128 | element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) { | |
129 | if (!isModifierPressed(e)) { | |
130 | if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { | |
131 | $(e.target).trigger(events.keyboardActivate, [{originalEvent: e}]); | |
132 | } | |
133 | } | |
134 | }); | |
135 | }; | |
136 | ||
137 | /** | |
138 | * Trigger the escape event on the given element if the escape key is pressed | |
139 | * without a modifier key. | |
140 | * | |
141 | * @method addEscapeListener | |
142 | * @private | |
2bcef559 | 143 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
144 | */ |
145 | var addEscapeListener = function(element) { | |
146 | addKeyboardEvent(element, events.escape, keyCodes.escape); | |
147 | }; | |
148 | ||
149 | /** | |
150 | * Trigger the down event on the given element if the down arrow key is pressed | |
151 | * without a modifier key. | |
152 | * | |
153 | * @method addDownListener | |
154 | * @private | |
2bcef559 | 155 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
156 | */ |
157 | var addDownListener = function(element) { | |
158 | addKeyboardEvent(element, events.down, keyCodes.arrowDown); | |
159 | }; | |
160 | ||
161 | /** | |
162 | * Trigger the up event on the given element if the up arrow key is pressed | |
163 | * without a modifier key. | |
164 | * | |
165 | * @method addUpListener | |
166 | * @private | |
2bcef559 | 167 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
168 | */ |
169 | var addUpListener = function(element) { | |
170 | addKeyboardEvent(element, events.up, keyCodes.arrowUp); | |
171 | }; | |
172 | ||
173 | /** | |
174 | * Trigger the home event on the given element if the home key is pressed | |
175 | * without a modifier key. | |
176 | * | |
177 | * @method addHomeListener | |
178 | * @private | |
2bcef559 | 179 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
180 | */ |
181 | var addHomeListener = function(element) { | |
182 | addKeyboardEvent(element, events.home, keyCodes.home); | |
183 | }; | |
184 | ||
185 | /** | |
186 | * Trigger the end event on the given element if the end key is pressed | |
187 | * without a modifier key. | |
188 | * | |
189 | * @method addEndListener | |
190 | * @private | |
2bcef559 | 191 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
192 | */ |
193 | var addEndListener = function(element) { | |
194 | addKeyboardEvent(element, events.end, keyCodes.end); | |
195 | }; | |
196 | ||
197 | /** | |
198 | * Trigger the next event on the given element if the right arrow key is pressed | |
199 | * without a modifier key in LTR mode or left arrow key in RTL mode. | |
200 | * | |
201 | * @method addNextListener | |
202 | * @private | |
2bcef559 | 203 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
204 | */ |
205 | var addNextListener = function(element) { | |
206 | // Left and right are flipped in RTL mode. | |
207 | var keyCode = $('html').attr('dir') == "rtl" ? keyCodes.arrowLeft : keyCodes.arrowRight; | |
208 | ||
209 | addKeyboardEvent(element, events.next, keyCode); | |
210 | }; | |
211 | ||
212 | /** | |
213 | * Trigger the previous event on the given element if the left arrow key is pressed | |
214 | * without a modifier key in LTR mode or right arrow key in RTL mode. | |
215 | * | |
216 | * @method addPreviousListener | |
217 | * @private | |
2bcef559 | 218 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
219 | */ |
220 | var addPreviousListener = function(element) { | |
221 | // Left and right are flipped in RTL mode. | |
222 | var keyCode = $('html').attr('dir') == "rtl" ? keyCodes.arrowRight : keyCodes.arrowLeft; | |
223 | ||
224 | addKeyboardEvent(element, events.previous, keyCode); | |
225 | }; | |
226 | ||
227 | /** | |
228 | * Trigger the asterix event on the given element if the asterix key is pressed | |
229 | * without a modifier key. | |
230 | * | |
231 | * @method addAsterixListener | |
232 | * @private | |
2bcef559 | 233 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
234 | */ |
235 | var addAsterixListener = function(element) { | |
236 | addKeyboardEvent(element, events.asterix, keyCodes.asterix); | |
237 | }; | |
238 | ||
239 | ||
240 | /** | |
241 | * Trigger the scrollTop event on the given element if the user scrolls to | |
242 | * the top of the given element. | |
243 | * | |
244 | * @method addScrollTopListener | |
245 | * @private | |
2bcef559 | 246 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
247 | */ |
248 | var addScrollTopListener = function(element) { | |
249 | element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function() { | |
250 | var scrollTop = element.scrollTop(); | |
251 | if (scrollTop === 0) { | |
252 | element.trigger(events.scrollTop); | |
253 | } | |
254 | }); | |
255 | }; | |
256 | ||
257 | /** | |
258 | * Trigger the scrollBottom event on the given element if the user scrolls to | |
259 | * the bottom of the given element. | |
260 | * | |
261 | * @method addScrollBottomListener | |
262 | * @private | |
2bcef559 | 263 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
264 | */ |
265 | var addScrollBottomListener = function(element) { | |
266 | element.off('scroll.cie.scrollBottom').on('scroll.cie.scrollBottom', function() { | |
267 | var scrollTop = element.scrollTop(); | |
268 | var innerHeight = element.innerHeight(); | |
269 | var scrollHeight = element[0].scrollHeight; | |
270 | ||
271 | if (scrollTop + innerHeight >= scrollHeight) { | |
272 | element.trigger(events.scrollBottom); | |
273 | } | |
274 | }); | |
275 | }; | |
276 | ||
277 | /** | |
278 | * Trigger the ctrlPageUp event on the given element if the user presses the | |
279 | * control and page up key. | |
280 | * | |
281 | * @method addCtrlPageUpListener | |
282 | * @private | |
2bcef559 | 283 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
284 | */ |
285 | var addCtrlPageUpListener = function(element) { | |
286 | element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) { | |
287 | if (e.ctrlKey) { | |
288 | if (e.keyCode == keyCodes.pageUp) { | |
289 | $(e.target).trigger(events.ctrlPageUp, [{originalEvent: e}]); | |
290 | } | |
291 | } | |
292 | }); | |
293 | }; | |
294 | ||
295 | /** | |
296 | * Trigger the ctrlPageDown event on the given element if the user presses the | |
297 | * control and page down key. | |
298 | * | |
299 | * @method addCtrlPageDownListener | |
300 | * @private | |
2bcef559 | 301 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
302 | */ |
303 | var addCtrlPageDownListener = function(element) { | |
304 | element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) { | |
305 | if (e.ctrlKey) { | |
306 | if (e.keyCode == keyCodes.pageDown) { | |
307 | $(e.target).trigger(events.ctrlPageDown, [{originalEvent: e}]); | |
308 | } | |
309 | } | |
310 | }); | |
311 | }; | |
312 | ||
313 | /** | |
314 | * Trigger the enter event on the given element if the enter key is pressed | |
315 | * without a modifier key. | |
316 | * | |
317 | * @method addEnterListener | |
318 | * @private | |
2bcef559 | 319 | * @param {object} element jQuery object to add event listeners to |
e845b96b RW |
320 | */ |
321 | var addEnterListener = function(element) { | |
322 | addKeyboardEvent(element, events.enter, keyCodes.enter); | |
323 | }; | |
324 | ||
325 | /** | |
326 | * Get the list of events and their handlers. | |
327 | * | |
328 | * @method getHandlers | |
329 | * @private | |
2bcef559 | 330 | * @return {object} object key of event names and value of handler functions |
e845b96b RW |
331 | */ |
332 | var getHandlers = function() { | |
333 | var handlers = {}; | |
334 | ||
335 | handlers[events.activate] = addActivateListener; | |
336 | handlers[events.keyboardActivate] = addKeyboardActivateListener; | |
337 | handlers[events.escape] = addEscapeListener; | |
338 | handlers[events.down] = addDownListener; | |
339 | handlers[events.up] = addUpListener; | |
340 | handlers[events.home] = addHomeListener; | |
341 | handlers[events.end] = addEndListener; | |
342 | handlers[events.next] = addNextListener; | |
343 | handlers[events.previous] = addPreviousListener; | |
344 | handlers[events.asterix] = addAsterixListener; | |
345 | handlers[events.scrollTop] = addScrollTopListener; | |
346 | handlers[events.scrollBottom] = addScrollBottomListener; | |
347 | handlers[events.ctrlPageUp] = addCtrlPageUpListener; | |
348 | handlers[events.ctrlPageDown] = addCtrlPageDownListener; | |
349 | handlers[events.enter] = addEnterListener; | |
350 | ||
351 | return handlers; | |
352 | }; | |
353 | ||
354 | /** | |
355 | * Add all of the listeners on the given element for the requested events. | |
356 | * | |
357 | * @method define | |
358 | * @public | |
2bcef559 | 359 | * @param {object} element the DOM element to register event listeners on |
e845b96b RW |
360 | * @param {array} include the array of events to be triggered |
361 | */ | |
362 | var define = function(element, include) { | |
363 | element = $(element); | |
364 | include = include || []; | |
365 | ||
366 | if (!element.length || !include.length) { | |
367 | return; | |
368 | } | |
369 | ||
370 | $.each(getHandlers(), function(eventType, handler) { | |
371 | if (shouldAddEvent(eventType, include)) { | |
372 | handler(element); | |
373 | } | |
374 | }); | |
375 | }; | |
376 | ||
377 | return /** @module core/custom_interaction_events */ { | |
378 | define: define, | |
379 | events: events, | |
380 | }; | |
381 | }); |