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