1 |
efrain |
1 |
YUI.add('event-key', function (Y, NAME) {
|
|
|
2 |
|
|
|
3 |
/**
|
|
|
4 |
* Functionality to listen for one or more specific key combinations.
|
|
|
5 |
* @module event
|
|
|
6 |
* @submodule event-key
|
|
|
7 |
*/
|
|
|
8 |
|
|
|
9 |
var ALT = "+alt",
|
|
|
10 |
CTRL = "+ctrl",
|
|
|
11 |
META = "+meta",
|
|
|
12 |
SHIFT = "+shift",
|
|
|
13 |
|
|
|
14 |
trim = Y.Lang.trim,
|
|
|
15 |
|
|
|
16 |
eventDef = {
|
|
|
17 |
KEY_MAP: {
|
|
|
18 |
enter : 13,
|
|
|
19 |
space : 32,
|
|
|
20 |
esc : 27,
|
|
|
21 |
backspace: 8,
|
|
|
22 |
tab : 9,
|
|
|
23 |
pageup : 33,
|
|
|
24 |
pagedown : 34
|
|
|
25 |
},
|
|
|
26 |
|
|
|
27 |
_typeRE: /^(up|down|press):/,
|
|
|
28 |
_keysRE: /^(?:up|down|press):|\+(alt|ctrl|meta|shift)/g,
|
|
|
29 |
|
|
|
30 |
processArgs: function (args) {
|
|
|
31 |
var spec = args.splice(3,1)[0],
|
|
|
32 |
mods = Y.Array.hash(spec.match(/\+(?:alt|ctrl|meta|shift)\b/g) || []),
|
|
|
33 |
config = {
|
|
|
34 |
type: this._typeRE.test(spec) ? RegExp.$1 : null,
|
|
|
35 |
mods: mods,
|
|
|
36 |
keys: null
|
|
|
37 |
},
|
|
|
38 |
// strip type and modifiers from spec, leaving only keyCodes
|
|
|
39 |
bits = spec.replace(this._keysRE, ''),
|
|
|
40 |
chr, uc, lc, i;
|
|
|
41 |
|
|
|
42 |
if (bits) {
|
|
|
43 |
bits = bits.split(',');
|
|
|
44 |
|
|
|
45 |
config.keys = {};
|
|
|
46 |
|
|
|
47 |
// FIXME: need to support '65,esc' => keypress, keydown
|
|
|
48 |
for (i = bits.length - 1; i >= 0; --i) {
|
|
|
49 |
chr = trim(bits[i]);
|
|
|
50 |
|
|
|
51 |
// catch sloppy filters, trailing commas, etc 'a,,'
|
|
|
52 |
if (!chr) {
|
|
|
53 |
continue;
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
// non-numerics are single characters or key names
|
|
|
57 |
if (+chr == chr) {
|
|
|
58 |
config.keys[chr] = mods;
|
|
|
59 |
} else {
|
|
|
60 |
lc = chr.toLowerCase();
|
|
|
61 |
|
|
|
62 |
if (this.KEY_MAP[lc]) {
|
|
|
63 |
config.keys[this.KEY_MAP[lc]] = mods;
|
|
|
64 |
// FIXME: '65,enter' defaults keydown for both
|
|
|
65 |
if (!config.type) {
|
|
|
66 |
config.type = "down"; // safest
|
|
|
67 |
}
|
|
|
68 |
} else {
|
|
|
69 |
// FIXME: Character mapping only works for keypress
|
|
|
70 |
// events. Otherwise, it uses String.fromCharCode()
|
|
|
71 |
// from the keyCode, which is wrong.
|
|
|
72 |
chr = chr.charAt(0);
|
|
|
73 |
uc = chr.toUpperCase();
|
|
|
74 |
|
|
|
75 |
if (mods["+shift"]) {
|
|
|
76 |
chr = uc;
|
|
|
77 |
}
|
|
|
78 |
|
|
|
79 |
// FIXME: stupid assumption that
|
|
|
80 |
// the keycode of the lower case == the
|
|
|
81 |
// charCode of the upper case
|
|
|
82 |
// a (key:65,char:97), A (key:65,char:65)
|
|
|
83 |
config.keys[chr.charCodeAt(0)] =
|
|
|
84 |
(chr === uc) ?
|
|
|
85 |
// upper case chars get +shift free
|
|
|
86 |
Y.merge(mods, { "+shift": true }) :
|
|
|
87 |
mods;
|
|
|
88 |
}
|
|
|
89 |
}
|
|
|
90 |
}
|
|
|
91 |
}
|
|
|
92 |
|
|
|
93 |
if (!config.type) {
|
|
|
94 |
config.type = "press";
|
|
|
95 |
}
|
|
|
96 |
|
|
|
97 |
return config;
|
|
|
98 |
},
|
|
|
99 |
|
|
|
100 |
on: function (node, sub, notifier, filter) {
|
|
|
101 |
var spec = sub._extra,
|
|
|
102 |
type = "key" + spec.type,
|
|
|
103 |
keys = spec.keys,
|
|
|
104 |
method = (filter) ? "delegate" : "on";
|
|
|
105 |
|
|
|
106 |
// Note: without specifying any keyCodes, this becomes a
|
|
|
107 |
// horribly inefficient alias for 'keydown' (et al), but I
|
|
|
108 |
// can't abort this subscription for a simple
|
|
|
109 |
// Y.on('keypress', ...);
|
|
|
110 |
// Please use keyCodes or just subscribe directly to keydown,
|
|
|
111 |
// keyup, or keypress
|
|
|
112 |
sub._detach = node[method](type, function (e) {
|
|
|
113 |
var key = keys ? keys[e.which] : spec.mods;
|
|
|
114 |
|
|
|
115 |
if (key &&
|
|
|
116 |
(!key[ALT] || (key[ALT] && e.altKey)) &&
|
|
|
117 |
(!key[CTRL] || (key[CTRL] && e.ctrlKey)) &&
|
|
|
118 |
(!key[META] || (key[META] && e.metaKey)) &&
|
|
|
119 |
(!key[SHIFT] || (key[SHIFT] && e.shiftKey)))
|
|
|
120 |
{
|
|
|
121 |
notifier.fire(e);
|
|
|
122 |
}
|
|
|
123 |
}, filter);
|
|
|
124 |
},
|
|
|
125 |
|
|
|
126 |
detach: function (node, sub, notifier) {
|
|
|
127 |
sub._detach.detach();
|
|
|
128 |
}
|
|
|
129 |
};
|
|
|
130 |
|
|
|
131 |
eventDef.delegate = eventDef.on;
|
|
|
132 |
eventDef.detachDelegate = eventDef.detach;
|
|
|
133 |
|
|
|
134 |
/**
|
|
|
135 |
* <p>Add a key listener. The listener will only be notified if the
|
|
|
136 |
* keystroke detected meets the supplied specification. The
|
|
|
137 |
* specification is a string that is defined as:</p>
|
|
|
138 |
*
|
|
|
139 |
* <dl>
|
|
|
140 |
* <dt>spec</dt>
|
|
|
141 |
* <dd><code>[{type}:]{code}[,{code}]*</code></dd>
|
|
|
142 |
* <dt>type</dt>
|
|
|
143 |
* <dd><code>"down", "up", or "press"</code></dd>
|
|
|
144 |
* <dt>code</dt>
|
|
|
145 |
* <dd><code>{keyCode|character|keyName}[+{modifier}]*</code></dd>
|
|
|
146 |
* <dt>modifier</dt>
|
|
|
147 |
* <dd><code>"shift", "ctrl", "alt", or "meta"</code></dd>
|
|
|
148 |
* <dt>keyName</dt>
|
|
|
149 |
* <dd><code>"enter", "space", "backspace", "esc", "tab", "pageup", or "pagedown"</code></dd>
|
|
|
150 |
* </dl>
|
|
|
151 |
*
|
|
|
152 |
* <p>Examples:</p>
|
|
|
153 |
* <ul>
|
|
|
154 |
* <li><code>Y.on("key", callback, "press:12,65+shift+ctrl", "#my-input");</code></li>
|
|
|
155 |
* <li><code>Y.delegate("key", preventSubmit, "#forms", "enter", "input[type=text]");</code></li>
|
|
|
156 |
* <li><code>Y.one("doc").on("key", viNav, "j,k,l,;");</code></li>
|
|
|
157 |
* </ul>
|
|
|
158 |
*
|
|
|
159 |
* @event key
|
|
|
160 |
* @for YUI
|
|
|
161 |
* @param type {string} 'key'
|
|
|
162 |
* @param fn {function} the function to execute
|
|
|
163 |
* @param id {string|HTMLElement|collection} the element(s) to bind
|
|
|
164 |
* @param spec {string} the keyCode and modifier specification
|
|
|
165 |
* @param o optional context object
|
|
|
166 |
* @param args 0..n additional arguments to provide to the listener.
|
|
|
167 |
* @return {Event.Handle} the detach handle
|
|
|
168 |
*/
|
|
|
169 |
Y.Event.define('key', eventDef, true);
|
|
|
170 |
|
|
|
171 |
|
|
|
172 |
}, '3.18.1', {"requires": ["event-synthetic"]});
|