jquery.hotkeys.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. (c) Copyrights 2007 - 2008
  3. Original idea by by Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
  4. jQuery Plugin by Tzury Bar Yochay
  5. tzury.by@gmail.com
  6. http://evalinux.wordpress.com
  7. http://facebook.com/profile.php?id=513676303
  8. Project's sites:
  9. http://code.google.com/p/js-hotkeys/
  10. http://github.com/tzuryby/hotkeys/tree/master
  11. License: same as jQuery license.
  12. USAGE:
  13. // simple usage
  14. $(document).bind('keydown', 'Ctrl+c', function(){ alert('copy anyone?');});
  15. // special options such as disableInIput
  16. $(document).bind('keydown', {combi:'Ctrl+x', disableInInput: true} , function() {});
  17. Note:
  18. This plugin wraps the following jQuery methods: $.fn.find, $.fn.bind and $.fn.unbind
  19. */
  20. (function (jQuery){
  21. // keep reference to the original $.fn.bind and $.fn.unbind
  22. jQuery.fn.__bind__ = jQuery.fn.bind;
  23. jQuery.fn.__unbind__ = jQuery.fn.unbind;
  24. jQuery.fn.__find__ = jQuery.fn.find;
  25. var hotkeys = {
  26. version: '0.7.8',
  27. override: /keydown|keypress|keyup/g,
  28. triggersMap: {},
  29. specialKeys: { 27: 'esc', 9: 'tab', 32:'space', 13: 'return', 8:'backspace', 145: 'scroll',
  30. 20: 'capslock', 144: 'numlock', 19:'pause', 45:'insert', 36:'home', 46:'del',
  31. 35:'end', 33: 'pageup', 34:'pagedown', 37:'left', 38:'up', 39:'right',40:'down',
  32. 112:'f1',113:'f2', 114:'f3', 115:'f4', 116:'f5', 117:'f6', 118:'f7', 119:'f8',
  33. 120:'f9', 121:'f10', 122:'f11', 123:'f12' },
  34. shiftNums: { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&",
  35. "8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<",
  36. ".":">", "/":"?", "\\":"|" },
  37. newTrigger: function (type, combi, callback) {
  38. // i.e. {'keyup': {'ctrl': {cb: callback, disableInInput: false}}}
  39. var result = {};
  40. result[type] = {};
  41. result[type][combi] = {cb: callback, disableInInput: false};
  42. return result;
  43. }
  44. };
  45. // add firefox num pad char codes
  46. if (jQuery.browser.mozilla){
  47. hotkeys.specialKeys = jQuery.extend(hotkeys.specialKeys, { 96: '0', 97:'1', 98: '2', 99:
  48. '3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9' });
  49. }
  50. // a wrapper around of $.fn.find
  51. // see more at: http://groups.google.com/group/jquery-en/browse_thread/thread/18f9825e8d22f18d
  52. jQuery.fn.find = function( selector ) {
  53. this.query=selector;
  54. return jQuery.fn.__find__.apply(this, arguments);
  55. };
  56. jQuery.fn.unbind = function (type, combi, fn){
  57. if (jQuery.isFunction(combi)){
  58. fn = combi;
  59. combi = null;
  60. }
  61. if (combi && typeof combi === 'string'){
  62. var selectorId = ((this.prevObject && this.prevObject.query) || (this[0].id && this[0].id) || this[0]).toString();
  63. var hkTypes = type.split(' ');
  64. for (var x=0; x<hkTypes.length; x++){
  65. delete hotkeys.triggersMap[selectorId][hkTypes[x]][combi];
  66. }
  67. }
  68. // call jQuery original unbind
  69. return this.__unbind__(type, fn);
  70. };
  71. jQuery.fn.bind = function(type, data, fn){
  72. // grab keyup,keydown,keypress
  73. var handle = type.match(hotkeys.override);
  74. if (jQuery.isFunction(data) || !handle){
  75. // call jQuery.bind only
  76. return this.__bind__(type, data, fn);
  77. }
  78. else{
  79. // split the job
  80. var result = null,
  81. // pass the rest to the original $.fn.bind
  82. pass2jq = jQuery.trim(type.replace(hotkeys.override, ''));
  83. // see if there are other types, pass them to the original $.fn.bind
  84. if (pass2jq){
  85. // call original jQuery.bind()
  86. result = this.__bind__(pass2jq, data, fn);
  87. }
  88. if (typeof data === "string"){
  89. data = {'combi': data};
  90. }
  91. if(data.combi){
  92. for (var x=0; x < handle.length; x++){
  93. var eventType = handle[x];
  94. var combi = data.combi.toLowerCase(),
  95. trigger = hotkeys.newTrigger(eventType, combi, fn),
  96. selectorId = ((this.prevObject && this.prevObject.query) || (this[0].id && this[0].id) || this[0]).toString();
  97. //trigger[eventType][combi].propagate = data.propagate;
  98. trigger[eventType][combi].disableInInput = data.disableInInput;
  99. // first time selector is bounded
  100. if (!hotkeys.triggersMap[selectorId]) {
  101. hotkeys.triggersMap[selectorId] = trigger;
  102. }
  103. // first time selector is bounded with this type
  104. else if (!hotkeys.triggersMap[selectorId][eventType]) {
  105. hotkeys.triggersMap[selectorId][eventType] = trigger[eventType];
  106. }
  107. // make trigger point as array so more than one handler can be bound
  108. var mapPoint = hotkeys.triggersMap[selectorId][eventType][combi];
  109. if (!mapPoint){
  110. hotkeys.triggersMap[selectorId][eventType][combi] = [trigger[eventType][combi]];
  111. }
  112. else if (mapPoint.constructor !== Array){
  113. hotkeys.triggersMap[selectorId][eventType][combi] = [mapPoint];
  114. }
  115. else {
  116. hotkeys.triggersMap[selectorId][eventType][combi][mapPoint.length] = trigger[eventType][combi];
  117. }
  118. // add attribute and call $.event.add per matched element
  119. this.each(function(){
  120. // jQuery wrapper for the current element
  121. var jqElem = jQuery(this);
  122. // element already associated with another collection
  123. if (jqElem.attr('hkId') && jqElem.attr('hkId') !== selectorId){
  124. selectorId = jqElem.attr('hkId') + ";" + selectorId;
  125. }
  126. jqElem.attr('hkId', selectorId);
  127. });
  128. result = this.__bind__(handle.join(' '), data, hotkeys.handler)
  129. }
  130. }
  131. return result;
  132. }
  133. };
  134. // work-around for opera and safari where (sometimes) the target is the element which was last
  135. // clicked with the mouse and not the document event it would make sense to get the document
  136. hotkeys.findElement = function (elem){
  137. if (!jQuery(elem).attr('hkId')){
  138. if (jQuery.browser.opera || jQuery.browser.safari){
  139. while (!jQuery(elem).attr('hkId') && elem.parentNode){
  140. elem = elem.parentNode;
  141. }
  142. }
  143. }
  144. return elem;
  145. };
  146. // the event handler
  147. hotkeys.handler = function(event) {
  148. var target = hotkeys.findElement(event.currentTarget),
  149. jTarget = jQuery(target),
  150. ids = jTarget.attr('hkId');
  151. if(ids){
  152. ids = ids.split(';');
  153. var code = event.which,
  154. type = event.type,
  155. special = hotkeys.specialKeys[code],
  156. // prevent f5 overlapping with 't' (or f4 with 's', etc.)
  157. character = !special && String.fromCharCode(code).toLowerCase(),
  158. shift = event.shiftKey,
  159. ctrl = event.ctrlKey,
  160. // patch for jquery 1.2.5 && 1.2.6 see more at:
  161. // http://groups.google.com/group/jquery-en/browse_thread/thread/83e10b3bb1f1c32b
  162. alt = event.altKey || event.originalEvent.altKey,
  163. mapPoint = null;
  164. for (var x=0; x < ids.length; x++){
  165. if (hotkeys.triggersMap[ids[x]][type]){
  166. mapPoint = hotkeys.triggersMap[ids[x]][type];
  167. break;
  168. }
  169. }
  170. //find by: id.type.combi.options
  171. if (mapPoint){
  172. var trigger;
  173. // event type is associated with the hkId
  174. if(!shift && !ctrl && !alt) { // No Modifiers
  175. trigger = mapPoint[special] || (character && mapPoint[character]);
  176. }
  177. else{
  178. // check combinations (alt|ctrl|shift+anything)
  179. var modif = '';
  180. if(alt) modif +='alt+';
  181. if(ctrl) modif+= 'ctrl+';
  182. if(shift) modif += 'shift+';
  183. // modifiers + special keys or modifiers + character or modifiers + shift character or just shift character
  184. trigger = mapPoint[modif+special];
  185. if (!trigger){
  186. if (character){
  187. trigger = mapPoint[modif+character]
  188. || mapPoint[modif+hotkeys.shiftNums[character]]
  189. // '$' can be triggered as 'Shift+4' or 'Shift+$' or just '$'
  190. || (modif === 'shift+' && mapPoint[hotkeys.shiftNums[character]]);
  191. }
  192. }
  193. }
  194. if (trigger){
  195. var result = false;
  196. for (var x=0; x < trigger.length; x++){
  197. if(trigger[x].disableInInput){
  198. // double check event.currentTarget and event.target
  199. var elem = jQuery(event.target);
  200. if (jTarget.is("input") || jTarget.is("textarea")
  201. || elem.is("input") || elem.is("textarea")) {
  202. return true;
  203. }
  204. }
  205. // call the registered callback function
  206. result = result || trigger[x].cb.apply(this, [event]);
  207. }
  208. return result;
  209. }
  210. }
  211. }
  212. };
  213. // place it under window so it can be extended and overridden by others
  214. window.hotkeys = hotkeys;
  215. return jQuery;
  216. })(jQuery);