Skip to content

Instantly share code, notes, and snippets.

@alexeypegov
Created April 14, 2012 09:28
Show Gist options
  • Save alexeypegov/2383139 to your computer and use it in GitHub Desktop.
Save alexeypegov/2383139 to your computer and use it in GitHub Desktop.
jquery human keyboard handling
// usage: $.initinput(); $.inputmap("pressed LEFT", function() { alert('pressed LEFT') }, null);
// or: $('div').inputmap("pressed ESCAPE", function() { alert('pressed ESCAPE') });
// with $.install_esc_handler() you popups will be closed by ESCAPE. Popups are elements with 'popup' class and 'z-index' > 0
$.extend({
keyCodes:{8:'BACKSPACE', 9:'TAB', 13:'ENTER', 16:'SHIFT', 17:'CONTROL', 18:'ALT', 27:'ESCAPE',
33:'PG_UP', 34:'PG_DN', 35:'END', 36:'HOME', 37:'LEFT', 38:'UP', 39:'RIGHT', 40:'DOWN',
46:'DELETE', 91:'META', 0:'UNKNOWN'},
repeatKeys:[ 37, 38, 39, 40 ],
inputmap:function (stroke, callback, parentDisposable) {
var H = $('html');
$._updatemap(H, stroke, callback, parentDisposable)
},
_updatemap:function (element, stroke, callback, parentDisposable) {
if (parentDisposable === undefined) {
throw "Warning: parentDisposable should be an element or null!";
}
var handlers = element.data(stroke);
if (!handlers) {
handlers = [];
element.data(stroke, handlers);
}
handlers.push(callback);
if (typeof(parentDisposable) == 'object' && parentDisposable != null) {
parentDisposable.bind('disposed', function() {
var handlers = element.data(stroke);
if (handlers) {
var ndx = handlers.indexOf(callback);
if (ndx >= 0) {
handlers.splice(ndx, 1);
element.data(stroke, handlers);
}
}
});
}
},
_swap:function (e, v) {
return $.inArray(e.keyCode, $.repeatKeys) != -1 && ($.browser.opera || $.browser.mozilla)
&& e.type == v ? (v == "keydown" ? "typed" : "pressed") : (v == "keydown" ? "pressed" : "typed");
},
stroke:function (e) {
var stroke = $.modifier(e);
var mouse_event = e.type == "click";
switch (e.type) {
case "keydown":
stroke += $._swap(e, "keydown");
break;
case "keyup":
stroke += "released";
break;
case "keypress":
stroke += $._swap(e, "keypress");
break;
case "click":
stroke += "clicked";
break;
default:
stroke += e.type;
break;
}
if (mouse_event) {
stroke += " LEFT"; // other buttons?
} else {
var keycode = $.keyCodes[e.keyCode];
if (keycode === undefined) keycode = String.fromCharCode(e.keyCode);
stroke += " " + ($.inArray(e.charCode, [0, 13]) != -1 || e.charCode === undefined ? keycode : String.fromCharCode(e.charCode));
}
if (false) {
console.log(stroke + " ( " + e.charCode + " / " + e.keyCode + " )")
}
return stroke;
},
modifier:function (e) {
if (e.shiftKey && e.charCode != 0 && !e.altKey && !e.ctrlKey) return "";
var S = "";
if (e.altKey) S += "ALT ";
if (e.shiftKey) S += "SHIFT ";
if (e.ctrlKey) S += "CONTROL ";
else if (e.metaKey) S += "META "; // meta is triggered with control on macs too!
return S;
},
event_handler:function (e) {
var result = true;
var S = $.stroke(e);
var P = $(e.target);
if (e.type != "click") {
var focused = $(':focus');
if (focused.length == 1) {
P = focused;
}
}
var popups = $(':popup(:visible)');
if (popups.length > 0) {
var sorted = popups.sort(function (a, b) {
var az = a.normz();
var bz = b.normz();
return az == bz ? 0 : az > bz ? 1 : -1;
});
P = $(sorted[0]);
}
var original = P;
while (P.length == 1) {
var handlers = P.data(S);
if (handlers) {
for (var j = 0; j < handlers.length; j++) {
var R = handlers[j].call(original);
if (R === undefined) {
throw "Handler should return true (if handled) or false otherwise!";
}
if (R) { // will also stop if no return value
result = false;
break;
}
}
}
if (!result) break;
if (e.type == "keypress" && ("TEXTAREA" == P.get(0).tagName || "INPUT" == P.get(0).tagName)) break; // skip if textarea
P = P.parent();
}
return result
},
init_input:function () {
$(document).click($.event_handler);
$(document).keydown($.event_handler);
$(document).keypress($.event_handler)
},
install_esc_handler:function () {
$.inputmap('pressed ESCAPE', function () {
var P = $(':popup(:visible)');
$.merge(P, $(':dialog(:visible)'));
var z = 0;
var to_close = undefined;
P.each(function () {
var $this = $(this);
var z2 = $this.css('z-index');
var n = isNaN(z2);
if ($this.is(':not(:busy)') && (!n && z2 > z) || (n && z == 0)) { // 'auto' values should also be processed
z = n ? 0 : z2;
to_close = $this;
}
});
if (to_close) {
to_close.find(':input').blur();
to_close.hide().doOnHide();
return true;
}
return false;
}, null);
}
});
$.fn.extend({
inputmap:function (stroke, callback) {
$._updatemap(this, stroke, callback, null)
},
normz:function () {
var z = $(this).css('z-index');
return isNaN(z) ? 0 : parseInt(z);
},
onHide:function (fun) {
$(this).data('onHide', fun);
return $(this)
},
doOnHide:function () {
var $this = $(this);
var cb = $this.data('onHide');
if (cb) cb($this);
},
dismiss:function() {
var $this = $(this);
$this.doOnHide();
$this.hide();
}
});
$.extend($.expr[':'], {
popup:function (a, i, m) {
var $this = $(a);
return $this.hasClass('popup') && (!m[3] || $this.is(m[3]));
},
dialog:function (a, i, m) {
var $this = $(a);
return $this.hasClass('dialog') && (!m[3] || $this.is(m[3]));
},
busy:function (a, i, m) {
var $this = $(a);
return $this.hasClass('busy') && (!m[3] || $this.is(m[3]));
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment