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