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