mousetrap-record.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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.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. // remember this character if we're currently recording a sequence
  55. if (e.type == 'keydown') {
  56. if (character.length === 1 && _recordedCharacterKey) {
  57. _recordCurrentCombo();
  58. }
  59. for (i = 0; i < modifiers.length; ++i) {
  60. _recordKey(modifiers[i]);
  61. }
  62. _recordKey(character);
  63. // once a key is released, all keys that were held down at the time
  64. // count as a keypress
  65. } else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {
  66. _recordCurrentCombo();
  67. }
  68. }
  69. /**
  70. * marks a character key as held down while recording a sequence
  71. *
  72. * @param {string} key
  73. * @returns void
  74. */
  75. function _recordKey(key) {
  76. var i;
  77. // one-off implementation of Array.indexOf, since IE6-9 don't support it
  78. for (i = 0; i < _currentRecordedKeys.length; ++i) {
  79. if (_currentRecordedKeys[i] === key) {
  80. return;
  81. }
  82. }
  83. _currentRecordedKeys.push(key);
  84. if (key.length === 1) {
  85. _recordedCharacterKey = true;
  86. }
  87. }
  88. /**
  89. * marks whatever key combination that's been recorded so far as finished
  90. * and gets ready for the next combo
  91. *
  92. * @returns void
  93. */
  94. function _recordCurrentCombo() {
  95. _recordedSequence.push(_currentRecordedKeys);
  96. _currentRecordedKeys = [];
  97. _recordedCharacterKey = false;
  98. _restartRecordTimer();
  99. }
  100. /**
  101. * ensures each combo in a sequence is in a predictable order and formats
  102. * key combos to be '+'-delimited
  103. *
  104. * modifies the sequence in-place
  105. *
  106. * @param {Array} sequence
  107. * @returns void
  108. */
  109. function _normalizeSequence(sequence) {
  110. var i;
  111. for (i = 0; i < sequence.length; ++i) {
  112. sequence[i].sort(function(x, y) {
  113. // modifier keys always come first, in alphabetical order
  114. if (x.length > 1 && y.length === 1) {
  115. return -1;
  116. } else if (x.length === 1 && y.length > 1) {
  117. return 1;
  118. }
  119. // character keys come next (list should contain no duplicates,
  120. // so no need for equality check)
  121. return x > y ? 1 : -1;
  122. });
  123. sequence[i] = sequence[i].join('+');
  124. }
  125. }
  126. /**
  127. * finishes the current recording, passes the recorded sequence to the stored
  128. * callback, and sets Mousetrap.handleKey back to its original function
  129. *
  130. * @returns void
  131. */
  132. function _finishRecording() {
  133. if (_recordedSequenceCallback) {
  134. _normalizeSequence(_recordedSequence);
  135. _recordedSequenceCallback(_recordedSequence);
  136. }
  137. // reset all recorded state
  138. _recordedSequence = [];
  139. _recordedSequenceCallback = null;
  140. _currentRecordedKeys = [];
  141. Mousetrap.handleKey = _origHandleKey;
  142. }
  143. /**
  144. * called to set a 1 second timeout on the current recording
  145. *
  146. * this is so after each key press in the sequence the recording will wait for
  147. * 1 more second before executing the callback
  148. *
  149. * @returns void
  150. */
  151. function _restartRecordTimer() {
  152. clearTimeout(_recordTimer);
  153. _recordTimer = setTimeout(_finishRecording, 1000);
  154. }
  155. /**
  156. * records the next sequence and passes it to a callback once it's
  157. * completed
  158. *
  159. * @param {Function} callback
  160. * @returns void
  161. */
  162. Mousetrap.record = function(callback) {
  163. Mousetrap.handleKey = _handleKey;
  164. _recordedSequenceCallback = callback;
  165. };
  166. })(Mousetrap);