Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit 0451de18d57d3401bd4cc021facbe5fd63b5aae6 1 parent 0042fad
@jeresig authored
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") ) {

"Hotkeys aren't tracked if you're inside of an input element"

The function still executes when inside a input type 'password', as that isn't checked for.

|| event.target.type === "password"

Solves the problem. Great script.

@nono
nono added a note

Same for HTML5 input types like url and email. See http://github.com/nono/jquery.hotkeys/commit/5daa9af361a7febb2de832008e74a3323a4c060e for a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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 = {};

key variable is unused

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
-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 );

John, I noticed you sometimes add white-space to parameters (like this one), I always wondered why many programmers do this, and why only at some places.
what is the general reason? does it make it more readable if so is there any pattern for when to add white-space?

Thanks.

@adardesign

It's per the jQuery Core Style Guidelines seen here: http://docs.jquery.com/JQuery_Core_Style_Guidelines#Spacing

...or at least that's why I do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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;
});

3 comments on commit 0451de1

@linjuming

can not unbind hotkeys , please fix this bug

@mathiasbynens

Great plugin!

I wrote a quick mini-addon to this plugin to easily bind ‘function keys’ based on the current platform, i.e. Ctrl + [key] on Windows and Cmd + [key] on Mac.

var isMac = /Mac/.test(navigator.platform);
$.fn.fnKeyBind = function(key, fn) {
 $(this).bind('keydown', (isMac ? 'meta' : 'ctrl') + '+' + key, fn);
 return this;
};

It can be used as follows:

$('textarea')
 .fnKeyBind('b', doBold)
 .fnKeyBind('i', doItalic)
 .fnKeyBind('k', doCode);

Perhaps something like this could be implemented in the main plugin?

@ryanpaulfyfe

"Hotkeys aren't tracked if you're inside of an input element"

The function still executes when inside a input type 'password', as that isn't checked for.

|| event.target.type === "password"

Solves the problem. Great script.

@nono

Same for HTML5 input types like url and email. See http://github.com/nono/jquery.hotkeys/commit/5daa9af361a7febb2de832008e74a3323a4c060e for a solution.

@adardesign

John, I noticed you sometimes add white-space to parameters (like this one), I always wondered why many programmers do this, and why only at some places.
what is the general reason? does it make it more readable if so is there any pattern for when to add white-space?

Thanks.

@JakeWharton

@adardesign

It's per the jQuery Core Style Guidelines seen here: http://docs.jquery.com/JQuery_Core_Style_Guidelines#Spacing

...or at least that's why I do it.

@camilonova

Mathias is a great idea, try to fork, patch your code and make a pull request.

@gabrielmoreira

key variable is unused

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