/** * Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind * navigation keys to function calls that will get called when the keys are pressed, providing * an easy way to implement custom navigation schemes for any UI component. * * The following are all of the possible keys that can be implemented: Enter, Space, Left, Right, * Up, Down, Tab, Esc, Page Up, Page Down, Delete, Backspace, Home, End. * * Usage: * * var nav = new Ext.util.KeyNav({ * target: "my-element", * * left: function(e) { * this.moveLeft(e.ctrlKey); * }, * right: function(e) { * this.moveRight(e.ctrlKey); * }, * enter: function(e) { * this.save(); * }, * * // Binding may be a function specifiying fn, scope and defaultEventAction * esc: { * fn: this.onEsc, * defaultEventAction: false * }, * * // Binding may be keyed by a single character * A: { * ctrl: true, * fn: selectAll * }, * * // Binding may be keyed by a key code (45 = INSERT) * 45: { * fn: doInsert * }, * * scope: myObject * }); */Ext.define('Ext.util.KeyNav', { alternateClassName: 'Ext.KeyNav', requires: ['Ext.util.KeyMap'], /** * @cfg {Boolean} disabled * True to disable this KeyNav instance. */ disabled: false, /** * @cfg {String} [defaultEventAction=false] * The method to call on the {@link Ext.event.Event} after this KeyNav intercepts a key. * Valid values are {@link Ext.event.Event#stopEvent}, {@link Ext.event.Event#preventDefault} * and {@link Ext.event.Event#stopPropagation}. * * If a falsy value is specified, no method is called on the key event. */ defaultEventAction: false, /** * @cfg {Boolean} forceKeyDown * * Handle the keydown event instead of keypress. KeyNav automatically does this for IE * since IE does not propagate special keys on keypress, but setting this to true will force * other browsers to also handle keydown instead of keypress. */ forceKeyDown: false, /** * @cfg {Ext.Component/Ext.dom.Element/HTMLElement/String} target * The object on which to listen for the event specified by the {@link #eventName} config option. */ /** * @cfg {String} eventName * The event to listen for to pick up key events. */ eventName: 'keypress', /** * @cfg {Function} processEvent * An optional event processor function which accepts the argument list provided by the * {@link #eventName configured event} of the {@link #target}, and returns a keyEvent * for processing by the KeyMap. * * This may be useful when the {@link #target} is a Component with s complex event signature. * Extra information from the event arguments may be injected into the event for use * by the handler functions before returning it. */ /** * @cfg {Object} [processEventScope=this] * The scope (`this` context) in which the {@link #processEvent} method is executed. */ /** * @cfg {Boolean} [ignoreInputFields=false] * Configure this as `true` if there are any input fields within the {@link #target}, and this * KeyNav should not process events from input fields (`<input>`, `<textarea>` and elements * with `contentEditable="true"`) */ /** * @cfg {Ext.util.KeyMap} [keyMap] * An optional pre-existing {@link Ext.util.KeyMap KeyMap} to use to listen for key events. * If not specified, one is created. */ /** * @property {Ext.event.Event} lastKeyEvent * The last key event that this KeyMap handled. */ /** * @cfg {Number} [priority] * The priority to set on this KeyNav's listener. Listeners with a higher priority are fired * before those with lower priority. */ statics: { keyOptions: { left: 37, right: 39, up: 38, down: 40, space: 32, pageUp: 33, pageDown: 34, del: 46, backspace: 8, home: 36, end: 35, enter: 13, esc: 27, tab: 9 } }, constructor: function(config) { var me = this, keymapCfg, map; //<debug> if (arguments.length === 2) { Ext.raise("2-argument KeyNav constructor is removed. Use a config object instead."); } //</debug> config = config || {}; keymapCfg = { target: config.target, ignoreInputFields: config.ignoreInputFields, eventName: me.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : me.forceKeyDown, config.eventName), capture: config.capture }; if (me.map) { me.map.destroy(); } // Ensure config system configs are set me.initConfig(config); if (config.processEvent) { keymapCfg.processEvent = config.processEvent; keymapCfg.processEventScope = config.processEventScope || me; } if (config.priority) { keymapCfg.priority = config.priority; } // If they specified a KeyMap to use, use it if (config.keyMap) { map = me.map = config.keyMap; } // Otherwise, create one, and remember to destroy it on destroy else { map = me.map = new Ext.util.KeyMap(keymapCfg); me.destroyKeyMap = true; } this.addBindings(config); map.disable(); if (!config.disabled) { map.enable(); } }, addBindings: function(bindings) { var me = this, map = me.map, keyCodes = Ext.util.KeyNav.keyOptions, defaultScope = bindings.scope || me, binding, keyName, keyCode; for (keyName in bindings) { binding = bindings[keyName]; // There is a property named after a key name. // It may be a function or an binding spec containing handler, scope and // defaultEventAction configs // Allow { A: { ctrl: true, handler: onCtrlA } } // Allow { 45: doInsert } to use key codes directly keyCode = keyCodes[keyName]; if (keyCode != null) { keyName = keyCode; } if (binding && (keyName.length === 1 || !isNaN(keyName = parseInt(keyName, 10)))) { if (typeof binding === 'function') { binding = { handler: binding, defaultEventAction: (bindings.defaultEventAction !== undefined) ? bindings.defaultEventAction : me.defaultEventAction }; } map.addBinding({ key: keyName, ctrl: binding.ctrl, shift: binding.shift, alt: binding.alt, handler: Ext.Function.bind(me.handleEvent, binding.scope || defaultScope, [binding.handler||binding.fn, me], true), defaultEventAction: (binding.defaultEventAction !== undefined) ? binding.defaultEventAction : me.defaultEventAction }); } } }, /** * Method for filtering out the map argument * @private * @param {Number} keyCode * @param {Ext.event.Event} event * @param {Function} handler The function to call * @param {Ext.util.KeyNav} keyNav The owning KeyNav */ handleEvent: function(keyCode, event, handler, keyNav) { keyNav.lastKeyEvent = event; return handler.call(this, event); }, /** * Destroy this KeyNav. * @param {Boolean} removeEl Pass `true` to remove the element associated with this KeyNav. */ destroy: function(removeEl) { var me = this; if (removeEl) { Ext.raise("removeEl argument in KeyNav destructor is not supported anymore."); } if (me.destroyKeyMap) { me.map.destroy(removeEl); } me.map = null; me.callParent(); }, /** * Enables this KeyNav. */ enable: function() { // this.map will be removed if destroyed if (this.map) { this.map.enable(); this.disabled = false; } }, /** * Disables this KeyNav. */ disable: function() { // this.map will be removed if destroyed if (this.map) { this.map.disable(); } this.disabled = true; }, /** * Convenience function for setting disabled/enabled by boolean. * @param {Boolean} disabled */ setDisabled: function(disabled) { this.map.setDisabled(disabled); this.disabled = disabled; }, isEnabled: function() { return !this.disabled; }, /** * @private * Determines the event to bind to listen for keys. Defaults to the {@link #eventName} value, but * may be overridden the {@link #forceKeyDown} setting. * * @return {String} The type of event to listen for. */ getKeyEvent: function(forceKeyDown, configuredEventName) { if (forceKeyDown || (Ext.supports.SpecialKeyDownRepeat && !configuredEventName)) { return 'keydown'; } else { return configuredEventName || this.eventName; } }});