MDL-55727 javascript: add custom interaction event module
[moodle.git] / lib / amd / src / custom_interaction_events.js
CommitLineData
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 */
27define(['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});