1 | /******************************************* |
||
2 | * * |
||
3 | * This util was created by Marius Olbertz * |
||
4 | * Please thank Marius on GitHub /owlbertz * |
||
5 | * or the web http://www.mariusolbertz.de/ * |
||
6 | * * |
||
7 | ******************************************/ |
||
8 | |||
9 | 'use strict'; |
||
10 | |||
11 | import { rtl as Rtl } from './siteapp.util.core'; |
||
12 | |||
13 | const keyCodes = { |
||
14 | 9 : 'TAB', |
||
15 | 13: 'ENTER', |
||
16 | 27: 'ESCAPE', |
||
17 | 32: 'SPACE', |
||
18 | 35: 'END', |
||
19 | 36: 'HOME', |
||
20 | 37: 'ARROW_LEFT', |
||
21 | 38: 'ARROW_UP', |
||
22 | 39: 'ARROW_RIGHT', |
||
23 | 40: 'ARROW_DOWN' |
||
24 | } |
||
25 | |||
26 | let commands = {} |
||
27 | |||
28 | // Functions pulled out to be referenceable from internals |
||
29 | function findFocusable($element) { |
||
30 | if(!$element) {return false; } |
||
31 | return $element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function() { |
||
32 | if (!$(this).is(':visible') || $(this).attr('tabindex') < 0) { return false; } //only have visible elements and those that have a tabindex greater or equal 0 |
||
33 | return true; |
||
34 | }); |
||
35 | } |
||
36 | |||
37 | function parseKey(event) { |
||
38 | var key = keyCodes[event.which || event.keyCode] || String.fromCharCode(event.which).toUpperCase(); |
||
39 | |||
40 | // Remove un-printable characters, e.g. for `fromCharCode` calls for CTRL only events |
||
41 | key = key.replace(/\W+/, ''); |
||
42 | |||
43 | if (event.shiftKey) key = `SHIFT_${key}`; |
||
0 ignored issues
–
show
|
|||
44 | if (event.ctrlKey) key = `CTRL_${key}`; |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
45 | if (event.altKey) key = `ALT_${key}`; |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
46 | if (event.metaKey) key = `META_${key}`; |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
47 | |||
48 | // Remove trailing underscore, in case only modifiers were used (e.g. only `CTRL_ALT`) |
||
49 | key = key.replace(/_$/, ''); |
||
50 | |||
51 | return key; |
||
52 | } |
||
53 | |||
54 | const Keyboard = { |
||
55 | |||
56 | /** |
||
57 | * @var {object} keys - keycodes map |
||
58 | */ |
||
59 | keys: getKeyCodes(keyCodes), |
||
60 | |||
61 | /** |
||
62 | * Parses the (keyboard) event and returns a String that represents its key |
||
63 | * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE |
||
64 | * @param {Event} event - the event generated by the event handler |
||
65 | * @return String key - String that represents the key pressed |
||
66 | */ |
||
67 | parseKey: parseKey, |
||
68 | |||
69 | /** |
||
70 | * Handles the given (keyboard) event |
||
71 | * @param {Event} event - the event generated by the event handler |
||
72 | * @param {String} component - Foundation component's name, e.g. Slider or Reveal |
||
73 | * @param {Objects} functions - collection of functions that are to be executed |
||
74 | */ |
||
75 | handleKey(event, component, functions) { |
||
76 | var commandList = commands[component], |
||
77 | keyCode = this.parseKey(event), |
||
78 | cmds, |
||
79 | command, |
||
80 | fn; |
||
81 | |||
82 | if (!commandList) return console.warn('Component not defined!'); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
83 | |||
84 | if (typeof commandList.ltr === 'undefined') { // this component does not differentiate between ltr and rtl |
||
85 | cmds = commandList; // use plain list |
||
86 | } else { // merge ltr and rtl: if document is rtl, rtl overwrites ltr and vice versa |
||
87 | if (Rtl()) cmds = $.extend({}, commandList.ltr, commandList.rtl); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
88 | |||
89 | else cmds = $.extend({}, commandList.rtl, commandList.ltr); |
||
90 | } |
||
91 | command = cmds[keyCode]; |
||
92 | |||
93 | fn = functions[command]; |
||
94 | if (fn && typeof fn === 'function') { // execute function if exists |
||
95 | var returnValue = fn.apply(null, [event]); |
||
96 | if (functions.handled || typeof functions.handled === 'function') { // execute function when event was handled |
||
0 ignored issues
–
show
There is no return statement if
functions.handled || typ...handled === "function" is false . Are you sure this is correct? If so, consider adding return; explicitly.
This check looks for functions where a Consider this little piece of code function isBig(a) {
if (a > 5000) {
return "yes";
}
}
console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined
The function This behaviour may not be what you had intended. In any case, you can add a
![]() |
|||
97 | functions.handled(returnValue); |
||
0 ignored issues
–
show
|
|||
98 | } |
||
99 | } else { |
||
100 | if (functions.unhandled || typeof functions.unhandled === 'function') { // execute function when event was not handled |
||
0 ignored issues
–
show
There is no return statement if
functions.unhandled || t...handled === "function" is false . Are you sure this is correct? If so, consider adding return; explicitly.
This check looks for functions where a Consider this little piece of code function isBig(a) {
if (a > 5000) {
return "yes";
}
}
console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined
The function This behaviour may not be what you had intended. In any case, you can add a
![]() |
|||
101 | functions.unhandled(); |
||
0 ignored issues
–
show
|
|||
102 | } |
||
103 | } |
||
104 | }, |
||
105 | |||
106 | /** |
||
107 | * Finds all focusable elements within the given `$element` |
||
108 | * @param {jQuery} $element - jQuery object to search within |
||
109 | * @return {jQuery} $focusable - all focusable elements within `$element` |
||
110 | */ |
||
111 | |||
112 | findFocusable: findFocusable, |
||
113 | |||
114 | /** |
||
115 | * Stores set of commands mapped to handler names in a given namespace |
||
116 | * |
||
117 | * @param {string} namespace - keyboard event/command namespace |
||
118 | * @param {object} cmds - handler name to command map |
||
119 | */ |
||
120 | |||
121 | register (namespace, cmds) { |
||
122 | commands[namespace] = cmds; |
||
123 | //console.//log('registered keyboard component:', namespace, cmds, commands); |
||
124 | }, |
||
125 | |||
126 | |||
127 | // TODO9438: These references to Keyboard need to not require global. Will 'this' work in this context? |
||
128 | // |
||
129 | /** |
||
130 | * Traps the focus in the given element. |
||
131 | * @param {jQuery} $element jQuery object to trap the foucs into. |
||
132 | */ |
||
133 | trapFocus($element) { |
||
134 | var $focusable = findFocusable($element), |
||
135 | $firstFocusable = $focusable.eq(0), |
||
136 | $lastFocusable = $focusable.eq(-1); |
||
137 | |||
138 | $element.on('keydown.'+Keyboard._ns+'.trapfocus', function(event) { |
||
139 | if (event.target === $lastFocusable[0] && parseKey(event) === 'TAB') { |
||
140 | event.preventDefault(); |
||
141 | $firstFocusable.focus(); |
||
142 | } |
||
143 | else if (event.target === $firstFocusable[0] && parseKey(event) === 'SHIFT_TAB') { |
||
144 | event.preventDefault(); |
||
145 | $lastFocusable.focus(); |
||
146 | } |
||
147 | }); |
||
148 | }, |
||
149 | |||
150 | /** |
||
151 | * Releases the trapped focus from the given element. |
||
152 | * @param {jQuery} $element jQuery object to release the focus for. |
||
153 | */ |
||
154 | releaseFocus($element) { |
||
155 | $element.off('keydown.'+Keyboard._ns+'.trapfocus'); |
||
156 | }, |
||
157 | |||
158 | /** |
||
159 | * Removes keyboard event handlers. If no namespace is given, all |
||
160 | * keyboard handlers will be removed. |
||
161 | * |
||
162 | * @param {string} namespace - keyboard event namespace |
||
163 | */ |
||
164 | unregister( namespace ) { |
||
165 | if (namespace != '') { |
||
166 | $(window).off('keydown.'+this.application.appName+'.'+namespace); |
||
167 | if (typeof commands[namespace] != 'undefined') { |
||
168 | delete commands[namespace]; |
||
169 | } |
||
170 | } else { |
||
171 | $(window).off('keydown.'+this.application.appName); |
||
172 | commands = {}; |
||
173 | } |
||
174 | }, |
||
175 | |||
176 | /** |
||
177 | * Adds keyboard event handlers. |
||
178 | * |
||
179 | * handlers := { |
||
180 | * '[KEY(S)]' : function myAction (event e) {...}, |
||
181 | * ... |
||
182 | * } |
||
183 | * |
||
184 | * @param {object} handlers - key(s) to action map |
||
185 | * @param {string} namespace - keyboard event namespace |
||
186 | */ |
||
187 | addKeyHandlers( handlers, namespace ) { |
||
188 | |||
189 | var $this = this, |
||
190 | $app = this._app, |
||
0 ignored issues
–
show
|
|||
191 | eventTrigger = 'keydown.'+this._ns, |
||
192 | registeredKeys = {}, |
||
193 | registeredHandlers = {} |
||
194 | ; |
||
195 | |||
196 | if ('' == namespace) { |
||
197 | let namespace = '_'+Siteapp.sys.genUUID(6); |
||
0 ignored issues
–
show
The variable
Siteapp seems to be never declared. If this is a global, consider adding a /** global: Siteapp */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. ![]() |
|||
198 | } |
||
199 | eventTrigger = eventTrigger+'.'+namespace; |
||
200 | |||
201 | // build 'reg keys' and 'hdl keys' maps |
||
202 | for ( var key in handlers ) { |
||
203 | if (handlers.hasOwnProperty(key)) { |
||
204 | var handleName = namespace+''+key; |
||
205 | registeredKeys[key] = handleName; |
||
206 | registeredHandlers[handleName] = handlers[key]; |
||
207 | } |
||
208 | } |
||
209 | |||
210 | $(window).on(eventTrigger, function(e){ |
||
211 | |||
212 | this.id = namespace+''+Siteapp.sys.genUUID(6); |
||
0 ignored issues
–
show
The variable
Siteapp seems to be never declared. If this is a global, consider adding a /** global: Siteapp */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. ![]() |
|||
213 | |||
214 | // ignore [TAB] key |
||
215 | // if (e.which === 9) return; |
||
216 | |||
217 | // handle keyboard event with keyboard util |
||
218 | $this.handleKey(e, namespace, registeredHandlers); |
||
219 | |||
220 | |||
221 | }); |
||
222 | |||
223 | // register keyboard keys mapping |
||
224 | this.register(namespace, registeredKeys); |
||
225 | |||
226 | } |
||
227 | |||
228 | }; |
||
229 | |||
230 | /* |
||
231 | * Constants for easier comparing. |
||
232 | * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE |
||
233 | */ |
||
234 | function getKeyCodes(kcs) { |
||
235 | var k = {}; |
||
236 | for (var kc in kcs) k[kcs[kc]] = kcs[kc]; |
||
0 ignored issues
–
show
A for in loop automatically includes the property of any prototype object, consider checking the key using
hasOwnProperty .
When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically: var someObject;
for (var key in someObject) {
if ( ! someObject.hasOwnProperty(key)) {
continue; // Skip keys from the prototype.
}
doSomethingWith(key);
}
![]() Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
237 | return k; |
||
238 | } |
||
239 | |||
240 | export {Keyboard}; |
||
241 |
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.
Consider:
If you or someone else later decides to put another statement in, only the first statement will be executed.
In this case the statement
b = 42
will always be executed, while the logging statement will be executed conditionally.ensures that the proper code will be executed conditionally no matter how many statements are added or removed.