1 | /**
|
2 | * This extension allows you to record a sequence using Mousetrap.
|
3 | *
|
4 | * @author Dan Tao <daniel.tao@gmail.com>
|
5 | */
|
6 | (function(Mousetrap) {
|
7 | /**
|
8 | * the sequence currently being recorded
|
9 | *
|
10 | * @type {Array}
|
11 | */
|
12 | var _recordedSequence = [],
|
13 |
|
14 | /**
|
15 | * a callback to invoke after recording a sequence
|
16 | *
|
17 | * @type {Function|null}
|
18 | */
|
19 | _recordedSequenceCallback = null,
|
20 |
|
21 | /**
|
22 | * a list of all of the keys currently held down
|
23 | *
|
24 | * @type {Array}
|
25 | */
|
26 | _currentRecordedKeys = [],
|
27 |
|
28 | /**
|
29 | * temporary state where we remember if we've already captured a
|
30 | * character key in the current combo
|
31 | *
|
32 | * @type {boolean}
|
33 | */
|
34 | _recordedCharacterKey = false,
|
35 |
|
36 | /**
|
37 | * a handle for the timer of the current recording
|
38 | *
|
39 | * @type {null|number}
|
40 | */
|
41 | _recordTimer = null,
|
42 |
|
43 | /**
|
44 | * the original handleKey method to override when Mousetrap.record() is
|
45 | * called
|
46 | *
|
47 | * @type {Function}
|
48 | */
|
49 | _origHandleKey = Mousetrap.handleKey;
|
50 |
|
51 | /**
|
52 | * handles a character key event
|
53 | *
|
54 | * @param {string} character
|
55 | * @param {Array} modifiers
|
56 | * @param {Event} e
|
57 | * @returns void
|
58 | */
|
59 | function _handleKey(character, modifiers, e) {
|
60 | // remember this character if we're currently recording a sequence
|
61 | if (e.type == 'keydown') {
|
62 | if (character.length === 1 && _recordedCharacterKey) {
|
63 | _recordCurrentCombo();
|
64 | }
|
65 |
|
66 | for (i = 0; i < modifiers.length; ++i) {
|
67 | _recordKey(modifiers[i]);
|
68 | }
|
69 | _recordKey(character);
|
70 |
|
71 | // once a key is released, all keys that were held down at the time
|
72 | // count as a keypress
|
73 | } else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {
|
74 | _recordCurrentCombo();
|
75 | }
|
76 | }
|
77 |
|
78 | /**
|
79 | * marks a character key as held down while recording a sequence
|
80 | *
|
81 | * @param {string} key
|
82 | * @returns void
|
83 | */
|
84 | function _recordKey(key) {
|
85 | var i;
|
86 |
|
87 | // one-off implementation of Array.indexOf, since IE6-9 don't support it
|
88 | for (i = 0; i < _currentRecordedKeys.length; ++i) {
|
89 | if (_currentRecordedKeys[i] === key) {
|
90 | return;
|
91 | }
|
92 | }
|
93 |
|
94 | _currentRecordedKeys.push(key);
|
95 |
|
96 | if (key.length === 1) {
|
97 | _recordedCharacterKey = true;
|
98 | }
|
99 | }
|
100 |
|
101 | /**
|
102 | * marks whatever key combination that's been recorded so far as finished
|
103 | * and gets ready for the next combo
|
104 | *
|
105 | * @returns void
|
106 | */
|
107 | function _recordCurrentCombo() {
|
108 | _recordedSequence.push(_currentRecordedKeys);
|
109 | _currentRecordedKeys = [];
|
110 | _recordedCharacterKey = false;
|
111 | _restartRecordTimer();
|
112 | }
|
113 |
|
114 | /**
|
115 | * ensures each combo in a sequence is in a predictable order and formats
|
116 | * key combos to be '+'-delimited
|
117 | *
|
118 | * modifies the sequence in-place
|
119 | *
|
120 | * @param {Array} sequence
|
121 | * @returns void
|
122 | */
|
123 | function _normalizeSequence(sequence) {
|
124 | var i;
|
125 |
|
126 | for (i = 0; i < sequence.length; ++i) {
|
127 | sequence[i].sort(function(x, y) {
|
128 | // modifier keys always come first, in alphabetical order
|
129 | if (x.length > 1 && y.length === 1) {
|
130 | return -1;
|
131 | } else if (x.length === 1 && y.length > 1) {
|
132 | return 1;
|
133 | }
|
134 |
|
135 | // character keys come next (list should contain no duplicates,
|
136 | // so no need for equality check)
|
137 | return x > y ? 1 : -1;
|
138 | });
|
139 |
|
140 | sequence[i] = sequence[i].join('+');
|
141 | }
|
142 | }
|
143 |
|
144 | /**
|
145 | * finishes the current recording, passes the recorded sequence to the stored
|
146 | * callback, and sets Mousetrap.handleKey back to its original function
|
147 | *
|
148 | * @returns void
|
149 | */
|
150 | function _finishRecording() {
|
151 | if (_recordedSequenceCallback) {
|
152 | _normalizeSequence(_recordedSequence);
|
153 | _recordedSequenceCallback(_recordedSequence);
|
154 | }
|
155 |
|
156 | // reset all recorded state
|
157 | _recordedSequence = [];
|
158 | _recordedSequenceCallback = null;
|
159 | _currentRecordedKeys = [];
|
160 |
|
161 | Mousetrap.handleKey = _origHandleKey;
|
162 | }
|
163 |
|
164 | /**
|
165 | * called to set a 1 second timeout on the current recording
|
166 | *
|
167 | * this is so after each key press in the sequence the recording will wait for
|
168 | * 1 more second before executing the callback
|
169 | *
|
170 | * @returns void
|
171 | */
|
172 | function _restartRecordTimer() {
|
173 | clearTimeout(_recordTimer);
|
174 | _recordTimer = setTimeout(_finishRecording, 1000);
|
175 | }
|
176 |
|
177 | /**
|
178 | * records the next sequence and passes it to a callback once it's
|
179 | * completed
|
180 | *
|
181 | * @param {Function} callback
|
182 | * @returns void
|
183 | */
|
184 | Mousetrap.record = function(callback) {
|
185 | Mousetrap.handleKey = _handleKey;
|
186 | _recordedSequenceCallback = callback;
|
187 | };
|
188 |
|
189 | })(Mousetrap);
|