Skip to content
Browse files

jQuery Hotkeys rewritten to work with jQuery 1.4.2. A bunch of old co…

…de stripped and bugs fixed.
  • Loading branch information...
1 parent 0042fad commit 0451de18d57d3401bd4cc021facbe5fd63b5aae6 @jeresig jeresig committed
Showing with 120 additions and 323 deletions.
  1. +16 −64 README.md
  2. +92 −243 jquery.hotkeys.js
  3. +11 −15 test-static-02.html
  4. +1 −1 test-static-04.html
View
80 README.md
@@ -1,47 +1,32 @@
#About
-**jQuery.hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.
+**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.
-It is based on a library [Shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/shortcut.js) written by [Binny V A](http://www.openjs.com/).
+This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](http://github.com/tzuryby/hotkeys)
The syntax is as follows:
-<pre>
-$(expression).bind(<types>,<options>, <handler>);
-$(expression).unbind(<types>,<options>, <handler>);
-$(document).bind('keydown', 'Ctrl+a', fn);
-
-// e.g. replace '$' sign with 'EUR'
-$('input.foo').bind('keyup', '$', function(){
- this.value = this.value.replace('$', 'EUR');
-});
-
-$('div.foo').unbind('keydown', 'Ctrl+a', fn);
-</pre>
-## [Live Demo](http://jshotkeys.googlepages.com/test-static-01.html)
+ $(expression).bind(types, keys, handler);
+ $(expression).unbind(types, handler);
+
+ $(document).bind('keydown', 'ctrl+a', fn);
+
+ // e.g. replace '$' sign with 'EUR'
+ $('input.foo').bind('keyup', '$', function(){
+ this.value = this.value.replace('$', 'EUR');
+ });
## Types
Supported types are `'keydown'`, `'keyup'` and `'keypress'`
-## Options
-The options are `'combi'` i.e. the key combination, and `'disableInInput'` which allow your code not to be executed when the cursor is located inside an input ( `$(elem).is('input') || $(elem).is('textarea')` ).
-
-As you can see, the key combination can be passed as string or as an object. You may pass an object in case you wish to override the default option for `disableInInput` which is set to `false`:
-<pre>
-$(document).bind('keydown', {combi:'a', disableinInput: true}, fn);
-</pre>
-I.e. when cursor is within an input field, `'a'` will be inserted into the input field without interfering.
+## Notes
If you want to use more than one modifiers (e.g. alt+ctrl+z) you should define them by an alphabetical order e.g. alt+ctrl+shift
-Modifiers are case insensitive, i.e. 'Ctrl+a' 'ctrl+a'.
-
-## Handler
-In previous versions there was an option propagate which is removed now and implemented at the user code level.
-
-When using jQuery, if an event handler returns false, jQuery will call `stopPropagation()` and `preventDefault()`
+Hotkeys aren't tracked if you're inside of an input element (unless you explicitly bind the hotkey directly to the input). This helps to avoid conflict with normal user typing.
## jQuery Compatibility
-Tested with *jQuery 1.2.6*
+
+Works with jQuery 1.4.2 and newer.
It known to be working with all the major browsers on all available platforms (Win/Mac/Linux)
@@ -51,43 +36,10 @@ It known to be working with all the major browsers on all available platforms (W
* Safari-3
* Chrome-0.2
-## Features added in this version (0.7.x)
- * Implemented as $.fn - let you use `this`.
- * jQuery selectors are supported.
- * Extending `$.fn.bind` and `$.fn.unbind` so you get a single interface for binding events to handlers
-
-## Overriding jQuery
-The plugin wraps the following jQuery methods:
- * $.fn.bind
- * $.fn.unbind
- * $.find
-
-Even though the plugin overrides these methods, the original methods will *always* be called.
-
-The plugin will add functionality only for the `keydown`, `keyup` and `keypress` event types. Any other types are passed untouched to the original `'bind()'` and `'unbind()'` methods.
-
-Moreover, if you call `bind()` without passing the shortcut key combination e.g. `$(document).bind('keydown', fn)` only the original `'bind()'` method will be executed.
-
-I also modified the `$.fn.find` method by adding a single line at the top of the function body. here is the code:
-
-<pre>
- jQuery.fn.find = function( selector ) {
- // the line I added
- this.query=selector;
- // call jQuery original find
- return jQuery.fn.__find__.apply(this, arguments);
- };
-</pre>
-
-You can read about this at [jQuery's User Group](http://groups.google.com/group/jquery-en/browse_thread/thread/18f9825e8d22f18d)
-
-###Notes
+### Addendum
Firefox is the most liberal one in the manner of letting you capture all short-cuts even those that are built-in in the browser such as `Ctrl-t` for new tab, or `Ctrl-a` for selecting all text. You can always bubble them up to the browser by returning `true` in your handler.
Others, (IE) either let you handle built-in short-cuts, but will add their functionality after your code has executed. Or (Opera/Safari) will *not* pass those events to the DOM at all.
*So, if you bind `Ctrl-Q` or `Alt-F4` and your Safari/Opera window is closed don't be surprised.*
-
-
-###Current Version is: beta 0.7
View
335 jquery.hotkeys.js
@@ -1,250 +1,99 @@
/*
-(c) Copyrights 2007 - 2008
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+*/
-Original idea by by Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
-
-jQuery Plugin by Tzury Bar Yochay
-tzury.by@gmail.com
-http://evalinux.wordpress.com
-http://facebook.com/profile.php?id=513676303
+(function(jQuery){
+
+ jQuery.hotkeys = {
+ version: "0.8",
-Project's sites:
-http://code.google.com/p/js-hotkeys/
-http://github.com/tzuryby/hotkeys/tree/master
+ specialKeys: {
+ 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+ 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+ 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
+ 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 : "/",
+ 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
+ 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+ },
+
+ shiftNums: {
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+ ".": ">", "/": "?", "\\": "|"
+ }
+ };
-License: same as jQuery license.
+ function keyHandler( handleObj ) {
+ // Only care when a possible input has been specified
+ if ( typeof handleObj.data !== "string" ) {
+ return;
+ }
+
+ var origHandler = handleObj.handler,
+ keys = handleObj.data.toLowerCase().split(" ");
+
+ handleObj.handler = function( event ) {
+ // Don't fire in text-accepting inputs that we didn't directly bind to
+ if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+ event.target.type === "text") ) {
+ return;
+ }
+
+ // Keypress represents characters, not special keys
+ var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+ character = String.fromCharCode( event.which ).toLowerCase(),
+ key, modif = "", possible = {};
-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() {});
-
-Note:
- This plugin wraps the following jQuery methods: $.fn.find, $.fn.bind and $.fn.unbind
-*/
+ // check combinations (alt|ctrl|shift+anything)
+ if ( event.altKey && special !== "alt" ) {
+ modif += "alt+";
+ }
-(function (jQuery){
- // keep reference to the original $.fn.bind, $.fn.unbind and $.fn.find
- if (jQuery.fn.__bind__ === undefined){
- jQuery.fn.__bind__ = jQuery.fn.bind;
- }
- if (jQuery.fn.__unbind__ === undefined){
- jQuery.fn.__unbind__ = jQuery.fn.unbind;
- }
- if (jQuery.fn.__find__ === undefined){
- jQuery.fn.__find__ = jQuery.fn.find;
- }
-
- var hotkeys = {
- version: '0.7.9',
- override: /keypress|keydown|keyup/g,
- triggersMap: {},
-
- specialKeys: { 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: '/'},
-
- shiftNums: { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&",
- "8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<",
- ".":">", "/":"?", "\\":"|" },
-
- newTrigger: function (type, combi, callback) {
- // i.e. {'keyup': {'ctrl': {cb: callback, disableInInput: false}}}
- var result = {};
- result[type] = {};
- result[type][combi] = {cb: callback, disableInInput: false, shortcut:combi};
- 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++){
- delete hotkeys.triggersMap[selectorId][hkTypes[x]][combi];
- }
- }
- // 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);
-
- // element already associated with another collection
- if (jqElem.attr('hkId') && jqElem.attr('hkId') !== selectorId){
- selectorId = jqElem.attr('hkId') + ";" + selectorId;
- }
- jqElem.attr('hkId', selectorId);
- });
- 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(),
- 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.altKey,
- mapPoint = null;
+ if ( event.ctrlKey && special !== "ctrl" ) {
+ modif += "ctrl+";
+ }
+
+ // TODO: Need to make sure this works consistently across platforms
+ if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+ modif += "meta+";
+ }
+
+ if ( event.shiftKey && special !== "shift" ) {
+ modif += "shift+";
+ }
+
+ if ( special ) {
+ possible[ modif + special ] = true;
+
+ } else {
+ possible[ modif + character ] = true;
+ possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+ if ( modif === "shift+" ) {
+ possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+ }
+ }
+
+ for ( var i = 0, l = keys.length; i < l; i++ ) {
+ if ( possible[ keys[i] ] ) {
+ return origHandler.apply( this, arguments );
+ }
+ }
+ };
+ }
+
+ jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+ jQuery.event.special[ this ] = { add: keyHandler };
+ });
- 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) { // No Modifiers
- trigger = mapPoint[special] || (character && mapPoint[character]);
- }
- else{
- // check combinations (alt|ctrl|shift+anything)
- var modif = '';
- 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
- 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 x=0; x < trigger.length; x++){
- if(trigger[x].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
- result = result || trigger[x].cb.apply(this, [event]);
- }
- return result;
- }
- }
- }
- };
- // place it under window so it can be extended and overridden by others
- window.hotkeys = hotkeys;
- return jQuery;
-})(jQuery);
+})( jQuery );
View
26 test-static-02.html
@@ -48,32 +48,28 @@
$(document).ready(function(){
$(document).bind('keydown', 'ctrl+l', function(){$('#input_01')[0].focus();})
.bind('keydown', 'shift+#', function(){$('#input_01')[0].value = "Shift#";})
- .bind('keyup', 'a', function(event){
- var v = $('#input_01')[0].value;
- v = v.replace("a", "b");
- v = $('#input_01')[0].value = v;
-
- })
//.bind('keyup', function () { alert (arguments); })
.bind('click', function (event){
if (event.target == $('html')[0]){
alert("save the planet, don't waste energy over meaningless clicking");
}
});
- $('input.foo').bind(
- 'keydown', {combi:'ctrl+k', disableInInput: false}, function(event){
- log('binding keydown/ctrl+k to <b>input</b> applied on <b>#' + event.target.id + '</b>');
- event.stopPropagation();
- event.preventDefault();
- }
- );
+
+ $('#input_01').bind('keyup', 'a', function(event){
+ this.value = this.value.replace(/a/g, "b");
+ });
+
+ $('input.foo').bind('keydown', 'ctrl+k', function(event){
+ log('binding keydown/ctrl+k to <b>input</b> applied on <b>#' + event.target.id + '</b>');
+ return false;
+ });
+
$('table').bind('keydown click keyup', 'ctrl+l', clickHandler);
});
function clickHandler(event){
log('binding ' + event.type + ' with(ctrl+l) to <b>table</b> applied on <b>#' + event.target.id + '</b>');
- event.stopPropagation();
- event.preventDefault();
+ return false;
}
function unbindClick(){
View
2 test-static-04.html
@@ -16,7 +16,7 @@
alert("Hello Slash");
return false;
});
- jQuery(document).bind('keydown', 'ctrl+p', function (evt){
+ jQuery(document).bind('keydown', 'ctrl+p meta+p', function (evt){
alert("think green-don't print");
return false;
});

0 comments on commit 0451de1

Please sign in to comment.
Something went wrong with that request. Please try again.