keyboardjs-rails 0.2.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ });