]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/jquery/jquery.hotkeys.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / jquery / jquery.hotkeys.js
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 (function (jQuery){
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;
33     
34     var hotkeys = {
35         version: '0.7.9',
36         override: /keypress|keydown|keyup/g,
37         triggersMap: {},
38         
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', 
42             109: '-', 
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: '/'},
45         
46         shiftNums: { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&", 
47             "8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<", 
48             ".":">",  "/":"?",  "\\":"|" },
49         
50         newTrigger: function (type, combi, callback) { 
51             // i.e. {'keyup': {'ctrl': {cb: callback, disableInInput: false}}}
52             var result = {};
53             result[type] = {};
54             result[type][combi] = {cb: callback, disableInInput: false};
55             return result;
56         }
57     };
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 : '/'
64         });
65     //}
66     
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);
72         };
73     
74     jQuery.fn.unbind = function (type, combi, fn){
75         if (jQuery.isFunction(combi)){
76             fn = combi;
77             combi = null;
78         }
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];
84             }
85         }
86         // call jQuery original unbind
87         return  this.__unbind__(type, fn);
88     };
89     
90     jQuery.fn.bind = function(type, data, fn){
91         // grab keyup,keydown,keypress
92         var handle = type.match(hotkeys.override);
93         
94         if (jQuery.isFunction(data) || !handle){
95             // call jQuery.bind only
96             return this.__bind__(type, data, fn);
97         }
98         else{
99             // split the job
100             var result = null,            
101             // pass the rest to the original $.fn.bind
102             pass2jq = jQuery.trim(type.replace(hotkeys.override, ''));
103             
104             // see if there are other types, pass them to the original $.fn.bind
105             if (pass2jq){
106                 result = this.__bind__(pass2jq, data, fn);
107             }            
108             
109             if (typeof data === "string"){
110                 data = {'combi': data};
111             }
112             if(data.combi){
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();
118                         
119                     //trigger[eventType][combi].propagate = data.propagate;
120                     trigger[eventType][combi].disableInInput = data.disableInInput;
121                     
122                     // first time selector is bounded
123                     if (!hotkeys.triggersMap[selectorId]) {
124                         hotkeys.triggersMap[selectorId] = trigger;
125                     }
126                     // first time selector is bounded with this type
127                     else if (!hotkeys.triggersMap[selectorId][eventType]) {
128                         hotkeys.triggersMap[selectorId][eventType] = trigger[eventType];
129                     }
130                     // make trigger point as array so more than one handler can be bound
131                     var mapPoint = hotkeys.triggersMap[selectorId][eventType][combi];
132                     if (!mapPoint){
133                         hotkeys.triggersMap[selectorId][eventType][combi] = [trigger[eventType][combi]];
134                     }
135                     else if (mapPoint.constructor !== Array){
136                         hotkeys.triggersMap[selectorId][eventType][combi] = [mapPoint];
137                     }
138                     else {
139                         hotkeys.triggersMap[selectorId][eventType][combi][mapPoint.length] = trigger[eventType][combi];
140                     }
141                     
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);
146                         
147                         // element already associated with another collection
148                         if (jqElem.attr('hkId') && jqElem.attr('hkId') !== selectorId){
149                             selectorId = jqElem.attr('hkId') + ";" + selectorId;
150                         }
151                         jqElem.attr('hkId', selectorId);
152                     });
153                     result = this.__bind__(handle.join(' '), data, hotkeys.handler)
154                 }
155             }
156             return result;
157         }
158     };
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;
166                 }
167             }
168         }
169         return elem;
170     };
171     // the event handler
172     hotkeys.handler = function(event) {
173         var target = hotkeys.findElement(event.currentTarget), 
174             jTarget = jQuery(target),
175             ids = jTarget.attr('hkId');
176         
177         if(ids){
178             ids = ids.split(';');
179             var code = event.which,
180                 type = event.type,
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,
189                 mapPoint = null;
190
191             for (var x=0; x < ids.length; x++){
192                 if (hotkeys.triggersMap[ids[x]][type]){
193                     mapPoint = hotkeys.triggersMap[ids[x]][type];
194                     break;
195                 }
196             }
197             
198             //find by: id.type.combi.options            
199             if (mapPoint){ 
200                 var trigger;
201                 // event type is associated with the hkId
202                 if(!shift && !ctrl && !alt) { // No Modifiers
203                     trigger = mapPoint[special] ||  (character && mapPoint[character]);
204                 }
205                 else{
206                     // check combinations (alt|ctrl|shift+anything)
207                     var modif = '';
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];
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") || jTarget.is("select") 
229                                 || elem.is("input") || elem.is("textarea") || elem.is("select")) {
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);