]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind navigation keys to\r | |
3 | * function calls that will get called when the keys are pressed, providing an easy way to implement custom navigation\r | |
4 | * schemes for any UI component.\r | |
5 | *\r | |
6 | * The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,\r | |
7 | * pageUp, pageDown, del, backspace, home, end.\r | |
8 | *\r | |
9 | * Usage:\r | |
10 | *\r | |
11 | * var nav = new Ext.util.KeyNav({\r | |
12 | * target : "my-element",\r | |
13 | * left : function(e){\r | |
14 | * this.moveLeft(e.ctrlKey);\r | |
15 | * },\r | |
16 | * right : function(e){\r | |
17 | * this.moveRight(e.ctrlKey);\r | |
18 | * },\r | |
19 | * enter : function(e){\r | |
20 | * this.save();\r | |
21 | * },\r | |
22 | * \r | |
23 | * // Binding may be a function specifiying fn, scope and defaultEventAction\r | |
24 | * esc: {\r | |
25 | * fn: this.onEsc,\r | |
26 | * defaultEventAction: false\r | |
27 | * },\r | |
28 | *\r | |
29 | * // Binding may be keyed by a single character\r | |
30 | * A: {\r | |
31 | * ctrl: true,\r | |
32 | * fn: selectAll\r | |
33 | * },\r | |
34 | *\r | |
35 | * // Binding may be keyed by a key code (45 = {@link Ext.event.Event#property-INSERT INSERT})\r | |
36 | * 45: {\r | |
37 | * fn: doInsert\r | |
38 | * }\r | |
39 | * scope : this\r | |
40 | * });\r | |
41 | */\r | |
42 | Ext.define('Ext.util.KeyNav', {\r | |
43 | alternateClassName: 'Ext.KeyNav',\r | |
44 | \r | |
45 | requires: ['Ext.util.KeyMap'],\r | |
46 | \r | |
47 | /**\r | |
48 | * @cfg {Boolean} disabled\r | |
49 | * True to disable this KeyNav instance.\r | |
50 | */\r | |
51 | disabled: false,\r | |
52 | \r | |
53 | /**\r | |
54 | * @cfg {String} [defaultEventAction=false]\r | |
55 | * The method to call on the {@link Ext.event.Event} after this KeyNav intercepts a key.\r | |
56 | * Valid values are {@link Ext.event.Event#stopEvent}, {@link Ext.event.Event#preventDefault}\r | |
57 | * and {@link Ext.event.Event#stopPropagation}.\r | |
58 | *\r | |
59 | * If a falsy value is specified, no method is called on the key event.\r | |
60 | */\r | |
61 | defaultEventAction: false,\r | |
62 | \r | |
63 | /**\r | |
64 | * @cfg {Boolean} forceKeyDown\r | |
65 | * Handle the keydown event instead of keypress. KeyNav automatically does this for IE since IE does not propagate\r | |
66 | * special keys on keypress, but setting this to true will force other browsers to also handle keydown instead of\r | |
67 | * keypress.\r | |
68 | */\r | |
69 | forceKeyDown: false,\r | |
70 | \r | |
71 | /**\r | |
72 | * @cfg {Ext.Component/Ext.dom.Element/HTMLElement/String} target\r | |
73 | * The object on which to listen for the event specified by the {@link #eventName} config option.\r | |
74 | */\r | |
75 | \r | |
76 | /**\r | |
77 | * @cfg {String} eventName\r | |
78 | * The event to listen for to pick up key events.\r | |
79 | */\r | |
80 | eventName: 'keypress',\r | |
81 | \r | |
82 | /**\r | |
83 | * @cfg {Function} processEvent\r | |
84 | * An optional event processor function which accepts the argument list provided by the {@link #eventName configured\r | |
85 | * event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.\r | |
86 | *\r | |
87 | * This may be useful when the {@link #target} is a Component with s complex event signature. Extra information from\r | |
88 | * the event arguments may be injected into the event for use by the handler functions before returning it.\r | |
89 | */\r | |
90 | \r | |
91 | /**\r | |
92 | * @cfg {Object} [processEventScope=this]\r | |
93 | * The scope (`this` context) in which the {@link #processEvent} method is executed.\r | |
94 | */\r | |
95 | \r | |
96 | /**\r | |
97 | * @cfg {Boolean} [ignoreInputFields=false]\r | |
98 | * Configure this as `true` if there are any input fields within the {@link #target}, and this KeyNav\r | |
99 | * should not process events from input fields, (`<input>, <textarea> and elements with `contentEditable="true"`)\r | |
100 | */\r | |
101 | \r | |
102 | /**\r | |
103 | * @cfg {Ext.util.KeyMap} [keyMap]\r | |
104 | * An optional pre-existing {@link Ext.util.KeyMap KeyMap} to use to listen for key events. If not specified,\r | |
105 | * one is created.\r | |
106 | */\r | |
107 | \r | |
108 | /**\r | |
109 | * @property {Ext.event.Event} lastKeyEvent\r | |
110 | * The last key event that this KeyMap handled.\r | |
111 | */\r | |
112 | \r | |
113 | /**\r | |
114 | * @cfg {Number} [priority]\r | |
115 | * The priority to set on this KeyNav's listener. Listeners with a higher priority are fired before those with\r | |
116 | * lower priority.\r | |
117 | */\r | |
118 | \r | |
119 | statics: {\r | |
120 | keyOptions: {\r | |
121 | left: 37,\r | |
122 | right: 39,\r | |
123 | up: 38,\r | |
124 | down: 40,\r | |
125 | space: 32,\r | |
126 | pageUp: 33,\r | |
127 | pageDown: 34,\r | |
128 | del: 46,\r | |
129 | backspace: 8,\r | |
130 | home: 36,\r | |
131 | end: 35,\r | |
132 | enter: 13,\r | |
133 | esc: 27,\r | |
134 | tab: 9\r | |
135 | }\r | |
136 | },\r | |
137 | \r | |
138 | constructor: function(config) {\r | |
139 | var me = this;\r | |
140 | if (arguments.length === 2) {\r | |
141 | me.legacyConstructor.apply(me, arguments);\r | |
142 | return;\r | |
143 | }\r | |
144 | me.doConstruction(config);\r | |
145 | },\r | |
146 | \r | |
147 | /**\r | |
148 | * @private\r | |
149 | * Old constructor signature.\r | |
150 | * @param {String/HTMLElement/Ext.dom.Element} el The element or its ID to bind to\r | |
151 | * @param {Object} config The config\r | |
152 | */\r | |
153 | legacyConstructor: function(el, config) {\r | |
154 | this.doConstruction(Ext.apply({\r | |
155 | target: el\r | |
156 | }, config));\r | |
157 | },\r | |
158 | \r | |
159 | /**\r | |
160 | * Sets up a configuration for the KeyNav.\r | |
161 | * @private\r | |
162 | * @param {Object} config A configuration object as specified in the constructor.\r | |
163 | */\r | |
164 | doConstruction: function(config) {\r | |
165 | var me = this,\r | |
166 | keymapCfg = {\r | |
167 | target: config.target,\r | |
168 | ignoreInputFields: config.ignoreInputFields,\r | |
169 | eventName: me.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : me.forceKeyDown, config.eventName),\r | |
170 | capture: config.capture\r | |
171 | },\r | |
172 | map;\r | |
173 | \r | |
174 | if (me.map) {\r | |
175 | me.map.destroy();\r | |
176 | }\r | |
177 | \r | |
178 | // Ensure config system configs are set\r | |
179 | me.initConfig(config);\r | |
180 | \r | |
181 | if (config.processEvent) {\r | |
182 | keymapCfg.processEvent = config.processEvent;\r | |
183 | keymapCfg.processEventScope = config.processEventScope||me;\r | |
184 | }\r | |
185 | if (config.priority) {\r | |
186 | keymapCfg.priority = config.priority;\r | |
187 | }\r | |
188 | \r | |
189 | // If they specified a KeyMap to use, use it\r | |
190 | if (config.keyMap) {\r | |
191 | map = me.map = config.keyMap;\r | |
192 | }\r | |
193 | // Otherwise, create one, and remember to destroy it on destroy\r | |
194 | else {\r | |
195 | map = me.map = new Ext.util.KeyMap(keymapCfg);\r | |
196 | me.destroyKeyMap = true;\r | |
197 | }\r | |
198 | \r | |
199 | this.addBindings(config);\r | |
200 | \r | |
201 | map.disable();\r | |
202 | if (!config.disabled) {\r | |
203 | map.enable();\r | |
204 | }\r | |
205 | },\r | |
206 | \r | |
207 | addBindings: function(bindings) {\r | |
208 | var me = this,\r | |
209 | keyName,\r | |
210 | binding,\r | |
211 | map = me.map,\r | |
212 | keyCodes = Ext.util.KeyNav.keyOptions,\r | |
213 | keyCode,\r | |
214 | defaultScope = bindings.scope || me;\r | |
215 | \r | |
216 | for (keyName in bindings) {\r | |
217 | binding = bindings[keyName];\r | |
218 | // There is a property named after a key name.\r | |
219 | // It may be a function or an binding spec containing handler, scope and defaultEventAction configs\r | |
220 | // Allow {A: { ctrl: true, handler: onCtrlA }}\r | |
221 | // Allow {45: doInsert} to use key codes directly\r | |
222 | keyCode = keyCodes[keyName];\r | |
223 | if (keyCode != null) {\r | |
224 | keyName = keyCode;\r | |
225 | }\r | |
226 | if (binding && (keyName.length === 1 || !isNaN(keyName = parseInt(keyName, 10)))) {\r | |
227 | if (typeof binding === 'function') {\r | |
228 | binding = {\r | |
229 | handler: binding,\r | |
230 | defaultEventAction: (bindings.defaultEventAction !== undefined) ? bindings.defaultEventAction : me.defaultEventAction\r | |
231 | };\r | |
232 | }\r | |
233 | map.addBinding({\r | |
234 | key: keyName,\r | |
235 | ctrl: binding.ctrl,\r | |
236 | shift: binding.shift,\r | |
237 | alt: binding.alt,\r | |
238 | handler: Ext.Function.bind(me.handleEvent, binding.scope||defaultScope, [binding.handler||binding.fn, me], true),\r | |
239 | defaultEventAction: (binding.defaultEventAction !== undefined) ? binding.defaultEventAction : me.defaultEventAction\r | |
240 | });\r | |
241 | }\r | |
242 | }\r | |
243 | },\r | |
244 | \r | |
245 | /**\r | |
246 | * Method for filtering out the map argument\r | |
247 | * @private\r | |
248 | * @param {Number} keyCode\r | |
249 | * @param {Ext.event.Event} event\r | |
250 | * @param {Function} handler The function to call\r | |
251 | * @param {Ext.util.KeyNav} keyNav The owning KeyNav\r | |
252 | */\r | |
253 | handleEvent: function(keyCode, event, handler, keyNav) {\r | |
254 | keyNav.lastKeyEvent = event;\r | |
255 | return handler.call(this, event);\r | |
256 | },\r | |
257 | \r | |
258 | /**\r | |
259 | * Destroy this KeyNav.\r | |
260 | * @param {Boolean} removeEl Pass `true` to remove the element associated with this KeyNav.\r | |
261 | */\r | |
262 | destroy: function(removeEl) {\r | |
263 | var me = this;\r | |
264 | \r | |
265 | if (me.destroyKeyMap) {\r | |
266 | me.map.destroy(removeEl);\r | |
267 | }\r | |
268 | delete me.map;\r | |
269 | me.callParent();\r | |
270 | },\r | |
271 | \r | |
272 | /**\r | |
273 | * Enables this KeyNav.\r | |
274 | */\r | |
275 | enable: function() {\r | |
276 | // this.map will be removed if destroyed\r | |
277 | if (this.map) {\r | |
278 | this.map.enable();\r | |
279 | this.disabled = false;\r | |
280 | }\r | |
281 | },\r | |
282 | \r | |
283 | /**\r | |
284 | * Disables this KeyNav.\r | |
285 | */\r | |
286 | disable: function() {\r | |
287 | // this.map will be removed if destroyed\r | |
288 | if (this.map) {\r | |
289 | this.map.disable();\r | |
290 | }\r | |
291 | this.disabled = true;\r | |
292 | },\r | |
293 | \r | |
294 | /**\r | |
295 | * Convenience function for setting disabled/enabled by boolean.\r | |
296 | * @param {Boolean} disabled\r | |
297 | */\r | |
298 | setDisabled : function(disabled) {\r | |
299 | this.map.setDisabled(disabled);\r | |
300 | this.disabled = disabled;\r | |
301 | },\r | |
302 | \r | |
303 | /**\r | |
304 | * @private\r | |
305 | * Determines the event to bind to listen for keys. Defaults to the {@link #eventName} value, but\r | |
306 | * may be overridden the {@link #forceKeyDown} setting.\r | |
307 | *\r | |
308 | * @return {String} The type of event to listen for.\r | |
309 | */\r | |
310 | getKeyEvent: function(forceKeyDown, configuredEventName) {\r | |
311 | if (forceKeyDown || (Ext.supports.SpecialKeyDownRepeat && !configuredEventName)) {\r | |
312 | return 'keydown';\r | |
313 | } else {\r | |
314 | return configuredEventName||this.eventName;\r | |
315 | }\r | |
316 | }\r | |
317 | }); |