key.js revision cc993d3cace28dec43cfc6f55b41961e90e76ba9
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp/**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Functionality to listen for one or more specific key combinations.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @module event
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @submodule event-key
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp */
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTrippvar ALT = "+alt",
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp CTRL = "+ctrl",
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp META = "+meta",
a89ad754cce3cfc8aee71760e10217b54020360dTripp SHIFT = "+shift",
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp trim = Y.Lang.trim,
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp eventDef = {
a89ad754cce3cfc8aee71760e10217b54020360dTripp KEY_MAP: {
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp enter : 13,
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp esc : 27,
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp backspace: 8,
c093c1aed867e18aa4778708592e1ceb45d18cffTripp tab : 9,
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp pageup : 33,
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp pagedown : 34
a89ad754cce3cfc8aee71760e10217b54020360dTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp _typeRE: /^(up|down|press):/,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp _keysRE: /^(?:up|down|press):|\+(alt|ctrl|meta|shift)/g,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c093c1aed867e18aa4778708592e1ceb45d18cffTripp processArgs: function (args) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp var spec = args.splice(3,1)[0],
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp mods = Y.Array.hash(spec.match(/\+(?:alt|ctrl|meta|shift)\b/g) || []),
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp config = {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp type: this._typeRE.test(spec) ? RegExp.$1 : null,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp mods: mods,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp keys: null
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp // strip type and modifiers from spec, leaving only keyCodes
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp bits = spec.replace(this._keysRE, ''),
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp chr, uc, lc, i;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp if (bits) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp bits = bits.split(',');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp config.keys = {};
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp // FIXME: need to support '65,esc' => keypress, keydown
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp for (i = bits.length - 1; i >= 0; --i) {
a89ad754cce3cfc8aee71760e10217b54020360dTripp chr = trim(bits[i]);
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp // catch sloppy filters, trailing commas, etc 'a,,'
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (!chr) {
c093c1aed867e18aa4778708592e1ceb45d18cffTripp continue;
a89ad754cce3cfc8aee71760e10217b54020360dTripp }
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp // non-numerics are single characters or key names
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (+chr == chr) {
a89ad754cce3cfc8aee71760e10217b54020360dTripp config.keys[chr] = mods;
a89ad754cce3cfc8aee71760e10217b54020360dTripp } else {
a89ad754cce3cfc8aee71760e10217b54020360dTripp lc = chr.toLowerCase();
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (this.KEY_MAP[lc]) {
a89ad754cce3cfc8aee71760e10217b54020360dTripp config.keys[this.KEY_MAP[lc]] = mods;
a89ad754cce3cfc8aee71760e10217b54020360dTripp // FIXME: '65,enter' defaults keydown for both
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (!config.type) {
a89ad754cce3cfc8aee71760e10217b54020360dTripp config.type = "down"; // safest
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp }
a89ad754cce3cfc8aee71760e10217b54020360dTripp } else {
c093c1aed867e18aa4778708592e1ceb45d18cffTripp uc = chr.charAt(0).toUpperCase();
a89ad754cce3cfc8aee71760e10217b54020360dTripp lc = lc.charAt(0);
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp // FIXME: possibly stupid assumption that
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp // the keycode of the lower case == the
a89ad754cce3cfc8aee71760e10217b54020360dTripp // charcode of the upper case
a89ad754cce3cfc8aee71760e10217b54020360dTripp // a (key:65,char:97), A (key:65,char:65)
a89ad754cce3cfc8aee71760e10217b54020360dTripp config.keys[uc.charCodeAt(0)] =
a89ad754cce3cfc8aee71760e10217b54020360dTripp (lc !== uc && chr === uc) ?
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp // upper case chars get +shift free
a89ad754cce3cfc8aee71760e10217b54020360dTripp Y.merge(mods, { "+shift": true }) :
a89ad754cce3cfc8aee71760e10217b54020360dTripp mods;
a89ad754cce3cfc8aee71760e10217b54020360dTripp }
a89ad754cce3cfc8aee71760e10217b54020360dTripp }
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp }
a89ad754cce3cfc8aee71760e10217b54020360dTripp }
c093c1aed867e18aa4778708592e1ceb45d18cffTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (!config.type) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp config.type = "press";
a89ad754cce3cfc8aee71760e10217b54020360dTripp }
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp return config;
a89ad754cce3cfc8aee71760e10217b54020360dTripp },
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp on: function (node, sub, notifier, filter) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp var spec = sub._extra,
a89ad754cce3cfc8aee71760e10217b54020360dTripp type = "key" + spec.type,
a89ad754cce3cfc8aee71760e10217b54020360dTripp keys = spec.keys,
a89ad754cce3cfc8aee71760e10217b54020360dTripp method = (filter) ? "delegate" : "on";
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp // Note: without specifying any keyCodes, this becomes a
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp // horribly inefficient alias for 'keydown' (et al), but I
c093c1aed867e18aa4778708592e1ceb45d18cffTripp // can't abort this subscription for a simple
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp // Y.on('keypress', ...);
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp // Please use keyCodes or just subscribe directly to keydown,
a89ad754cce3cfc8aee71760e10217b54020360dTripp // keyup, or keypress
a89ad754cce3cfc8aee71760e10217b54020360dTripp sub._detach = node[method](type, function (e) {
a89ad754cce3cfc8aee71760e10217b54020360dTripp var key = keys ? keys[e.keyCode] : spec.mods;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (key &&
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp (!key[ALT] || (key[ALT] && e.altKey)) &&
a89ad754cce3cfc8aee71760e10217b54020360dTripp (!key[CTRL] || (key[CTRL] && e.ctrlKey)) &&
a89ad754cce3cfc8aee71760e10217b54020360dTripp (!key[META] || (key[META] && e.metaKey)) &&
a89ad754cce3cfc8aee71760e10217b54020360dTripp (!key[SHIFT] || (key[SHIFT] && e.shiftKey)))
a89ad754cce3cfc8aee71760e10217b54020360dTripp {
a89ad754cce3cfc8aee71760e10217b54020360dTripp notifier.fire(e);
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp }
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp }, filter);
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp },
c093c1aed867e18aa4778708592e1ceb45d18cffTripp
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp detach: function (node, sub, notifier) {
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp sub._detach.detach();
a89ad754cce3cfc8aee71760e10217b54020360dTripp }
a89ad754cce3cfc8aee71760e10217b54020360dTripp };
a89ad754cce3cfc8aee71760e10217b54020360dTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTrippeventDef.delegate = eventDef.on;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTrippeventDef.detachDelegate = eventDef.detach;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp/**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>Add a key listener. The listener will only be notified if the
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * keystroke detected meets the supplied specification. The
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * specification is a string that is defined as:</p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>spec</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd><code>[{type}:]{code}[,{code}]*</code></dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>type</dt>
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * <dd><code>"down", "up", or "press"</code></dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>code</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd><code>{keyCode|character|keyName}[+{modifier}]*</code></dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>modifier</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd><code>"shift", "ctrl", "alt", or "meta"</code></dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>keyName</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd><code>"enter", "backspace", "esc", "tab", "pageup", or "pagedown"</code></dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
bf801d6851ecf7ed14742ef3639a077daecb5cf8Tripp * <p>Examples:</p>
a4d2446149b07f9e5c32947091dcbcf4d2eee765Tripp * <ul>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <li><code>Y.on("key", callback, "press:12,65+shift+ctrl", "#my-input");</code></li>
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * <li><code>Y.delegate("key", preventSubmit, "enter", "#forms", "input[type=text]");</code></li>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <li><code>Y.one("doc").on("key", viNav, "j,k,l,;");</code></li>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </ul>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @event key
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @for YUI
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param type {string} 'key'
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param fn {function} the function to execute
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param id {string|HTMLElement|collection} the element(s) to bind
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param spec {string} the keyCode and modifier specification
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param o optional context object
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param args 0..n additional arguments to provide to the listener.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @return {Event.Handle} the detach handle
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTrippY.Event.define('key', eventDef, true);
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp