mousetrap-rails 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1
2
+
3
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mousetrap-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2012 Nick Kugaevsky
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Mousetrap::Rails
2
+
3
+ [Moustrap](https://github.com/ccampbell/mousetrap) is a javascript library for handling keyboard shortcuts in your web applications written by [Craig Campbell](http://craig.is/).
4
+
5
+ The `mousetrap-rails` gem integrates Moustrap javascript library with Rails Asset Pipeline.
6
+
7
+ ## Installation
8
+
9
+ ### Install mousetrap-rails gem
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'mousetrap-rails'
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ ### Include moustrap to asset pipeline
20
+
21
+ Add to your `app/assets/javascripts/application.js` file
22
+
23
+ //= require mousetrap
24
+
25
+ Voila!
26
+
27
+ ## Usage
28
+
29
+ Now you can use Moustrap library features in your rails application. To test it out create coffeescript file in your `app/assets/javascripts` directory and add to it this code
30
+
31
+ # app/assets/javascripts/test_hotkeys.js.coffee
32
+ Mousetrap.bind 's', -> console.log 's pressed!'
33
+
34
+ Run application and press `s` on your keyboard. You should see `s pressed!` message in your javascript console.
35
+
36
+ You can find full documentation on [Moustrap library page](http://craig.is/killing/mice). Really, look there – there are plenty examples of using this awesome library.
37
+
38
+ ## Authors
39
+
40
+ * mousetrap-rails gem by [Nick Kugaevsky](http://kugaevsky.ru)
41
+ * original moustrap library by [Craig Campbell](http://craig.is/).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,11 @@
1
+ require "mousetrap-rails/version"
2
+
3
+ module Mousetrap
4
+ module Rails
5
+ if ::Rails.version < "3.1"
6
+ require "mousetrap-rails/railtie"
7
+ else
8
+ require "mousetrap-rails/engine"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Mousetrap
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Mousetrap
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Mousetrap
2
+ module Rails
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mousetrap-rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "mousetrap-rails"
8
+ gem.version = Mousetrap::Rails::VERSION
9
+ gem.authors = ["Nick Kugaevsky"]
10
+ gem.email = ["nick@kugaevsky.ru"]
11
+ gem.description = %q{Mousetrap is a javascript library for handling keyboard shortcuts in your web applications. This gem integrates Mousetrap with Rails asset pipeline for easy of use.}
12
+ gem.summary = %q{Integrate Moustrap javascript library with Rails Asset Pipeline}
13
+ gem.homepage = "https://github.com/kugaevsky/mousetrap-rails"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,811 @@
1
+ /**
2
+ * Copyright 2012 Craig Campbell
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * Mousetrap is a simple keyboard shortcut library for Javascript with
17
+ * no external dependencies
18
+ *
19
+ * @version 1.1.3
20
+ * @url craig.is/killing/mice
21
+ */
22
+ (function() {
23
+
24
+ /**
25
+ * mapping of special keycodes to their corresponding keys
26
+ *
27
+ * everything in this dictionary cannot use keypress events
28
+ * so it has to be here to map to the correct keycodes for
29
+ * keyup/keydown events
30
+ *
31
+ * @type {Object}
32
+ */
33
+ var _MAP = {
34
+ 8: 'backspace',
35
+ 9: 'tab',
36
+ 13: 'enter',
37
+ 16: 'shift',
38
+ 17: 'ctrl',
39
+ 18: 'alt',
40
+ 20: 'capslock',
41
+ 27: 'esc',
42
+ 32: 'space',
43
+ 33: 'pageup',
44
+ 34: 'pagedown',
45
+ 35: 'end',
46
+ 36: 'home',
47
+ 37: 'left',
48
+ 38: 'up',
49
+ 39: 'right',
50
+ 40: 'down',
51
+ 45: 'ins',
52
+ 46: 'del',
53
+ 91: 'meta',
54
+ 93: 'meta',
55
+ 224: 'meta'
56
+ },
57
+
58
+ /**
59
+ * mapping for special characters so they can support
60
+ *
61
+ * this dictionary is only used incase you want to bind a
62
+ * keyup or keydown event to one of these keys
63
+ *
64
+ * @type {Object}
65
+ */
66
+ _KEYCODE_MAP = {
67
+ 106: '*',
68
+ 107: '+',
69
+ 109: '-',
70
+ 110: '.',
71
+ 111 : '/',
72
+ 186: ';',
73
+ 187: '=',
74
+ 188: ',',
75
+ 189: '-',
76
+ 190: '.',
77
+ 191: '/',
78
+ 192: '`',
79
+ 219: '[',
80
+ 220: '\\',
81
+ 221: ']',
82
+ 222: '\''
83
+ },
84
+
85
+ /**
86
+ * this is a mapping of keys that require shift on a US keypad
87
+ * back to the non shift equivelents
88
+ *
89
+ * this is so you can use keyup events with these keys
90
+ *
91
+ * note that this will only work reliably on US keyboards
92
+ *
93
+ * @type {Object}
94
+ */
95
+ _SHIFT_MAP = {
96
+ '~': '`',
97
+ '!': '1',
98
+ '@': '2',
99
+ '#': '3',
100
+ '$': '4',
101
+ '%': '5',
102
+ '^': '6',
103
+ '&': '7',
104
+ '*': '8',
105
+ '(': '9',
106
+ ')': '0',
107
+ '_': '-',
108
+ '+': '=',
109
+ ':': ';',
110
+ '\"': '\'',
111
+ '<': ',',
112
+ '>': '.',
113
+ '?': '/',
114
+ '|': '\\'
115
+ },
116
+
117
+ /**
118
+ * this is a list of special strings you can use to map
119
+ * to modifier keys when you specify your keyboard shortcuts
120
+ *
121
+ * @type {Object}
122
+ */
123
+ _SPECIAL_ALIASES = {
124
+ 'option': 'alt',
125
+ 'command': 'meta',
126
+ 'return': 'enter',
127
+ 'escape': 'esc'
128
+ },
129
+
130
+ /**
131
+ * variable to store the flipped version of _MAP from above
132
+ * needed to check if we should use keypress or not when no action
133
+ * is specified
134
+ *
135
+ * @type {Object|undefined}
136
+ */
137
+ _REVERSE_MAP,
138
+
139
+ /**
140
+ * a list of all the callbacks setup via Mousetrap.bind()
141
+ *
142
+ * @type {Object}
143
+ */
144
+ _callbacks = {},
145
+
146
+ /**
147
+ * direct map of string combinations to callbacks used for trigger()
148
+ *
149
+ * @type {Object}
150
+ */
151
+ _direct_map = {},
152
+
153
+ /**
154
+ * keeps track of what level each sequence is at since multiple
155
+ * sequences can start out with the same sequence
156
+ *
157
+ * @type {Object}
158
+ */
159
+ _sequence_levels = {},
160
+
161
+ /**
162
+ * variable to store the setTimeout call
163
+ *
164
+ * @type {null|number}
165
+ */
166
+ _reset_timer,
167
+
168
+ /**
169
+ * temporary state where we will ignore the next keyup
170
+ *
171
+ * @type {boolean|string}
172
+ */
173
+ _ignore_next_keyup = false,
174
+
175
+ /**
176
+ * are we currently inside of a sequence?
177
+ * type of action ("keyup" or "keydown" or "keypress") or false
178
+ *
179
+ * @type {boolean|string}
180
+ */
181
+ _inside_sequence = false;
182
+
183
+ /**
184
+ * loop through the f keys, f1 to f19 and add them to the map
185
+ * programatically
186
+ */
187
+ for (var i = 1; i < 20; ++i) {
188
+ _MAP[111 + i] = 'f' + i;
189
+ }
190
+
191
+ /**
192
+ * loop through to map numbers on the numeric keypad
193
+ */
194
+ for (i = 0; i <= 9; ++i) {
195
+ _MAP[i + 96] = i;
196
+ }
197
+
198
+ /**
199
+ * cross browser add event method
200
+ *
201
+ * @param {Element|HTMLDocument} object
202
+ * @param {string} type
203
+ * @param {Function} callback
204
+ * @returns void
205
+ */
206
+ function _addEvent(object, type, callback) {
207
+ if (object.addEventListener) {
208
+ object.addEventListener(type, callback, false);
209
+ return;
210
+ }
211
+
212
+ object.attachEvent('on' + type, callback);
213
+ }
214
+
215
+ /**
216
+ * takes the event and returns the key character
217
+ *
218
+ * @param {Event} e
219
+ * @return {string}
220
+ */
221
+ function _characterFromEvent(e) {
222
+
223
+ // for keypress events we should return the character as is
224
+ if (e.type == 'keypress') {
225
+ return String.fromCharCode(e.which);
226
+ }
227
+
228
+ // for non keypress events the special maps are needed
229
+ if (_MAP[e.which]) {
230
+ return _MAP[e.which];
231
+ }
232
+
233
+ if (_KEYCODE_MAP[e.which]) {
234
+ return _KEYCODE_MAP[e.which];
235
+ }
236
+
237
+ // if it is not in the special map
238
+ return String.fromCharCode(e.which).toLowerCase();
239
+ }
240
+
241
+ /**
242
+ * checks if two arrays are equal
243
+ *
244
+ * @param {Array} modifiers1
245
+ * @param {Array} modifiers2
246
+ * @returns {boolean}
247
+ */
248
+ function _modifiersMatch(modifiers1, modifiers2) {
249
+ return modifiers1.sort().join(',') === modifiers2.sort().join(',');
250
+ }
251
+
252
+ /**
253
+ * resets all sequence counters except for the ones passed in
254
+ *
255
+ * @param {Object} do_not_reset
256
+ * @returns void
257
+ */
258
+ function _resetSequences(do_not_reset) {
259
+ do_not_reset = do_not_reset || {};
260
+
261
+ var active_sequences = false,
262
+ key;
263
+
264
+ for (key in _sequence_levels) {
265
+ if (do_not_reset[key]) {
266
+ active_sequences = true;
267
+ continue;
268
+ }
269
+ _sequence_levels[key] = 0;
270
+ }
271
+
272
+ if (!active_sequences) {
273
+ _inside_sequence = false;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * finds all callbacks that match based on the keycode, modifiers,
279
+ * and action
280
+ *
281
+ * @param {string} character
282
+ * @param {Array} modifiers
283
+ * @param {Event|Object} e
284
+ * @param {boolean=} remove - should we remove any matches
285
+ * @param {string=} combination
286
+ * @returns {Array}
287
+ */
288
+ function _getMatches(character, modifiers, e, remove, combination) {
289
+ var i,
290
+ callback,
291
+ matches = [],
292
+ action = e.type;
293
+
294
+ // if there are no events related to this keycode
295
+ if (!_callbacks[character]) {
296
+ return [];
297
+ }
298
+
299
+ // if a modifier key is coming up on its own we should allow it
300
+ if (action == 'keyup' && _isModifier(character)) {
301
+ modifiers = [character];
302
+ }
303
+
304
+ // loop through all callbacks for the key that was pressed
305
+ // and see if any of them match
306
+ for (i = 0; i < _callbacks[character].length; ++i) {
307
+ callback = _callbacks[character][i];
308
+
309
+ // if this is a sequence but it is not at the right level
310
+ // then move onto the next match
311
+ if (callback.seq && _sequence_levels[callback.seq] != callback.level) {
312
+ continue;
313
+ }
314
+
315
+ // if the action we are looking for doesn't match the action we got
316
+ // then we should keep going
317
+ if (action != callback.action) {
318
+ continue;
319
+ }
320
+
321
+ // if this is a keypress event and the meta key and control key
322
+ // are not pressed that means that we need to only look at the
323
+ // character, otherwise check the modifiers as well
324
+ //
325
+ // chrome will not fire a keypress if meta or control is down
326
+ // safari will fire a keypress if meta or meta+shift is down
327
+ // firefox will fire a keypress if meta or control is down
328
+ if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
329
+
330
+ // remove is used so if you change your mind and call bind a
331
+ // second time with a new function the first one is overwritten
332
+ if (remove && callback.combo == combination) {
333
+ _callbacks[character].splice(i, 1);
334
+ }
335
+
336
+ matches.push(callback);
337
+ }
338
+ }
339
+
340
+ return matches;
341
+ }
342
+
343
+ /**
344
+ * takes a key event and figures out what the modifiers are
345
+ *
346
+ * @param {Event} e
347
+ * @returns {Array}
348
+ */
349
+ function _eventModifiers(e) {
350
+ var modifiers = [];
351
+
352
+ if (e.shiftKey) {
353
+ modifiers.push('shift');
354
+ }
355
+
356
+ if (e.altKey) {
357
+ modifiers.push('alt');
358
+ }
359
+
360
+ if (e.ctrlKey) {
361
+ modifiers.push('ctrl');
362
+ }
363
+
364
+ if (e.metaKey) {
365
+ modifiers.push('meta');
366
+ }
367
+
368
+ return modifiers;
369
+ }
370
+
371
+ /**
372
+ * actually calls the callback function
373
+ *
374
+ * if your callback function returns false this will use the jquery
375
+ * convention - prevent default and stop propogation on the event
376
+ *
377
+ * @param {Function} callback
378
+ * @param {Event} e
379
+ * @returns void
380
+ */
381
+ function _fireCallback(callback, e) {
382
+ if (callback(e) === false) {
383
+ if (e.preventDefault) {
384
+ e.preventDefault();
385
+ }
386
+
387
+ if (e.stopPropagation) {
388
+ e.stopPropagation();
389
+ }
390
+
391
+ e.returnValue = false;
392
+ e.cancelBubble = true;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * handles a character key event
398
+ *
399
+ * @param {string} character
400
+ * @param {Event} e
401
+ * @returns void
402
+ */
403
+ function _handleCharacter(character, e) {
404
+
405
+ // if this event should not happen stop here
406
+ if (Mousetrap.stopCallback(e, e.target || e.srcElement)) {
407
+ return;
408
+ }
409
+
410
+ var callbacks = _getMatches(character, _eventModifiers(e), e),
411
+ i,
412
+ do_not_reset = {},
413
+ processed_sequence_callback = false;
414
+
415
+ // loop through matching callbacks for this key event
416
+ for (i = 0; i < callbacks.length; ++i) {
417
+
418
+ // fire for all sequence callbacks
419
+ // this is because if for example you have multiple sequences
420
+ // bound such as "g i" and "g t" they both need to fire the
421
+ // callback for matching g cause otherwise you can only ever
422
+ // match the first one
423
+ if (callbacks[i].seq) {
424
+ processed_sequence_callback = true;
425
+
426
+ // keep a list of which sequences were matches for later
427
+ do_not_reset[callbacks[i].seq] = 1;
428
+ _fireCallback(callbacks[i].callback, e);
429
+ continue;
430
+ }
431
+
432
+ // if there were no sequence matches but we are still here
433
+ // that means this is a regular match so we should fire that
434
+ if (!processed_sequence_callback && !_inside_sequence) {
435
+ _fireCallback(callbacks[i].callback, e);
436
+ }
437
+ }
438
+
439
+ // if you are inside of a sequence and the key you are pressing
440
+ // is not a modifier key then we should reset all sequences
441
+ // that were not matched by this key event
442
+ if (e.type == _inside_sequence && !_isModifier(character)) {
443
+ _resetSequences(do_not_reset);
444
+ }
445
+ }
446
+
447
+ /**
448
+ * handles a keydown event
449
+ *
450
+ * @param {Event} e
451
+ * @returns void
452
+ */
453
+ function _handleKey(e) {
454
+
455
+ // normalize e.which for key events
456
+ // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
457
+ e.which = typeof e.which == "number" ? e.which : e.keyCode;
458
+
459
+ var character = _characterFromEvent(e);
460
+
461
+ // no character found then stop
462
+ if (!character) {
463
+ return;
464
+ }
465
+
466
+ if (e.type == 'keyup' && _ignore_next_keyup == character) {
467
+ _ignore_next_keyup = false;
468
+ return;
469
+ }
470
+
471
+ _handleCharacter(character, e);
472
+ }
473
+
474
+ /**
475
+ * determines if the keycode specified is a modifier key or not
476
+ *
477
+ * @param {string} key
478
+ * @returns {boolean}
479
+ */
480
+ function _isModifier(key) {
481
+ return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
482
+ }
483
+
484
+ /**
485
+ * called to set a 1 second timeout on the specified sequence
486
+ *
487
+ * this is so after each key press in the sequence you have 1 second
488
+ * to press the next key before you have to start over
489
+ *
490
+ * @returns void
491
+ */
492
+ function _resetSequenceTimer() {
493
+ clearTimeout(_reset_timer);
494
+ _reset_timer = setTimeout(_resetSequences, 1000);
495
+ }
496
+
497
+ /**
498
+ * reverses the map lookup so that we can look for specific keys
499
+ * to see what can and can't use keypress
500
+ *
501
+ * @return {Object}
502
+ */
503
+ function _getReverseMap() {
504
+ if (!_REVERSE_MAP) {
505
+ _REVERSE_MAP = {};
506
+ for (var key in _MAP) {
507
+
508
+ // pull out the numeric keypad from here cause keypress should
509
+ // be able to detect the keys from the character
510
+ if (key > 95 && key < 112) {
511
+ continue;
512
+ }
513
+
514
+ if (_MAP.hasOwnProperty(key)) {
515
+ _REVERSE_MAP[_MAP[key]] = key;
516
+ }
517
+ }
518
+ }
519
+ return _REVERSE_MAP;
520
+ }
521
+
522
+ /**
523
+ * picks the best action based on the key combination
524
+ *
525
+ * @param {string} key - character for key
526
+ * @param {Array} modifiers
527
+ * @param {string=} action passed in
528
+ */
529
+ function _pickBestAction(key, modifiers, action) {
530
+
531
+ // if no action was picked in we should try to pick the one
532
+ // that we think would work best for this key
533
+ if (!action) {
534
+ action = _getReverseMap()[key] ? 'keydown' : 'keypress';
535
+ }
536
+
537
+ // modifier keys don't work as expected with keypress,
538
+ // switch to keydown
539
+ if (action == 'keypress' && modifiers.length) {
540
+ action = 'keydown';
541
+ }
542
+
543
+ return action;
544
+ }
545
+
546
+ /**
547
+ * binds a key sequence to an event
548
+ *
549
+ * @param {string} combo - combo specified in bind call
550
+ * @param {Array} keys
551
+ * @param {Function} callback
552
+ * @param {string=} action
553
+ * @returns void
554
+ */
555
+ function _bindSequence(combo, keys, callback, action) {
556
+
557
+ // start off by adding a sequence level record for this combination
558
+ // and setting the level to 0
559
+ _sequence_levels[combo] = 0;
560
+
561
+ // if there is no action pick the best one for the first key
562
+ // in the sequence
563
+ if (!action) {
564
+ action = _pickBestAction(keys[0], []);
565
+ }
566
+
567
+ /**
568
+ * callback to increase the sequence level for this sequence and reset
569
+ * all other sequences that were active
570
+ *
571
+ * @param {Event} e
572
+ * @returns void
573
+ */
574
+ var _increaseSequence = function(e) {
575
+ _inside_sequence = action;
576
+ ++_sequence_levels[combo];
577
+ _resetSequenceTimer();
578
+ },
579
+
580
+ /**
581
+ * wraps the specified callback inside of another function in order
582
+ * to reset all sequence counters as soon as this sequence is done
583
+ *
584
+ * @param {Event} e
585
+ * @returns void
586
+ */
587
+ _callbackAndReset = function(e) {
588
+ _fireCallback(callback, e);
589
+
590
+ // we should ignore the next key up if the action is key down
591
+ // or keypress. this is so if you finish a sequence and
592
+ // release the key the final key will not trigger a keyup
593
+ if (action !== 'keyup') {
594
+ _ignore_next_keyup = _characterFromEvent(e);
595
+ }
596
+
597
+ // weird race condition if a sequence ends with the key
598
+ // another sequence begins with
599
+ setTimeout(_resetSequences, 10);
600
+ },
601
+ i;
602
+
603
+ // loop through keys one at a time and bind the appropriate callback
604
+ // function. for any key leading up to the final one it should
605
+ // increase the sequence. after the final, it should reset all sequences
606
+ for (i = 0; i < keys.length; ++i) {
607
+ _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i);
608
+ }
609
+ }
610
+
611
+ /**
612
+ * binds a single keyboard combination
613
+ *
614
+ * @param {string} combination
615
+ * @param {Function} callback
616
+ * @param {string=} action
617
+ * @param {string=} sequence_name - name of sequence if part of sequence
618
+ * @param {number=} level - what part of the sequence the command is
619
+ * @returns void
620
+ */
621
+ function _bindSingle(combination, callback, action, sequence_name, level) {
622
+
623
+ // make sure multiple spaces in a row become a single space
624
+ combination = combination.replace(/\s+/g, ' ');
625
+
626
+ var sequence = combination.split(' '),
627
+ i,
628
+ key,
629
+ keys,
630
+ modifiers = [];
631
+
632
+ // if this pattern is a sequence of keys then run through this method
633
+ // to reprocess each pattern one key at a time
634
+ if (sequence.length > 1) {
635
+ _bindSequence(combination, sequence, callback, action);
636
+ return;
637
+ }
638
+
639
+ // take the keys from this pattern and figure out what the actual
640
+ // pattern is all about
641
+ keys = combination === '+' ? ['+'] : combination.split('+');
642
+
643
+ for (i = 0; i < keys.length; ++i) {
644
+ key = keys[i];
645
+
646
+ // normalize key names
647
+ if (_SPECIAL_ALIASES[key]) {
648
+ key = _SPECIAL_ALIASES[key];
649
+ }
650
+
651
+ // if this is not a keypress event then we should
652
+ // be smart about using shift keys
653
+ // this will only work for US keyboards however
654
+ if (action && action != 'keypress' && _SHIFT_MAP[key]) {
655
+ key = _SHIFT_MAP[key];
656
+ modifiers.push('shift');
657
+ }
658
+
659
+ // if this key is a modifier then add it to the list of modifiers
660
+ if (_isModifier(key)) {
661
+ modifiers.push(key);
662
+ }
663
+ }
664
+
665
+ // depending on what the key combination is
666
+ // we will try to pick the best event for it
667
+ action = _pickBestAction(key, modifiers, action);
668
+
669
+ // make sure to initialize array if this is the first time
670
+ // a callback is added for this key
671
+ if (!_callbacks[key]) {
672
+ _callbacks[key] = [];
673
+ }
674
+
675
+ // remove an existing match if there is one
676
+ _getMatches(key, modifiers, {type: action}, !sequence_name, combination);
677
+
678
+ // add this call back to the array
679
+ // if it is a sequence put it at the beginning
680
+ // if not put it at the end
681
+ //
682
+ // this is important because the way these are processed expects
683
+ // the sequence ones to come first
684
+ _callbacks[key][sequence_name ? 'unshift' : 'push']({
685
+ callback: callback,
686
+ modifiers: modifiers,
687
+ action: action,
688
+ seq: sequence_name,
689
+ level: level,
690
+ combo: combination
691
+ });
692
+ }
693
+
694
+ /**
695
+ * binds multiple combinations to the same callback
696
+ *
697
+ * @param {Array} combinations
698
+ * @param {Function} callback
699
+ * @param {string|undefined} action
700
+ * @returns void
701
+ */
702
+ function _bindMultiple(combinations, callback, action) {
703
+ for (var i = 0; i < combinations.length; ++i) {
704
+ _bindSingle(combinations[i], callback, action);
705
+ }
706
+ }
707
+
708
+ // start!
709
+ _addEvent(document, 'keypress', _handleKey);
710
+ _addEvent(document, 'keydown', _handleKey);
711
+ _addEvent(document, 'keyup', _handleKey);
712
+
713
+ var Mousetrap = {
714
+
715
+ /**
716
+ * binds an event to mousetrap
717
+ *
718
+ * can be a single key, a combination of keys separated with +,
719
+ * an array of keys, or a sequence of keys separated by spaces
720
+ *
721
+ * be sure to list the modifier keys first to make sure that the
722
+ * correct key ends up getting bound (the last key in the pattern)
723
+ *
724
+ * @param {string|Array} keys
725
+ * @param {Function} callback
726
+ * @param {string=} action - 'keypress', 'keydown', or 'keyup'
727
+ * @returns void
728
+ */
729
+ bind: function(keys, callback, action) {
730
+ _bindMultiple(keys instanceof Array ? keys : [keys], callback, action);
731
+ _direct_map[keys + ':' + action] = callback;
732
+ return this;
733
+ },
734
+
735
+ /**
736
+ * unbinds an event to mousetrap
737
+ *
738
+ * the unbinding sets the callback function of the specified key combo
739
+ * to an empty function and deletes the corresponding key in the
740
+ * _direct_map dict.
741
+ *
742
+ * the keycombo+action has to be exactly the same as
743
+ * it was defined in the bind method
744
+ *
745
+ * TODO: actually remove this from the _callbacks dictionary instead
746
+ * of binding an empty function
747
+ *
748
+ * @param {string|Array} keys
749
+ * @param {string} action
750
+ * @returns void
751
+ */
752
+ unbind: function(keys, action) {
753
+ if (_direct_map[keys + ':' + action]) {
754
+ delete _direct_map[keys + ':' + action];
755
+ this.bind(keys, function() {}, action);
756
+ }
757
+ return this;
758
+ },
759
+
760
+ /**
761
+ * triggers an event that has already been bound
762
+ *
763
+ * @param {string} keys
764
+ * @param {string=} action
765
+ * @returns void
766
+ */
767
+ trigger: function(keys, action) {
768
+ _direct_map[keys + ':' + action]();
769
+ return this;
770
+ },
771
+
772
+ /**
773
+ * resets the library back to its initial state. this is useful
774
+ * if you want to clear out the current keyboard shortcuts and bind
775
+ * new ones - for example if you switch to another page
776
+ *
777
+ * @returns void
778
+ */
779
+ reset: function() {
780
+ _callbacks = {};
781
+ _direct_map = {};
782
+ return this;
783
+ },
784
+
785
+ /**
786
+ * should we stop this event before firing off callbacks
787
+ *
788
+ * @param {Event} e
789
+ * @param {Element} element
790
+ * @return {boolean}
791
+ */
792
+ stopCallback: function(e, element) {
793
+
794
+ // if the element has the class "mousetrap" then no need to stop
795
+ if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
796
+ return false;
797
+ }
798
+
799
+ // stop for input, select, and textarea
800
+ return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
801
+ }
802
+ };
803
+
804
+ // expose mousetrap to the global object
805
+ window.Mousetrap = Mousetrap;
806
+
807
+ // expose mousetrap as an AMD module
808
+ if (typeof define == 'function' && define.amd) {
809
+ define('mousetrap', function() { return Mousetrap; });
810
+ }
811
+ }) ();
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mousetrap-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nick Kugaevsky
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Mousetrap is a javascript library for handling keyboard shortcuts in
15
+ your web applications. This gem integrates Mousetrap with Rails asset pipeline for
16
+ easy of use.
17
+ email:
18
+ - nick@kugaevsky.ru
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - .gitignore
24
+ - CHANGELOG.md
25
+ - Gemfile
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - lib/mousetrap-rails.rb
30
+ - lib/mousetrap-rails/engine.rb
31
+ - lib/mousetrap-rails/railtie.rb
32
+ - lib/mousetrap-rails/version.rb
33
+ - mousetrap-rails.gemspec
34
+ - vendor/assets/javascripts/mousetrap.js
35
+ homepage: https://github.com/kugaevsky/mousetrap-rails
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 1.8.24
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Integrate Moustrap javascript library with Rails Asset Pipeline
59
+ test_files: []