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