]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/util/KeyNav.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / util / KeyNav.js
CommitLineData
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
42Ext.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});