mousetrap-record.js 5.4 KB

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