forked from zuriby/jquery.hotkeys
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathjquery.hotkeys.js
299 lines (263 loc) · 12.8 KB
/
jquery.hotkeys.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/*
(c) Copyrights 2007 - 2008
Original idea by by Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
jQuery Plugin by Tzury Bar Yochay
http://evalinux.wordpress.com
http://facebook.com/profile.php?id=513676303
Project's sites:
http://code.google.com/p/js-hotkeys/
http://github.com/tzuryby/hotkeys/tree/master
License: same as jQuery license.
USAGE:
// simple usage
$(document).bind('keydown', 'Ctrl+c', function(){ alert('copy anyone?');});
// special options such as disableInIput
$(document).bind('keydown', {combi:'Ctrl+x', disableInInput: true} , function() {});
// adjustments for multiple key commands (more than 2) or 2 modifiers such as shift+ctrl
multiple keys can now be done as follows:
$(document).bind('keydown', 'ctrl+shift+up', function(){ alert('copy anyone?');});
!!! However, if you doing a modifier and a modifier ONLY, you need to (currently) do as follows...
place modifiers in the correct sequence that they are added:
command
ctrl
alt
shift
and then add the last one as a special key as well.
such as cmd+shift+shift or ctrl+alt+alt
NOTE: many combo keys will interfere with screen reader software, make sure to research standard access keys
determining your combination of keys.asz-
Note:
This plugin wraps the following jQuery methods: $.fn.find, $.fn.bind and $.fn.unbind
*/
//
(function (jQuery){
// keep reference to the original $.fn.bind, $.fn.unbind and $.fn.find
jQuery.fn.__bind__ = jQuery.fn.bind;
jQuery.fn.__unbind__ = jQuery.fn.unbind;
jQuery.fn.__find__ = jQuery.fn.find;
var hotkeys = {
version: '0.7.9',
override: /keypress|keydown|keyup/g,
triggersMap: {},
specialKeys: { 16: 'shift', 17: 'ctrl', 18: 'alt', 27: 'esc', 9: 'tab', 32:'space', 13: 'return', 8:'backspace', 145: 'scroll',
20: 'capslock', 144: 'numlock', 19:'pause', 45:'insert', 36:'home', 46:'del',
35:'end', 33: 'pageup', 34:'pagedown', 37:'left', 38:'up', 39:'right',40:'down',
109: '-',
112:'f1',113:'f2', 114:'f3', 115:'f4', 116:'f5', 117:'f6', 118:'f7', 119:'f8',
120:'f9', 121:'f10', 122:'f11', 123:'f12', 191: '/', 91:'wincmd', 224:'cmd'},
Nums: { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&",
"8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<",
".":">", "/":"?", "\\":"|" },
newTrigger: function (type, combi, callback) { /* Here is your sample, use the ctrl key */
// i.e. {'keyup': {'ctrl': {cb: callback, disableInInput: false}}}
var result = {};
result[type] = {};
result[type][combi] = {cb: callback, disableInInput: false};
return result;
}
};
// add firefox num pad char codes
//if (jQuery.browser.mozilla){
// add num pad char codes
hotkeys.specialKeys = jQuery.extend(hotkeys.specialKeys, { 96: '0', 97:'1', 98: '2', 99:
'3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9', 106: '*',
107: '+', 109: '-', 110: '.', 111 : '/'
});
//}
// a wrapper around of $.fn.find
// see more at: http://groups.google.com/group/jquery-en/browse_thread/thread/18f9825e8d22f18d
jQuery.fn.find = function( selector ) {
this.query = selector;
return jQuery.fn.__find__.apply(this, arguments);
};
jQuery.fn.unbind = function (type, combi, fn){
if (jQuery.isFunction(combi)){
fn = combi;
combi = null;
}
if (combi && typeof combi === 'string'){
var selectorId = ((this.prevObject && this.prevObject.query) || (this[0].id && this[0].id) || this[0]).toString();
var hkTypes = type.split(' ');
for (var x=0; x<hkTypes.length; x++){
if(hotkeys.triggersMap[selectorId] && hotkeys.triggersMap[selectorId][hkTypes[x]] && hotkeys.triggersMap[selectorId][hkTypes[x]][combi])
delete hotkeys.triggersMap[selectorId][hkTypes[x]][combi];
}
} else if (typeof type === 'undefined') {
if(hotkeys.triggersMap[selectorId])
delete hotkeys.triggersMap[selectorId];
} else if (typeof combi === 'undefined') {
var hkTypes = type.split(' ');
for (var x=0; x<hkTypes.length; x++){
if(hotkeys.triggersMap[selectorId] && hotkeys.triggersMap[selectorId][hkTypes[x]] && hotkeys.triggersMap[selectorId][hkTypes[x]])
delete hotkeys.triggersMap[selectorId][hkTypes[x]];
}
}
// call jQuery original unbind
return this.__unbind__(type, fn);
};
jQuery.fn.bind = function(type, data, fn){
// grab keyup,keydown,keypress
var handle = type.match(hotkeys.override);
if (jQuery.isFunction(data) || !handle){
// call jQuery.bind only
return this.__bind__(type, data, fn);
}
else{
// split the job
var result = null,
// pass the rest to the original $.fn.bind
pass2jq = jQuery.trim(type.replace(hotkeys.override, ''));
// see if there are other types, pass them to the original $.fn.bind
if (pass2jq){
result = this.__bind__(pass2jq, data, fn);
}
if (typeof data === "string"){
data = {'combi': data};
}
if(data.combi){
for (var x=0; x < handle.length; x++){
var eventType = handle[x];
var combi = data.combi.toLowerCase(),
trigger = hotkeys.newTrigger(eventType, combi, fn),
selectorId = ((this.prevObject && this.prevObject.query) || (this[0].id && this[0].id) || this[0]).toString();
//trigger[eventType][combi].propagate = data.propagate;
trigger[eventType][combi].disableInInput = data.disableInInput;
// first time selector is bounded
if (!hotkeys.triggersMap[selectorId]) {
hotkeys.triggersMap[selectorId] = trigger;
}
// first time selector is bounded with this type
else if (!hotkeys.triggersMap[selectorId][eventType]) {
hotkeys.triggersMap[selectorId][eventType] = trigger[eventType];
}
// make trigger point as array so more than one handler can be bound
var mapPoint = hotkeys.triggersMap[selectorId][eventType][combi];
if (!mapPoint){
hotkeys.triggersMap[selectorId][eventType][combi] = [trigger[eventType][combi]];
}
else if (mapPoint.constructor !== Array){
hotkeys.triggersMap[selectorId][eventType][combi] = [mapPoint];
}
else {
hotkeys.triggersMap[selectorId][eventType][combi][mapPoint.length] = trigger[eventType][combi];
}
// add attribute and call $.event.add per matched element
this.each(function(){
// jQuery wrapper for the current element
var jqElem = jQuery(this),
newSelectorId = selectorId;
// element already associated with another collection
// branching modified to prevent duplicates 04/02/2012 --BM
var hkId = jqElem.attr('hkId');
if (hkId) {
newSelectorId = hkId;
if($(hkId
.split(";"))
.filter(function(i, item) { return item === selectorId })
.length == 0){
newSelectorId = newSelectorId + ";" + selectorId;
}
}
jqElem.attr('hkId', newSelectorId);
});
result = this.__bind__(handle.join(' '), data, hotkeys.handler);
}
}
return result;
}
};
// work-around for opera and safari where (sometimes) the target is the element which was last
// clicked with the mouse and not the document event it would make sense to get the document
hotkeys.findElement = function (elem){
if (!jQuery(elem).attr('hkId')){
if (jQuery.browser.opera || jQuery.browser.safari){
while (!jQuery(elem).attr('hkId') && elem.parentNode){
elem = elem.parentNode;
}
}
}
return elem;
};
// the event handler
hotkeys.handler = function(event) {
var target = hotkeys.findElement(event.currentTarget),
jTarget = jQuery(target),
ids = jTarget.attr('hkId');
if(ids){
ids = ids.split(';');
var code = event.which,
type = event.type,
special = hotkeys.specialKeys[code],
// prevent f5 overlapping with 't' (or f4 with 's', etc.)
character = !special && String.fromCharCode(code).toLowerCase(),
command = event.metaKey,
shift = event.shiftKey,
ctrl = event.ctrlKey,
// patch for jquery 1.2.5 && 1.2.6 see more at:
// http://groups.google.com/group/jquery-en/browse_thread/thread/83e10b3bb1f1c32b
alt = event.altKey || (event.originalEvent &&event.originalEvent.altKey),
mapPoint = null;
for (var x=0; x < ids.length; x++){
if (hotkeys.triggersMap[ids[x]][type]){
mapPoint = hotkeys.triggersMap[ids[x]][type];
//break;
//}
//}
//find by: id.type.combi.options
//if (mapPoint){
var trigger;
// event type is associated with the hkId
if(!shift && !ctrl && !alt && !command) { // No Modifiers
if(event.data.combi != special && event.data.combi != character) continue; //Prevent wrong combis from firing --BM
trigger = mapPoint[special] || (character && mapPoint[character]);
}
else{
// check combinations (alt|ctrl|shift+anything)
var modif = '';
if(command) modif +='cmd+';
if(alt) modif +='alt+';
if(ctrl) modif+= 'ctrl+';
if(shift) modif += 'shift+';
// modifiers + special keys or modifiers + character or modifiers + shift character or just shift character
if(event.data.combi != modif+special
&& event.data.combi != modif+character
&& event.data.combi != modif+hotkeys.shiftNums[character]
&& event.data.combi != 'shift+' + +hotkeys.shiftNums[character])
continue; //Prevent wrong combi from firing --BM
trigger = mapPoint[modif+special];
if (!trigger){
if (character){
trigger = mapPoint[modif+character]
|| mapPoint[modif+hotkeys.shiftNums[character]]
// '$' can be triggered as 'Shift+4' or 'Shift+$' or just '$'
|| (modif === 'shift+' && mapPoint[hotkeys.shiftNums[character]]);
}
}
}
if (trigger){
var result = false;
for (var y=0; y < trigger.length; y++){
if(trigger[y].disableInInput){
// double check event.currentTarget and event.target
var elem = jQuery(event.target);
if (jTarget.is("input") || jTarget.is("textarea") || jTarget.is("select")
|| elem.is("input") || elem.is("textarea") || elem.is("select")) {
return true;
}
}
// call the registered callback function
window.console && console.log("id:", ids[x], "; target:", jTarget);
result = result || trigger[y].cb.apply(this, [event]);
}
//return result;
}
} //matches if(hotkKeys.triggersMap... --BM
} //matches for (var x=0; x < ids.length; x++){ --BM
// }
}
};
// place it under window so it can be extended and overridden by others
window.hotkeys = hotkeys;
return jQuery;
})(jQuery);