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 | |
55 | * @return bool true if the event should be added, false otherwise. | |
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 | |
73 | * @return bool true if shift, meta (command on Mac), alt or ctrl are pressed | |
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 | |
84 | * @param {string} event The custom interaction event name | |
85 | * @param {int} keyCode The key code. | |
86 | */ | |
87 | var addKeyboardEvent = function(element, event, keyCode) { | |
88 | element.off('keydown.' + event).on('keydown.' + event, function(e) { | |
89 | if (!isModifierPressed(e)) { | |
90 | if (e.keyCode == keyCode) { | |
91 | $(e.target).trigger(event, [{originalEvent: e}]); | |
92 | } | |
93 | } | |
94 | }); | |
95 | }; | |
96 | ||
97 | /** | |
98 | * Trigger the activate event on the given element if it is clicked or the enter | |
99 | * or space key are pressed without a modifier key. | |
100 | * | |
101 | * @method addActivateListener | |
102 | * @private | |
103 | * @param {jQuery object} element jQuery object to add event listeners to | |
104 | */ | |
105 | var addActivateListener = function(element) { | |
106 | element.off('click.cie.activate').on('click.cie.activate', function(e) { | |
107 | $(e.target).trigger(events.activate, [{originalEvent: e}]); | |
108 | }); | |
109 | element.off('keydown.cie.activate').on('keydown.cie.activate', function(e) { | |
110 | if (!isModifierPressed(e)) { | |
111 | if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { | |
112 | $(e.target).trigger(events.activate, [{originalEvent: e}]); | |
113 | } | |
114 | } | |
115 | }); | |
116 | }; | |
117 | ||
118 | /** | |
119 | * Trigger the keyboard activate event on the given element if the enter | |
120 | * or space key are pressed without a modifier key. | |
121 | * | |
122 | * @method addKeyboardActivateListener | |
123 | * @private | |
124 | * @param {jQuery object} element jQuery object to add event listeners to | |
125 | */ | |
126 | var addKeyboardActivateListener = function(element) { | |
127 | element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) { | |
128 | if (!isModifierPressed(e)) { | |
129 | if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { | |
130 | $(e.target).trigger(events.keyboardActivate, [{originalEvent: e}]); | |
131 | } | |
132 | } | |
133 | }); | |
134 | }; | |
135 | ||
136 | /** | |
137 | * Trigger the escape event on the given element if the escape key is pressed | |
138 | * without a modifier key. | |
139 | * | |
140 | * @method addEscapeListener | |
141 | * @private | |
142 | * @param {jQuery object} element jQuery object to add event listeners to | |
143 | */ | |
144 | var addEscapeListener = function(element) { | |
145 | addKeyboardEvent(element, events.escape, keyCodes.escape); | |
146 | }; | |
147 | ||
148 | /** | |
149 | * Trigger the down event on the given element if the down arrow key is pressed | |
150 | * without a modifier key. | |
151 | * | |
152 | * @method addDownListener | |
153 | * @private | |
154 | * @param {jQuery object} element jQuery object to add event listeners to | |
155 | */ | |
156 | var addDownListener = function(element) { | |
157 | addKeyboardEvent(element, events.down, keyCodes.arrowDown); | |
158 | }; | |
159 | ||
160 | /** | |
161 | * Trigger the up event on the given element if the up arrow key is pressed | |
162 | * without a modifier key. | |
163 | * | |
164 | * @method addUpListener | |
165 | * @private | |
166 | * @param {jQuery object} element jQuery object to add event listeners to | |
167 | */ | |
168 | var addUpListener = function(element) { | |
169 | addKeyboardEvent(element, events.up, keyCodes.arrowUp); | |
170 | }; | |
171 | ||
172 | /** | |
173 | * Trigger the home event on the given element if the home key is pressed | |
174 | * without a modifier key. | |
175 | * | |
176 | * @method addHomeListener | |
177 | * @private | |
178 | * @param {jQuery object} element jQuery object to add event listeners to | |
179 | */ | |
180 | var addHomeListener = function(element) { | |
181 | addKeyboardEvent(element, events.home, keyCodes.home); | |
182 | }; | |
183 | ||
184 | /** | |
185 | * Trigger the end event on the given element if the end key is pressed | |
186 | * without a modifier key. | |
187 | * | |
188 | * @method addEndListener | |
189 | * @private | |
190 | * @param {jQuery object} element jQuery object to add event listeners to | |
191 | */ | |
192 | var addEndListener = function(element) { | |
193 | addKeyboardEvent(element, events.end, keyCodes.end); | |
194 | }; | |
195 | ||
196 | /** | |
197 | * Trigger the next event on the given element if the right arrow key is pressed | |
198 | * without a modifier key in LTR mode or left arrow key in RTL mode. | |
199 | * | |
200 | * @method addNextListener | |
201 | * @private | |
202 | * @param {jQuery object} element jQuery object to add event listeners to | |
203 | */ | |
204 | var addNextListener = function(element) { | |
205 | // Left and right are flipped in RTL mode. | |
206 | var keyCode = $('html').attr('dir') == "rtl" ? keyCodes.arrowLeft : keyCodes.arrowRight; | |
207 | ||
208 | addKeyboardEvent(element, events.next, keyCode); | |
209 | }; | |
210 | ||
211 | /** | |
212 | * Trigger the previous event on the given element if the left arrow key is pressed | |
213 | * without a modifier key in LTR mode or right arrow key in RTL mode. | |
214 | * | |
215 | * @method addPreviousListener | |
216 | * @private | |
217 | * @param {jQuery object} element jQuery object to add event listeners to | |
218 | */ | |
219 | var addPreviousListener = function(element) { | |
220 | // Left and right are flipped in RTL mode. | |
221 | var keyCode = $('html').attr('dir') == "rtl" ? keyCodes.arrowRight : keyCodes.arrowLeft; | |
222 | ||
223 | addKeyboardEvent(element, events.previous, keyCode); | |
224 | }; | |
225 | ||
226 | /** | |
227 | * Trigger the asterix event on the given element if the asterix key is pressed | |
228 | * without a modifier key. | |
229 | * | |
230 | * @method addAsterixListener | |
231 | * @private | |
232 | * @param {jQuery object} element jQuery object to add event listeners to | |
233 | */ | |
234 | var addAsterixListener = function(element) { | |
235 | addKeyboardEvent(element, events.asterix, keyCodes.asterix); | |
236 | }; | |
237 | ||
238 | ||
239 | /** | |
240 | * Trigger the scrollTop event on the given element if the user scrolls to | |
241 | * the top of the given element. | |
242 | * | |
243 | * @method addScrollTopListener | |
244 | * @private | |
245 | * @param {jQuery object} element jQuery object to add event listeners to | |
246 | */ | |
247 | var addScrollTopListener = function(element) { | |
248 | element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function() { | |
249 | var scrollTop = element.scrollTop(); | |
250 | if (scrollTop === 0) { | |
251 | element.trigger(events.scrollTop); | |
252 | } | |
253 | }); | |
254 | }; | |
255 | ||
256 | /** | |
257 | * Trigger the scrollBottom event on the given element if the user scrolls to | |
258 | * the bottom of the given element. | |
259 | * | |
260 | * @method addScrollBottomListener | |
261 | * @private | |
262 | * @param {jQuery object} element jQuery object to add event listeners to | |
263 | */ | |
264 | var addScrollBottomListener = function(element) { | |
265 | element.off('scroll.cie.scrollBottom').on('scroll.cie.scrollBottom', function() { | |
266 | var scrollTop = element.scrollTop(); | |
267 | var innerHeight = element.innerHeight(); | |
268 | var scrollHeight = element[0].scrollHeight; | |
269 | ||
270 | if (scrollTop + innerHeight >= scrollHeight) { | |
271 | element.trigger(events.scrollBottom); | |
272 | } | |
273 | }); | |
274 | }; | |
275 | ||
276 | /** | |
277 | * Trigger the ctrlPageUp event on the given element if the user presses the | |
278 | * control and page up key. | |
279 | * | |
280 | * @method addCtrlPageUpListener | |
281 | * @private | |
282 | * @param {jQuery object} element jQuery object to add event listeners to | |
283 | */ | |
284 | var addCtrlPageUpListener = function(element) { | |
285 | element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) { | |
286 | if (e.ctrlKey) { | |
287 | if (e.keyCode == keyCodes.pageUp) { | |
288 | $(e.target).trigger(events.ctrlPageUp, [{originalEvent: e}]); | |
289 | } | |
290 | } | |
291 | }); | |
292 | }; | |
293 | ||
294 | /** | |
295 | * Trigger the ctrlPageDown event on the given element if the user presses the | |
296 | * control and page down key. | |
297 | * | |
298 | * @method addCtrlPageDownListener | |
299 | * @private | |
300 | * @param {jQuery object} element jQuery object to add event listeners to | |
301 | */ | |
302 | var addCtrlPageDownListener = function(element) { | |
303 | element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) { | |
304 | if (e.ctrlKey) { | |
305 | if (e.keyCode == keyCodes.pageDown) { | |
306 | $(e.target).trigger(events.ctrlPageDown, [{originalEvent: e}]); | |
307 | } | |
308 | } | |
309 | }); | |
310 | }; | |
311 | ||
312 | /** | |
313 | * Trigger the enter event on the given element if the enter key is pressed | |
314 | * without a modifier key. | |
315 | * | |
316 | * @method addEnterListener | |
317 | * @private | |
318 | * @param {jQuery object} element jQuery object to add event listeners to | |
319 | */ | |
320 | var addEnterListener = function(element) { | |
321 | addKeyboardEvent(element, events.enter, keyCodes.enter); | |
322 | }; | |
323 | ||
324 | /** | |
325 | * Get the list of events and their handlers. | |
326 | * | |
327 | * @method getHandlers | |
328 | * @private | |
329 | * @return {jQuery object} object key of event names and value of handler functions | |
330 | */ | |
331 | var getHandlers = function() { | |
332 | var handlers = {}; | |
333 | ||
334 | handlers[events.activate] = addActivateListener; | |
335 | handlers[events.keyboardActivate] = addKeyboardActivateListener; | |
336 | handlers[events.escape] = addEscapeListener; | |
337 | handlers[events.down] = addDownListener; | |
338 | handlers[events.up] = addUpListener; | |
339 | handlers[events.home] = addHomeListener; | |
340 | handlers[events.end] = addEndListener; | |
341 | handlers[events.next] = addNextListener; | |
342 | handlers[events.previous] = addPreviousListener; | |
343 | handlers[events.asterix] = addAsterixListener; | |
344 | handlers[events.scrollTop] = addScrollTopListener; | |
345 | handlers[events.scrollBottom] = addScrollBottomListener; | |
346 | handlers[events.ctrlPageUp] = addCtrlPageUpListener; | |
347 | handlers[events.ctrlPageDown] = addCtrlPageDownListener; | |
348 | handlers[events.enter] = addEnterListener; | |
349 | ||
350 | return handlers; | |
351 | }; | |
352 | ||
353 | /** | |
354 | * Add all of the listeners on the given element for the requested events. | |
355 | * | |
356 | * @method define | |
357 | * @public | |
358 | * @param {jQuery object} element the DOM element to register event listeners on | |
359 | * @param {array} include the array of events to be triggered | |
360 | */ | |
361 | var define = function(element, include) { | |
362 | element = $(element); | |
363 | include = include || []; | |
364 | ||
365 | if (!element.length || !include.length) { | |
366 | return; | |
367 | } | |
368 | ||
369 | $.each(getHandlers(), function(eventType, handler) { | |
370 | if (shouldAddEvent(eventType, include)) { | |
371 | handler(element); | |
372 | } | |
373 | }); | |
374 | }; | |
375 | ||
376 | return /** @module core/custom_interaction_events */ { | |
377 | define: define, | |
378 | events: events, | |
379 | }; | |
380 | }); |