keyboardjs-rails 0.2.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9aa6f3d3905e55159d80e25b33798dc3fd9c817
4
+ data.tar.gz: 49a95410dbaacec8a20d3251a6890e4b05e19c8d
5
+ SHA512:
6
+ metadata.gz: 4a9c678da87fdfc14ec1cc4fc8c1073c860a9b65b036e2b2efe397dceeba413991cda3d4c85eb336575bdff5122a93ca367c2b911659761b0f9f184b89101ad4
7
+ data.tar.gz: 8bf8a4969fcd0444ec6e7912c5e5f4d91c2303004a22be7a19710bc3be92c1d53426f1ce625f649ce6ce6df3fa253503377ae360541821e2a819fe4414dc340f
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ keyboardjs-rails
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3
data/README.md CHANGED
@@ -51,6 +51,14 @@ You're done!
51
51
  5. Create new Pull Request
52
52
 
53
53
 
54
+ ## Contributors
55
+
56
+ Many thanks go to the following who have contributed to making this cookbook even better:
57
+
58
+ * **[@kmmndr](https://github.com/kmmndr)**
59
+ * upgrade keyboardjs to v0.4.1
60
+
61
+
54
62
  ## License
55
63
 
56
64
  **keyboardjs-rails**
@@ -16,6 +16,6 @@ Gem::Specification.new do |gem|
16
16
  gem.version = KeyboardJS::Rails::VERSION
17
17
 
18
18
  gem.add_dependency "railties", ">= 3.0.0"
19
- gem.add_development_dependency "bundler", "~> 1.1.3"
20
- gem.add_development_dependency "rake", "~> 0.9.2.2"
19
+ gem.add_development_dependency "bundler"
20
+ gem.add_development_dependency "rake"
21
21
  end
@@ -1,6 +1,6 @@
1
1
  module KeyboardJS
2
2
  module Rails
3
- VERSION = "0.2.2"
4
- KEYBOARDJS_VERSION = "0.2.2"
3
+ VERSION = "0.4.1"
4
+ KEYBOARDJS_VERSION = "0.4.1"
5
5
  end
6
6
  end
@@ -1,483 +1,926 @@
1
- /*!
2
- * KeyboardJS
3
- *
1
+ /**
2
+ * Title: KeyboardJS
3
+ * Version: v0.4.1
4
+ * Description: KeyboardJS is a flexible and easy to use keyboard binding
5
+ * library.
6
+ * Author: Robert Hurst.
7
+ *
4
8
  * Copyright 2011, Robert William Hurst
5
9
  * Licenced under the BSD License.
6
10
  * See https://raw.github.com/RobertWHurst/KeyboardJS/master/license.txt
7
11
  */
8
- (function (root, factory) {
9
- if (typeof define === 'function' && define.amd) {
10
- // AMD. Register as an anonymous module.
11
- define(factory);
12
- } else {
13
- // Browser globals
14
- root.KeyboardJS = factory();
15
- }
16
- }(this, function() {
17
-
18
- //polyfills for ms's peice o' shit browsers
19
- function bind(target, type, handler) { if (target.addEventListener) { target.addEventListener(type, handler, false); } else { target.attachEvent("on" + type, function(event) { return handler.call(target, event); });Â } }
12
+ (function(context, factory) {
13
+
14
+ //INDEXOF POLLYFILL
20
15
  [].indexOf||(Array.prototype.indexOf=function(a,b,c){for(c=this.length,b=(c+~~b)%c;b<c&&(!(b in this)||this[b]!==a);b++);return b^c?b:-1;});
21
16
 
22
- //locals
23
- var locals = {
24
- 'us': {
25
- "backspace": 8,
26
- "tab": 9,
27
- "enter": 13,
28
- "shift": 16,
29
- "ctrl": 17,
30
- "alt": 18,
31
- "pause": 19, "break": 19,
32
- "capslock": 20,
33
- "escape": 27, "esc": 27,
34
- "space": 32, "spacebar": 32,
35
- "pageup": 33,
36
- "pagedown": 34,
37
- "end": 35,
38
- "home": 36,
39
- "left": 37,
40
- "up": 38,
41
- "right": 39,
42
- "down": 40,
43
- "insert": 45,
44
- "delete": 46,
45
- "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 57,
46
- "a": 65, "b": 66, "c": 67, "d": 68, "e": 69, "f": 70, "g": 71, "h": 72, "i": 73, "j": 74, "k": 75, "l": 76, "m": 77, "n": 78, "o": 79, "p": 80, "q": 81, "r": 82, "s": 83, "t": 84, "u": 85, "v": 86, "w": 87, "x": 88, "y": 89, "z": 90,
47
- "meta": 91, "command": 91, "windows": 91, "win": 91,
48
- "_91": 92,
49
- "select": 93,
50
- "num0": 96, "num1": 97, "num2": 98, "num3": 99, "num4": 100, "num5": 101, "num6": 102, "num7": 103, "num8": 104, "num9": 105,
51
- "multiply": 106,
52
- "add": 107,
53
- "subtract": 109,
54
- "decimal": 110,
55
- "divide": 111,
56
- "f1": 112, "f2": 113, "f3": 114, "f4": 115, "f5": 116, "f6": 117, "f7": 118, "f8": 119, "f9": 120, "f10": 121, "f11": 122, "f12": 123,
57
- "numlock": 144, "num": 144,
58
- "scrolllock": 145, "scroll": 145,
59
- "semicolon": 186,
60
- "equal": 187, "equalsign": 187,
61
- "comma": 188,
62
- "dash": 189,
63
- "period": 190,
64
- "slash": 191, "forwardslash": 191,
65
- "graveaccent": 192,
66
- "openbracket": 219,
67
- "backslash": 220,
68
- "closebracket": 221,
69
- "singlequote": 222
70
- }
17
+ //AMD
18
+ if(typeof define === 'function' && define.amd) { define(constructAMD); }
71
19
 
72
- //If you create a new local please submit it as a pull request or post it in the issue tracker at
73
- // http://github.com/RobertWhurst/KeyboardJS/issues/
74
- }
20
+ //GLOBAL
21
+ else { constructGlobal(); }
75
22
 
76
- //keys
77
- var keys = locals['us'],
78
- activeKeys = [],
79
- activeBindings = {},
80
- keyBindingGroups = [];
23
+ /**
24
+ * Construct AMD version of the library
25
+ */
26
+ function constructAMD() {
81
27
 
82
- //adds keys to the active keys array
83
- bind(document, "keydown", function(event) {
28
+ //create a library instance
29
+ return init();
84
30
 
85
- //lookup the key pressed and save it to the active keys array
86
- for (var key in keys) {
87
- if(keys.hasOwnProperty(key) && event.keyCode === keys[key]) {
88
- if(activeKeys.indexOf(key) < 0) {
89
- activeKeys.push(key);
90
- }
91
- }
31
+ //spawns a library instance
32
+ function init() {
33
+ var library;
34
+ library = factory('amd');
35
+ library.fork = init;
36
+ return library;
92
37
  }
38
+ }
39
+
40
+ /**
41
+ * Construct a Global version of the library
42
+ */
43
+ function constructGlobal() {
44
+ var library;
93
45
 
94
- //execute the first callback the longest key binding that matches the active keys
95
- return executeActiveKeyBindings(event);
46
+ //create a library instance
47
+ library = init();
48
+ library.noConflict('KeyboardJS', 'k');
96
49
 
97
- });
50
+ //spawns a library instance
51
+ function init() {
52
+ var library, namespaces = [], previousValues = {};
98
53
 
99
- //removes keys from the active array
100
- bind(document, "keyup", function (event) {
54
+ library = factory('global');
55
+ library.fork = init;
56
+ library.noConflict = noConflict;
57
+ return library;
101
58
 
102
- //lookup the key released and prune it from the active keys array
103
- for(var key in keys) {
104
- if(keys.hasOwnProperty(key) && event.keyCode === keys[key]) {
59
+ //sets library namespaces
60
+ function noConflict( ) {
61
+ var args, nI, newNamespaces;
105
62
 
106
- var iAK = activeKeys.indexOf(key);
63
+ newNamespaces = Array.prototype.slice.apply(arguments);
107
64
 
108
- if(iAK > -1) {
109
- activeKeys.splice(iAK, 1);
65
+ for(nI = 0; nI < namespaces.length; nI += 1) {
66
+ if(typeof previousValues[namespaces[nI]] === 'undefined') {
67
+ delete context[namespaces[nI]];
68
+ } else {
69
+ context[namespaces[nI]] = previousValues[namespaces[nI]];
70
+ }
110
71
  }
72
+
73
+ previousValues = {};
74
+
75
+ for(nI = 0; nI < newNamespaces.length; nI += 1) {
76
+ if(typeof newNamespaces[nI] !== 'string') {
77
+ throw new Error('Cannot replace namespaces. All new namespaces must be strings.');
78
+ }
79
+ previousValues[newNamespaces[nI]] = context[newNamespaces[nI]];
80
+ context[newNamespaces[nI]] = library;
81
+ }
82
+
83
+ namespaces = newNamespaces;
84
+
85
+ return namespaces;
111
86
  }
112
87
  }
88
+ }
89
+ })(this, function(env) {
90
+ var KeyboardJS = {}, locales = {}, locale, map, macros, activeKeys = [], bindings = [], activeBindings = [],
91
+ activeMacros = [], aI, usLocale;
92
+
93
+
94
+ ///////////////////////
95
+ // DEFUALT US LOCALE //
96
+ ///////////////////////
97
+
98
+ //define US locale
99
+ //If you create a new locale please submit it as a pull request or post
100
+ // it in the issue tracker at
101
+ // http://github.com/RobertWhurst/KeyboardJS/issues/
102
+ usLocale = {
103
+ "map": {
104
+
105
+ //general
106
+ "3": ["cancel"],
107
+ "8": ["backspace"],
108
+ "9": ["tab"],
109
+ "12": ["clear"],
110
+ "13": ["enter"],
111
+ "16": ["shift"],
112
+ "17": ["ctrl"],
113
+ "18": ["alt", "menu"],
114
+ "19": ["pause", "break"],
115
+ "20": ["capslock"],
116
+ "27": ["escape", "esc"],
117
+ "32": ["space", "spacebar"],
118
+ "33": ["pageup"],
119
+ "34": ["pagedown"],
120
+ "35": ["end"],
121
+ "36": ["home"],
122
+ "37": ["left"],
123
+ "38": ["up"],
124
+ "39": ["right"],
125
+ "40": ["down"],
126
+ "41": ["select"],
127
+ "42": ["printscreen"],
128
+ "43": ["execute"],
129
+ "44": ["snapshot"],
130
+ "45": ["insert", "ins"],
131
+ "46": ["delete", "del"],
132
+ "47": ["help"],
133
+ "91": ["command", "windows", "win", "super", "leftcommand", "leftwindows", "leftwin", "leftsuper"],
134
+ "92": ["command", "windows", "win", "super", "rightcommand", "rightwindows", "rightwin", "rightsuper"],
135
+ "145": ["scrolllock", "scroll"],
136
+ "186": ["semicolon", ";"],
137
+ "187": ["equal", "equalsign", "="],
138
+ "188": ["comma", ","],
139
+ "189": ["dash", "-"],
140
+ "190": ["period", "."],
141
+ "191": ["slash", "forwardslash", "/"],
142
+ "192": ["graveaccent", "`"],
143
+ "219": ["openbracket", "["],
144
+ "220": ["backslash", "\\"],
145
+ "221": ["closebracket", "]"],
146
+ "222": ["apostrophe", "'"],
147
+
148
+ //0-9
149
+ "48": ["zero", "0"],
150
+ "49": ["one", "1"],
151
+ "50": ["two", "2"],
152
+ "51": ["three", "3"],
153
+ "52": ["four", "4"],
154
+ "53": ["five", "5"],
155
+ "54": ["six", "6"],
156
+ "55": ["seven", "7"],
157
+ "56": ["eight", "8"],
158
+ "57": ["nine", "9"],
159
+
160
+ //numpad
161
+ "96": ["numzero", "num0"],
162
+ "97": ["numone", "num1"],
163
+ "98": ["numtwo", "num2"],
164
+ "99": ["numthree", "num3"],
165
+ "100": ["numfour", "num4"],
166
+ "101": ["numfive", "num5"],
167
+ "102": ["numsix", "num6"],
168
+ "103": ["numseven", "num7"],
169
+ "104": ["numeight", "num8"],
170
+ "105": ["numnine", "num9"],
171
+ "106": ["nummultiply", "num*"],
172
+ "107": ["numadd", "num+"],
173
+ "108": ["numenter"],
174
+ "109": ["numsubtract", "num-"],
175
+ "110": ["numdecimal", "num."],
176
+ "111": ["numdevide", "num/"],
177
+ "144": ["numlock", "num"],
178
+
179
+ //function keys
180
+ "112": ["f1"],
181
+ "113": ["f2"],
182
+ "114": ["f3"],
183
+ "115": ["f4"],
184
+ "116": ["f5"],
185
+ "117": ["f6"],
186
+ "118": ["f7"],
187
+ "119": ["f8"],
188
+ "120": ["f9"],
189
+ "121": ["f10"],
190
+ "122": ["f11"],
191
+ "123": ["f12"]
192
+ },
193
+ "macros": [
194
+
195
+ //secondary key symbols
196
+ ['shift + `', ["tilde", "~"]],
197
+ ['shift + 1', ["exclamation", "exclamationpoint", "!"]],
198
+ ['shift + 2', ["at", "@"]],
199
+ ['shift + 3', ["number", "#"]],
200
+ ['shift + 4', ["dollar", "dollars", "dollarsign", "$"]],
201
+ ['shift + 5', ["percent", "%"]],
202
+ ['shift + 6', ["caret", "^"]],
203
+ ['shift + 7', ["ampersand", "and", "&"]],
204
+ ['shift + 8', ["asterisk", "*"]],
205
+ ['shift + 9', ["openparen", "("]],
206
+ ['shift + 0', ["closeparen", ")"]],
207
+ ['shift + -', ["underscore", "_"]],
208
+ ['shift + =', ["plus", "+"]],
209
+ ['shift + (', ["opencurlybrace", "opencurlybracket", "{"]],
210
+ ['shift + )', ["closecurlybrace", "closecurlybracket", "}"]],
211
+ ['shift + \\', ["verticalbar", "|"]],
212
+ ['shift + ;', ["colon", ":"]],
213
+ ['shift + \'', ["quotationmark", "\""]],
214
+ ['shift + !,', ["openanglebracket", "<"]],
215
+ ['shift + .', ["closeanglebracket", ">"]],
216
+ ['shift + /', ["questionmark", "?"]]
217
+ ]
218
+ };
219
+ //a-z and A-Z
220
+ for (aI = 65; aI <= 90; aI += 1) {
221
+ usLocale.map[aI] = String.fromCharCode(aI + 32);
222
+ usLocale.macros.push(['shift + ' + String.fromCharCode(aI + 32) + ', capslock + ' + String.fromCharCode(aI + 32), [String.fromCharCode(aI)]]);
223
+ }
224
+ registerLocale('us', usLocale);
225
+ getSetLocale('us');
226
+
227
+
228
+ //////////
229
+ // INIT //
230
+ //////////
231
+
232
+ //enable the library
233
+ enable();
234
+
235
+
236
+ /////////
237
+ // API //
238
+ /////////
239
+
240
+ //assemble the library and return it
241
+ KeyboardJS.enable = enable;
242
+ KeyboardJS.disable = disable;
243
+ KeyboardJS.activeKeys = getActiveKeys;
244
+ KeyboardJS.on = createBinding;
245
+ KeyboardJS.clear = removeBindingByKeyCombo;
246
+ KeyboardJS.clear.key = removeBindingByKeyName;
247
+ KeyboardJS.locale = getSetLocale;
248
+ KeyboardJS.locale.register = registerLocale;
249
+ KeyboardJS.macro = createMacro;
250
+ KeyboardJS.macro.remove = removeMacro;
251
+ KeyboardJS.key = {};
252
+ KeyboardJS.key.name = getKeyName;
253
+ KeyboardJS.key.code = getKeyCode;
254
+ KeyboardJS.combo = {};
255
+ KeyboardJS.combo.active = isSatisfiedCombo;
256
+ KeyboardJS.combo.parse = parseKeyCombo;
257
+ KeyboardJS.combo.stringify = stringifyKeyCombo;
258
+ return KeyboardJS;
259
+
260
+
261
+ //////////////////////
262
+ // INSTANCE METHODS //
263
+ //////////////////////
113
264
 
114
- //execute the end callback on the active key binding
115
- return pruneActiveKeyBindings(event);
265
+ /**
266
+ * Enables KeyboardJS
267
+ */
268
+ function enable() {
269
+ if(window.addEventListener) {
270
+ document.addEventListener('keydown', keydown, false);
271
+ document.addEventListener('keyup', keyup, false);
272
+ window.addEventListener('blur', reset, false);
273
+ } else if(window.attachEvent) {
274
+ document.attachEvent('onkeydown', keydown);
275
+ document.attachEvent('onkeyup', keyup);
276
+ window.attachEvent('onblur', reset);
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Exits all active bindings and disables KeyboardJS
282
+ */
283
+ function disable() {
284
+ reset();
285
+ if(window.removeEventListener) {
286
+ document.removeEventListener('keydown', keydown, false);
287
+ document.removeEventListener('keyup', keyup, false);
288
+ window.removeEventListener('blur', reset, false);
289
+ } else if(window.detachEvent) {
290
+ document.detachEvent('onkeydown', keydown);
291
+ document.detachEvent('onkeyup', keyup);
292
+ window.detachEvent('onblur', reset);
293
+ }
294
+ }
295
+
296
+
297
+ ////////////////////
298
+ // EVENT HANDLERS //
299
+ ////////////////////
300
+
301
+ /**
302
+ * Exits all active bindings. Optionally passes an event to all binding
303
+ * handlers.
304
+ * @param {KeyboardEvent} event [Optional]
305
+ */
306
+ function reset(event) {
307
+ activeKeys = [];
308
+ pruneMacros();
309
+ pruneBindings(event);
310
+ }
116
311
 
117
- });
312
+ /**
313
+ * Key down event handler.
314
+ * @param {KeyboardEvent} event
315
+ */
316
+ function keydown(event) {
317
+ var keyNames, kI;
318
+ keyNames = getKeyName(event.keyCode);
319
+ if(keyNames.length < 1) { return; }
320
+ for(kI = 0; kI < keyNames.length; kI += 1) {
321
+ addActiveKey(keyNames[kI]);
322
+ }
323
+ executeMacros();
324
+ executeBindings(event);
325
+ }
326
+
327
+ /**
328
+ * Key up event handler.
329
+ * @param {KeyboardEvent} event
330
+ */
331
+ function keyup(event) {
332
+ var keyNames, kI;
333
+ keyNames = getKeyName(event.keyCode);
334
+ if(keyNames.length < 1) { return; }
335
+ for(kI = 0; kI < keyNames.length; kI += 1) {
336
+ removeActiveKey(keyNames[kI]);
337
+ }
338
+ pruneMacros();
339
+ pruneBindings(event);
340
+ }
118
341
 
119
342
  /**
120
- * Generates an array of active key bindings
343
+ * Accepts a key code and returns the key names defined by the current
344
+ * locale.
345
+ * @param {Number} keyCode
346
+ * @return {Array} keyNames An array of key names defined for the key
347
+ * code as defined by the current locale.
121
348
  */
122
- function queryActiveBindings() {
123
- var bindingStack = [];
349
+ function getKeyName(keyCode) {
350
+ return map[keyCode] || [];
351
+ }
124
352
 
125
- //loop through the key binding groups by number of keys.
126
- for(var keyCount = keyBindingGroups.length; keyCount > -1; keyCount -= 1) {
127
- if(keyBindingGroups[keyCount]) {
128
- var KeyBindingGroup = keyBindingGroups[keyCount];
353
+ /**
354
+ * Accepts a key name and returns the key code defined by the current
355
+ * locale.
356
+ * @param {Number} keyName
357
+ * @return {Number|false}
358
+ */
359
+ function getKeyCode(keyName) {
360
+ var keyCode;
361
+ for(keyCode in map) {
362
+ if(!map.hasOwnProperty(keyCode)) { continue; }
363
+ if(map[keyCode].indexOf(keyName) > -1) { return keyCode; }
364
+ }
365
+ return false;
366
+ }
129
367
 
130
- //loop through the key bindings of the same key length.
131
- for(var bindingIndex = 0; bindingIndex < KeyBindingGroup.length; bindingIndex += 1) {
132
- var binding = KeyBindingGroup[bindingIndex],
133
368
 
134
- //assume the binding is active till a required key is found to be unsatisfied
135
- keyBindingActive = true;
369
+ ////////////
370
+ // MACROS //
371
+ ////////////
136
372
 
137
- //loop through each key required by the binding.
138
- for(var keyIndex = 0; keyIndex < binding.keys.length; keyIndex += 1) {
139
- var key = binding.keys[keyIndex];
373
+ /**
374
+ * Accepts a key combo and an array of key names to inject once the key
375
+ * combo is satisfied.
376
+ * @param {String} combo
377
+ * @param {Array} injectedKeys
378
+ */
379
+ function createMacro(combo, injectedKeys) {
380
+ if(typeof combo !== 'string' && (typeof combo !== 'object' || typeof combo.push !== 'function')) {
381
+ throw new Error("Cannot create macro. The combo must be a string or array.");
382
+ }
383
+ if(typeof injectedKeys !== 'object' || typeof injectedKeys.push !== 'function') {
384
+ throw new Error("Cannot create macro. The injectedKeys must be an array.");
385
+ }
386
+ macros.push([combo, injectedKeys]);
387
+ }
140
388
 
141
- //if the current key is not in the active keys array the mark the binding as inactive
142
- if(activeKeys.indexOf(key) < 0) {
143
- keyBindingActive = false;
144
- }
145
- }
389
+ /**
390
+ * Accepts a key combo and clears any and all macros bound to that key
391
+ * combo.
392
+ * @param {String} combo
393
+ */
394
+ function removeMacro(combo) {
395
+ var macro;
396
+ if(typeof combo !== 'string' && (typeof combo !== 'object' || typeof combo.push !== 'function')) { throw new Error("Cannot remove macro. The combo must be a string or array."); }
397
+ for(mI = 0; mI < macros.length; mI += 1) {
398
+ macro = macros[mI];
399
+ if(compareCombos(combo, macro[0])) {
400
+ removeActiveKey(macro[1]);
401
+ macros.splice(mI, 1);
402
+ break;
403
+ }
404
+ }
405
+ }
146
406
 
147
- //if the key combo is still active then push it into the binding stack
148
- if(keyBindingActive) {
149
- bindingStack.push(binding);
150
- }
407
+ /**
408
+ * Executes macros against the active keys. Each macro's key combo is
409
+ * checked and if found to be satisfied, the macro's key names are injected
410
+ * into active keys.
411
+ */
412
+ function executeMacros() {
413
+ var mI, combo, kI;
414
+ for(mI = 0; mI < macros.length; mI += 1) {
415
+ combo = parseKeyCombo(macros[mI][0]);
416
+ if(activeMacros.indexOf(macros[mI]) === -1 && isSatisfiedCombo(combo)) {
417
+ activeMacros.push(macros[mI]);
418
+ for(kI = 0; kI < macros[mI][1].length; kI += 1) {
419
+ addActiveKey(macros[mI][1][kI]);
151
420
  }
152
421
  }
153
422
  }
423
+ }
154
424
 
155
- return bindingStack;
425
+ /**
426
+ * Prunes active macros. Checks each active macro's key combo and if found
427
+ * to no longer to be satisfied, each of the macro's key names are removed
428
+ * from active keys.
429
+ */
430
+ function pruneMacros() {
431
+ var mI, combo, kI;
432
+ for(mI = 0; mI < activeMacros.length; mI += 1) {
433
+ combo = parseKeyCombo(activeMacros[mI][0]);
434
+ if(isSatisfiedCombo(combo) === false) {
435
+ for(kI = 0; kI < activeMacros[mI][1].length; kI += 1) {
436
+ removeActiveKey(activeMacros[mI][1][kI]);
437
+ }
438
+ activeMacros.splice(mI, 1);
439
+ mI -= 1;
440
+ }
441
+ }
156
442
  }
157
443
 
444
+
445
+ //////////////
446
+ // BINDINGS //
447
+ //////////////
448
+
158
449
  /**
159
- * Collects active keys, sets active binds and fires on key down callbacks
160
- * @param event
450
+ * Creates a binding object, and, if provided, binds a key down hander and
451
+ * a key up handler. Returns a binding object that emits keyup and
452
+ * keydown events.
453
+ * @param {String} keyCombo
454
+ * @param {Function} keyDownCallback [Optional]
455
+ * @param {Function} keyUpCallback [Optional]
456
+ * @return {Object} binding
161
457
  */
162
- function executeActiveKeyBindings(event) {
458
+ function createBinding(keyCombo, keyDownCallback, keyUpCallback) {
459
+ var api = {}, binding, subBindings = [], bindingApi = {}, kI,
460
+ subCombo;
163
461
 
164
- if(activeKeys < 1) {
165
- return true;
462
+ //break the combo down into a combo array
463
+ if(typeof keyCombo === 'string') {
464
+ keyCombo = parseKeyCombo(keyCombo);
166
465
  }
167
466
 
168
- var bindingStack = queryActiveBindings(),
169
- spentKeys = [],
170
- output;
171
-
172
- //loop through each active binding
173
- for (var bindingIndex = 0; bindingIndex < bindingStack.length; bindingIndex += 1) {
174
- var binding = bindingStack[bindingIndex],
175
- usesSpentKey = false;
176
-
177
- //check each of the required keys. Make sure they have not been used by another binding
178
- for(var keyIndex = 0; keyIndex < binding.keys.length; keyIndex += 1) {
179
- var key = binding.keys[keyIndex];
180
- if(spentKeys.indexOf(key) > -1) {
181
- usesSpentKey = true;
182
- break;
183
- }
184
- }
467
+ //bind each sub combo contained within the combo string
468
+ for(kI = 0; kI < keyCombo.length; kI += 1) {
469
+ binding = {};
185
470
 
186
- //if the binding does not use a key that has been spent then execute it
187
- if(!usesSpentKey) {
471
+ //stringify the combo again
472
+ subCombo = stringifyKeyCombo([keyCombo[kI]]);
188
473
 
189
- //fire the callback
190
- if(typeof binding.callback === "function") {
191
- if(!binding.callback(event, binding.keys, binding.keyCombo)) {
192
- output = false
193
- }
194
- }
474
+ //validate the sub combo
475
+ if(typeof subCombo !== 'string') { throw new Error('Failed to bind key combo. The key combo must be string.'); }
476
+
477
+ //create the binding
478
+ binding.keyCombo = subCombo;
479
+ binding.keyDownCallback = [];
480
+ binding.keyUpCallback = [];
481
+
482
+ //inject the key down and key up callbacks if given
483
+ if(keyDownCallback) { binding.keyDownCallback.push(keyDownCallback); }
484
+ if(keyUpCallback) { binding.keyUpCallback.push(keyUpCallback); }
195
485
 
196
- //add the binding's combo to the active bindings array
197
- if(!activeBindings[binding.keyCombo]) {
198
- activeBindings[binding.keyCombo] = binding;
486
+ //stash the new binding
487
+ bindings.push(binding);
488
+ subBindings.push(binding);
489
+ }
490
+
491
+ //build the binding api
492
+ api.clear = clear;
493
+ api.on = on;
494
+ return api;
495
+
496
+ /**
497
+ * Clears the binding
498
+ */
499
+ function clear() {
500
+ var bI;
501
+ for(bI = 0; bI < subBindings.length; bI += 1) {
502
+ bindings.splice(bindings.indexOf(subBindings[bI]), 1);
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Accepts an event name. and any number of callbacks. When the event is
508
+ * emitted, all callbacks are executed. Available events are key up and
509
+ * key down.
510
+ * @param {String} eventName
511
+ * @return {Object} subBinding
512
+ */
513
+ function on(eventName ) {
514
+ var api = {}, callbacks, cI, bI;
515
+
516
+ //validate event name
517
+ if(typeof eventName !== 'string') { throw new Error('Cannot bind callback. The event name must be a string.'); }
518
+ if(eventName !== 'keyup' && eventName !== 'keydown') { throw new Error('Cannot bind callback. The event name must be a "keyup" or "keydown".'); }
519
+
520
+ //gather the callbacks
521
+ callbacks = Array.prototype.slice.apply(arguments, [1]);
522
+
523
+ //stash each the new binding
524
+ for(cI = 0; cI < callbacks.length; cI += 1) {
525
+ if(typeof callbacks[cI] === 'function') {
526
+ if(eventName === 'keyup') {
527
+ for(bI = 0; bI < subBindings.length; bI += 1) {
528
+ subBindings[bI].keyUpCallback.push(callbacks[cI]);
529
+ }
530
+ } else if(eventName === 'keydown') {
531
+ for(bI = 0; bI < subBindings.length; bI += 1) {
532
+ subBindings[bI].keyDownCallback.push(callbacks[cI]);
533
+ }
534
+ }
199
535
  }
536
+ }
200
537
 
201
- //add the current key binding's keys to the spent keys array
202
- for(var keyIndex = 0; keyIndex < binding.keys.length; keyIndex += 1) {
203
- var key = binding.keys[keyIndex];
204
- if(spentKeys.indexOf(key) < 0) {
205
- spentKeys.push(key);
538
+ //construct and return the sub binding api
539
+ api.clear = clear;
540
+ return api;
541
+
542
+ /**
543
+ * Clears the binding
544
+ */
545
+ function clear() {
546
+ var cI, bI;
547
+ for(cI = 0; cI < callbacks.length; cI += 1) {
548
+ if(typeof callbacks[cI] === 'function') {
549
+ if(eventName === 'keyup') {
550
+ for(bI = 0; bI < subBindings.length; bI += 1) {
551
+ subBindings[bI].keyUpCallback.splice(subBindings[bI].keyUpCallback.indexOf(callbacks[cI]), 1);
552
+ }
553
+ } else {
554
+ for(bI = 0; bI < subBindings.length; bI += 1) {
555
+ subBindings[bI].keyDownCallback.splice(subBindings[bI].keyDownCallback.indexOf(callbacks[cI]), 1);
556
+ }
557
+ }
206
558
  }
207
559
  }
208
560
  }
209
561
  }
562
+ }
210
563
 
211
- //if there are spent keys then we know a binding was fired
212
- // and that we need to tell jQuery to prevent event bubbling.
213
- if(spentKeys.length) {
214
- return false;
564
+ /**
565
+ * Clears all binding attached to a given key combo. Key name order does not
566
+ * matter as long as the key combos equate.
567
+ * @param {String} keyCombo
568
+ */
569
+ function removeBindingByKeyCombo(keyCombo) {
570
+ var bI, binding, keyName;
571
+ for(bI = 0; bI < bindings.length; bI += 1) {
572
+ binding = bindings[bI];
573
+ if(compareCombos(keyCombo, binding.keyCombo)) {
574
+ bindings.splice(bI, 1); bI -= 1;
575
+ }
215
576
  }
216
-
217
- return output;
218
577
  }
219
578
 
220
579
  /**
221
- * Removes no longer active keys and fires the on key up callbacks for associated active bindings.
222
- * @param event
580
+ * Clears all binding attached to key combos containing a given key name.
581
+ * @param {String} keyName
223
582
  */
224
- function pruneActiveKeyBindings(event) {
225
- var bindingStack = queryActiveBindings();
226
- var output;
227
-
228
- //loop through the active combos
229
- for(var bindingCombo in activeBindings) {
230
- if(activeBindings.hasOwnProperty(bindingCombo)) {
231
- var binding = activeBindings[bindingCombo],
232
- active = false;
233
-
234
- //loop thorugh the active bindings
235
- for(var bindingIndex = 0; bindingIndex < bindingStack.length; bindingIndex += 1) {
236
- var activeCombo = bindingStack[bindingIndex].keyCombo;
237
-
238
- //check to see if the combo is still active
239
- if(activeCombo === bindingCombo) {
240
- active = true;
583
+ function removeBindingByKeyName(keyName) {
584
+ var bI, cI, binding;
585
+ if(keyName) {
586
+ for(bI = 0; bI < bindings.length; bI += 1) {
587
+ binding = bindings[bI];
588
+ for(cI = 0; cI < binding.keyCombo.length; cI += 1) {
589
+ if(binding.keyCombo[kI].indexOf(keyName) > -1) {
590
+ bindings.splice(bI, 1); bI -= 1;
241
591
  break;
242
592
  }
243
593
  }
594
+ }
595
+ } else {
596
+ bindings = [];
597
+ }
598
+ }
244
599
 
245
- //if the combo is no longer active then fire its end callback and remove it
246
- if(!active) {
247
-
248
- if(typeof binding.endCallback === "function") {
249
- if(!binding.endCallback(event, binding.keys, binding.keyCombo)) {
250
- output = false
600
+ /**
601
+ * Executes bindings that are active. Only allows the keys to be used once
602
+ * as to prevent binding overlap.
603
+ * @param {KeyboardEvent} event The keyboard event.
604
+ */
605
+ function executeBindings(event) {
606
+ var bI, sBI, binding, bidningKeys, remainingKeys, cI, killEventBubble, kI, bindingKeysSatisfied,
607
+ index, sortedBindings = [], bindingWeight;
608
+
609
+ remainingKeys = [].concat(activeKeys);
610
+ for(bI = 0; bI < bindings.length; bI += 1) {
611
+ bindingWeight = extractComboKeys(bindings[bI].keyCombo).length;
612
+ if(!sortedBindings[bindingWeight]) { sortedBindings[bindingWeight] = []; }
613
+ sortedBindings[bindingWeight].push(bindings[bI]);
614
+ }
615
+ for(sBI = sortedBindings.length - 1; sBI >= 0; sBI -= 1) {
616
+ if(!sortedBindings[sBI]) { continue; }
617
+ for(bI = 0; bI < sortedBindings[sBI].length; bI += 1) {
618
+ binding = sortedBindings[sBI][bI];
619
+ bindingKeys = extractComboKeys(binding.keyCombo);
620
+ bindingKeysSatisfied = true;
621
+ for(kI = 0; kI < bindingKeys.length; kI += 1) {
622
+ if(remainingKeys.indexOf(bindingKeys[kI]) === -1) {
623
+ bindingKeysSatisfied = false;
624
+ break;
625
+ }
626
+ }
627
+ if(bindingKeysSatisfied && isSatisfiedCombo(binding.keyCombo)) {
628
+ activeBindings.push(binding);
629
+ for(kI = 0; kI < bindingKeys.length; kI += 1) {
630
+ index = remainingKeys.indexOf(bindingKeys[kI]);
631
+ if(index > -1) {
632
+ remainingKeys.splice(index, 1);
633
+ kI -= 1;
251
634
  }
252
635
  }
253
-
254
- delete activeBindings[bindingCombo];
636
+ for(cI = 0; cI < binding.keyDownCallback.length; cI += 1) {
637
+ if (binding.keyDownCallback[cI](event, getActiveKeys(), binding.keyCombo) === false) {
638
+ killEventBubble = true;
639
+ }
640
+ }
641
+ if(killEventBubble === true) {
642
+ event.preventDefault();
643
+ event.stopPropagation();
644
+ }
255
645
  }
256
646
  }
257
647
  }
258
-
259
- return output;
260
648
  }
261
649
 
262
650
  /**
263
- * Binds a on key down and on key up callback to a key or key combo. Accepts a string containing the name of each
264
- * key you want to bind to comma separated. If you want to bind a combo the use the plus sign to link keys together.
265
- * Example: 'ctrl + x, ctrl + c' Will fire if Control and x or y are pressed at the same time.
266
- * @param keyCombo
267
- * @param callback
268
- * @param endCallback
651
+ * Removes bindings that are no longer satisfied by the active keys. Also
652
+ * fires the key up callbacks.
653
+ * @param {KeyboardEvent} event
269
654
  */
270
- function bindKey(keyCombo, callback, endCallback) {
271
-
272
- function clear() {
273
- if(keys && keys.length) {
274
- var keyBindingGroup = keyBindingGroups[keys.length];
275
-
276
- if(keyBindingGroup.indexOf(keyBinding) > -1) {
277
- var index = keyBindingGroups[keys.length].indexOf(keyBinding);
278
- keyBindingGroups[keys.length].splice(index, 1);
655
+ function pruneBindings(event) {
656
+ var bI, cI, binding, killEventBubble;
657
+ for(bI = 0; bI < activeBindings.length; bI += 1) {
658
+ binding = activeBindings[bI];
659
+ if(isSatisfiedCombo(binding.keyCombo) === false) {
660
+ for(cI = 0; cI < binding.keyUpCallback.length; cI += 1) {
661
+ if (binding.keyUpCallback[cI](event, getActiveKeys(), binding.keyCombo) === false) {
662
+ killEventBubble = true;
663
+ }
664
+ }
665
+ if(killEventBubble === true) {
666
+ event.preventDefault();
667
+ event.stopPropagation();
279
668
  }
669
+ activeBindings.splice(bI, 1);
670
+ bI -= 1;
280
671
  }
281
672
  }
673
+ }
282
674
 
283
- //create an array of combos from the first argument
284
- var bindSets = keyCombo.toLowerCase().replace(/\s/g, '').split(',');
285
-
286
- //create a binding for each key combo
287
- for(var i = 0; i < bindSets.length; i += 1) {
288
-
289
- //split up the keys
290
- var keys = bindSets[i].split('+');
291
-
292
- //if there are keys in the current combo
293
- if(keys.length) {
294
- if(!keyBindingGroups[keys.length]) { keyBindingGroups[keys.length] = []; }
295
675
 
296
- //define the
297
- var keyBinding = {
298
- "callback": callback,
299
- "endCallback": endCallback,
300
- "keyCombo": bindSets[i],
301
- "keys": keys
302
- };
676
+ ///////////////////
677
+ // COMBO STRINGS //
678
+ ///////////////////
303
679
 
304
- //save the binding sorted by length
305
- keyBindingGroups[keys.length].push(keyBinding);
680
+ /**
681
+ * Compares two key combos returning true when they are functionally
682
+ * equivalent.
683
+ * @param {String} keyComboArrayA keyCombo A key combo string or array.
684
+ * @param {String} keyComboArrayB keyCombo A key combo string or array.
685
+ * @return {Boolean}
686
+ */
687
+ function compareCombos(keyComboArrayA, keyComboArrayB) {
688
+ var cI, sI, kI;
689
+ keyComboArrayA = parseKeyCombo(keyComboArrayA);
690
+ keyComboArrayB = parseKeyCombo(keyComboArrayB);
691
+ if(keyComboArrayA.length !== keyComboArrayB.length) { return false; }
692
+ for(cI = 0; cI < keyComboArrayA.length; cI += 1) {
693
+ if(keyComboArrayA[aI].length !== keyComboArrayB[aI].length) { return false; }
694
+ for(sI = 0; sI < keyComboArrayA[aI].length; sI += 1) {
695
+ if(keyComboArrayA[aI][sI].length !== keyComboArrayB[aI][sI].length) { return false; }
696
+ for(kI = 0; kI < keyComboArrayA[aI][sI].length; kI += 1) {
697
+ if(keyComboArrayB[aI][sI].indexOf(keyComboArrayA[aI][sI][kI]) === -1) { return false; }
698
+ }
306
699
  }
307
700
  }
308
-
309
- return {
310
- "clear": clear
311
- }
701
+ return true;
312
702
  }
313
703
 
314
704
  /**
315
- * Binds keys or key combos to an axis. The keys should be in the following order; up, down, left, right. If any
316
- * of the the binded key or key combos are active the callback will fire. The callback will be passed an array
317
- * containing two numbers. The first represents x and the second represents y. Both have a possible range of -1,
318
- * 0, or 1 depending on the axis direction.
319
- * @param up
320
- * @param down
321
- * @param left
322
- * @param right
323
- * @param callback
705
+ * Checks to see if a key combo string or key array is satisfied by the
706
+ * currently active keys. It does not take into account spent keys.
707
+ * @param {String} keyCombo A key combo string or array.
708
+ * @return {Boolean}
324
709
  */
325
- function bindAxis(up, down, left, right, callback) {
326
-
327
- function clear() {
328
- if(typeof clearUp === 'function') { clearUp(); }
329
- if(typeof clearDown === 'function') { clearDown(); }
330
- if(typeof clearLeft === 'function') { clearLeft(); }
331
- if(typeof clearRight === 'function') { clearRight(); }
332
- if(typeof timer === 'function') { clearInterval(timer); }
710
+ function isSatisfiedCombo(keyCombo) {
711
+ var cI, sI, stage, kI, stageOffset = 0, index, comboMatches;
712
+ keyCombo = parseKeyCombo(keyCombo);
713
+ for(cI = 0; cI < keyCombo.length; cI += 1) {
714
+ comboMatches = true;
715
+ stageOffset = 0;
716
+ for(sI = 0; sI < keyCombo[cI].length; sI += 1) {
717
+ stage = [].concat(keyCombo[cI][sI]);
718
+ for(kI = stageOffset; kI < activeKeys.length; kI += 1) {
719
+ index = stage.indexOf(activeKeys[kI]);
720
+ if(index > -1) {
721
+ stage.splice(index, 1);
722
+ stageOffset = kI;
723
+ }
724
+ }
725
+ if(stage.length !== 0) { comboMatches = false; break; }
726
+ }
727
+ if(comboMatches) { return true; }
333
728
  }
729
+ return false;
730
+ }
334
731
 
335
- var axis = [0, 0];
336
-
337
- if(typeof callback !== 'function') {
338
- return false;
732
+ /**
733
+ * Accepts a key combo array or string and returns a flat array containing all keys referenced by
734
+ * the key combo.
735
+ * @param {String} keyCombo A key combo string or array.
736
+ * @return {Array}
737
+ */
738
+ function extractComboKeys(keyCombo) {
739
+ var cI, sI, kI, keys = [];
740
+ keyCombo = parseKeyCombo(keyCombo);
741
+ for(cI = 0; cI < keyCombo.length; cI += 1) {
742
+ for(sI = 0; sI < keyCombo[cI].length; sI += 1) {
743
+ keys = keys.concat(keyCombo[cI][sI]);
744
+ }
339
745
  }
746
+ return keys;
747
+ }
340
748
 
341
- //up
342
- var clearUp = bindKey(up, function () {
343
- if(axis[0] === 0) {
344
- axis[0] = -1;
345
- }
346
- }, function() {
347
- axis[0] = 0;
348
- }).clear;
349
-
350
- //down
351
- var clearDown = bindKey(down, function () {
352
- if(axis[0] === 0) {
353
- axis[0] = 1;
354
- }
355
- }, function() {
356
- axis[0] = 0;
357
- }).clear;
358
-
359
- //left
360
- var clearLeft = bindKey(left, function () {
361
- if(axis[1] === 0) {
362
- axis[1] = -1;
363
- }
364
- }, function() {
365
- axis[1] = 0;
366
- }).clear;
367
-
368
- //right
369
- var clearRight = bindKey(right, function () {
370
- if(axis[1] === 0) {
371
- axis[1] = 1;
749
+ /**
750
+ * Parses a key combo string into a 3 dimensional array.
751
+ * - Level 1 - sub combos.
752
+ * - Level 2 - combo stages. A stage is a set of key name pairs that must
753
+ * be satisfied in the order they are defined.
754
+ * - Level 3 - each key name to the stage.
755
+ * @param {String|Array} keyCombo A key combo string.
756
+ * @return {Array}
757
+ */
758
+ function parseKeyCombo(keyCombo) {
759
+ var s = keyCombo, i = 0, op = 0, ws = false, nc = false, combos = [], combo = [], stage = [], key = '';
760
+
761
+ if(typeof keyCombo === 'object' && typeof keyCombo.push === 'function') { return keyCombo; }
762
+ if(typeof keyCombo !== 'string') { throw new Error('Cannot parse "keyCombo" because its type is "' + (typeof keyCombo) + '". It must be a "string".'); }
763
+
764
+ //remove leading whitespace
765
+ while(s.charAt(i) === ' ') { i += 1; }
766
+ while(true) {
767
+ if(s.charAt(i) === ' ') {
768
+ //white space & next combo op
769
+ while(s.charAt(i) === ' ') { i += 1; }
770
+ ws = true;
771
+ } else if(s.charAt(i) === ',') {
772
+ if(op || nc) { throw new Error('Failed to parse key combo. Unexpected , at character index ' + i + '.'); }
773
+ nc = true;
774
+ i += 1;
775
+ } else if(s.charAt(i) === '+') {
776
+ //next key
777
+ if(key.length) { stage.push(key); key = ''; }
778
+ if(op || nc) { throw new Error('Failed to parse key combo. Unexpected + at character index ' + i + '.'); }
779
+ op = true;
780
+ i += 1;
781
+ } else if(s.charAt(i) === '>') {
782
+ //next stage op
783
+ if(key.length) { stage.push(key); key = ''; }
784
+ if(stage.length) { combo.push(stage); stage = []; }
785
+ if(op || nc) { throw new Error('Failed to parse key combo. Unexpected > at character index ' + i + '.'); }
786
+ op = true;
787
+ i += 1;
788
+ } else if(i < s.length - 1 && s.charAt(i) === '!' && (s.charAt(i + 1) === '>' || s.charAt(i + 1) === ',' || s.charAt(i + 1) === '+')) {
789
+ key += s.charAt(i + 1);
790
+ op = false;
791
+ ws = false;
792
+ nc = false;
793
+ i += 2;
794
+ } else if(i < s.length && s.charAt(i) !== '+' && s.charAt(i) !== '>' && s.charAt(i) !== ',' && s.charAt(i) !== ' ') {
795
+ //end combo
796
+ if(op === false && ws === true || nc === true) {
797
+ if(key.length) { stage.push(key); key = ''; }
798
+ if(stage.length) { combo.push(stage); stage = []; }
799
+ if(combo.length) { combos.push(combo); combo = []; }
800
+ }
801
+ op = false;
802
+ ws = false;
803
+ nc = false;
804
+ //key
805
+ while(i < s.length && s.charAt(i) !== '+' && s.charAt(i) !== '>' && s.charAt(i) !== ',' && s.charAt(i) !== ' ') {
806
+ key += s.charAt(i);
807
+ i += 1;
808
+ }
809
+ } else {
810
+ //unknown char
811
+ i += 1;
812
+ continue;
372
813
  }
373
- }, function() {
374
- axis[1] = 0;
375
- }).clear;
376
-
377
- var timer = setInterval(function(){
378
-
379
- //NO CHANGE
380
- if(axis[0] === 0 && axis[1] === 0) {
381
- return;
814
+ //end of combos string
815
+ if(i >= s.length) {
816
+ if(key.length) { stage.push(key); key = ''; }
817
+ if(stage.length) { combo.push(stage); stage = []; }
818
+ if(combo.length) { combos.push(combo); combo = []; }
819
+ break;
382
820
  }
383
-
384
- //run the callback
385
- callback(axis);
386
-
387
- }, 1);
388
-
389
- return {
390
- "clear": clear
391
821
  }
822
+ return combos;
392
823
  }
393
824
 
394
825
  /**
395
- * Clears all key and key combo binds containing a given key or keys.
396
- * @param keys
826
+ * Stringifys a key combo.
827
+ * @param {Array|String} keyComboArray A key combo array. If a key
828
+ * combo string is given it will be returned.
829
+ * @return {String}
397
830
  */
398
- function unbindKey(keys) {
399
-
400
- if(keys === 'all') {
401
- keyBindingGroups = [];
402
- return;
831
+ function stringifyKeyCombo(keyComboArray) {
832
+ var cI, ccI, output = [];
833
+ if(typeof keyComboArray === 'string') { return keyComboArray; }
834
+ if(typeof keyComboArray !== 'object' || typeof keyComboArray.push !== 'function') { throw new Error('Cannot stringify key combo.'); }
835
+ for(cI = 0; cI < keyComboArray.length; cI += 1) {
836
+ output[cI] = [];
837
+ for(ccI = 0; ccI < keyComboArray[cI].length; ccI += 1) {
838
+ output[cI][ccI] = keyComboArray[cI][ccI].join(' + ');
839
+ }
840
+ output[cI] = output[cI].join(' > ');
403
841
  }
842
+ return output.join(' ');
843
+ }
404
844
 
405
- keys = keys.replace(/\s/g, '').split(',');
406
-
407
- //loop through the key binding groups.
408
- for(var iKCL = keyBindingGroups.length; iKCL > -1; iKCL -= 1) {
409
- if(keyBindingGroups[iKCL]) {
410
- var KeyBindingGroup = keyBindingGroups[iKCL];
411
-
412
- //loop through the key bindings.
413
- for(var iB = 0; iB < KeyBindingGroup.length; iB += 1) {
414
- var keyBinding = KeyBindingGroup[iB],
415
- remove = false;
416
845
 
417
- //loop through the current key binding keys.
418
- for(var iKB = 0; iKB < keyBinding.keys.length; iKB += 1) {
419
- var key = keyBinding.keys[iKB];
846
+ /////////////////
847
+ // ACTIVE KEYS //
848
+ /////////////////
420
849
 
421
- //loop through the keys to be removed
422
- for(var iKR = 0; iKR < keys.length; iKR += 1) {
423
- var keyToRemove = keys[iKR];
424
- if(keyToRemove === key) {
425
- remove = true;
426
- break;
427
- }
428
- }
429
- if(remove) { break; }
430
- }
431
- if(remove) {
432
- keyBindingGroups[iKCL].splice(iB, 1); iB -= 1;
433
- if(keyBindingGroups[iKCL].length < 1) {
434
- delete keyBindingGroups[iKCL];
435
- }
436
- }
437
- }
438
- }
439
- }
850
+ /**
851
+ * Returns the a copy of the active keys array.
852
+ * @return {Array}
853
+ */
854
+ function getActiveKeys() {
855
+ return [].concat(activeKeys);
440
856
  }
441
857
 
442
858
  /**
443
- * Gets an array of active keys
859
+ * Adds a key to the active keys array, but only if it has not already been
860
+ * added.
861
+ * @param {String} keyName The key name string.
444
862
  */
445
- function getActiveKeys() {
446
- return activeKeys;
863
+ function addActiveKey(keyName) {
864
+ if(keyName.match(/\s/)) { throw new Error('Cannot add key name ' + keyName + ' to active keys because it contains whitespace.'); }
865
+ if(activeKeys.indexOf(keyName) > -1) { return; }
866
+ activeKeys.push(keyName);
447
867
  }
448
868
 
449
869
  /**
450
- * Adds a new keyboard local not supported by keyboard JS
451
- * @param local
452
- * @param keys
870
+ * Removes a key from the active keys array.
871
+ * @param {String} keyNames The key name string.
453
872
  */
454
- function addLocale(local, keys) {
455
- locals[local] = keys;
873
+ function removeActiveKey(keyName) {
874
+ var keyCode = getKeyCode(keyName);
875
+ if(keyCode === '91' || keyCode === '92') { activeKeys = []; } //remove all key on release of super.
876
+ else { activeKeys.splice(activeKeys.indexOf(keyName), 1); }
456
877
  }
457
878
 
879
+
880
+ /////////////
881
+ // LOCALES //
882
+ /////////////
883
+
458
884
  /**
459
- * Changes the keyboard local
460
- * @param local
885
+ * Registers a new locale. This is useful if you would like to add support for a new keyboard layout. It could also be useful for
886
+ * alternative key names. For example if you program games you could create a locale for your key mappings. Instead of key 65 mapped
887
+ * to 'a' you could map it to 'jump'.
888
+ * @param {String} localeName The name of the new locale.
889
+ * @param {Object} localeMap The locale map.
461
890
  */
462
- function setLocale(local) {
463
- if(locals[local]) {
464
- keys = locals[local];
465
- }
891
+ function registerLocale(localeName, localeMap) {
892
+
893
+ //validate arguments
894
+ if(typeof localeName !== 'string') { throw new Error('Cannot register new locale. The locale name must be a string.'); }
895
+ if(typeof localeMap !== 'object') { throw new Error('Cannot register ' + localeName + ' locale. The locale map must be an object.'); }
896
+ if(typeof localeMap.map !== 'object') { throw new Error('Cannot register ' + localeName + ' locale. The locale map is invalid.'); }
466
897
 
898
+ //stash the locale
899
+ if(!localeMap.macros) { localeMap.macros = []; }
900
+ locales[localeName] = localeMap;
467
901
  }
468
902
 
469
- return {
470
- "bind": {
471
- "key": bindKey,
472
- "axis": bindAxis
473
- },
474
- "activeKeys": getActiveKeys,
475
- "unbind": {
476
- "key": unbindKey
477
- },
478
- "locale": {
479
- "add": addLocale,
480
- "set": setLocale
903
+ /**
904
+ * Swaps the current locale.
905
+ * @param {String} localeName The locale to activate.
906
+ * @return {Object}
907
+ */
908
+ function getSetLocale(localeName) {
909
+
910
+ //if a new locale is given then set it
911
+ if(localeName) {
912
+ if(typeof localeName !== 'string') { throw new Error('Cannot set locale. The locale name must be a string.'); }
913
+ if(!locales[localeName]) { throw new Error('Cannot set locale to ' + localeName + ' because it does not exist. If you would like to submit a ' + localeName + ' locale map for KeyboardJS please submit it at https://github.com/RobertWHurst/KeyboardJS/issues.'); }
914
+
915
+ //set the current map and macros
916
+ map = locales[localeName].map;
917
+ macros = locales[localeName].macros;
918
+
919
+ //set the current locale
920
+ locale = localeName;
481
921
  }
922
+
923
+ //return the current locale
924
+ return locale;
482
925
  }
483
- }));
926
+ });