Ext.namespace('Zarafa.core'); /** * @class Zarafa.core.KeyMapMgr * @extends Object * * The {@link Zarafa.core.KeyMap KeyMap} manager. * @singleton */ Zarafa.core.KeyMapMgr = Ext.extend(Object, { /** * The list of registered {@link Zarafa.core.KeyMap keymaps}. It contains the list with the element the * keymap is bound to as key. * @property * @type Object * @private */ keyMaps : undefined, /** * The list of key configurations (see {@link Zarafa.core.KeyMap#addBinding Zarafa.core.KeyMap.addBinding}) * registeredlisted to a mapId. It contains the list with the mapId as the key. * @property * @type Object * @private */ keys : undefined, /** * @constructor */ constructor : function() { this.keyMaps = new Ext.util.MixedCollection(); this.keys = {}; }, /** * Registers the key binding configuration to the specified mapId. * @param {String} mapId The ID of the map keys are registered to. * @param {Object|Array} keyConfig The config (see {@link Zarafa.core.KeyMap#addBinding addBinding}). */ register : function(mapId, keyConfig) { if(!this.keys[mapId]){ this.keys[mapId] = []; } if(!Array.isArray(keyConfig)) { keyConfig = [keyConfig]; } this.keys[mapId] = this.keys[mapId].concat(keyConfig); // register same bindings for global webapp as well this.preventBrowserDefaults(mapId, keyConfig); }, /** * Function will register all key bindings that are registerd in {@link Zarafa.core.KeyMapMgr this} * class to the 'globaldisable' mapid and assign stop event and empty hadler so that we can disable * default behaviour of browser when no component is focused and user tries to perform some * key-combinations which isn't available globally. * @param {String} mapId The ID of the map keys are registered to. * @param {Object|Array} keyConfig The config (see {@link Zarafa.core.KeyMap#addBinding addBinding}). */ preventBrowserDefaults : function(mapId, keyConfig) { // Check that mapID isn't 'global' as they are already registered globally. // Check that mapID isn't 'globaldisable' to prevent recursive call. if(mapId !== 'global' && mapId !== 'globaldisable') { for(var i = 0, len = keyConfig.length; i < len; i++) { var config = keyConfig[i]; if(config.enableGlobally !== true) { var disableKeyConfig = { handler : Ext.emptyFn, stopEvent : true }; Ext.applyIf(disableKeyConfig, config); // Add all events in 'globaldisable' keymap. this.register('globaldisable', disableKeyConfig); } else if (config.ctrl === true) { var key = config.key; if(Ext.isString(key)) { key = key.toUpperCase().charCodeAt(0); } if (key === Ext.EventObject.A) { // special case for ctrl + a, as we want to allow action in text fields but not on body of webapp var disableKeyConfig = { handler : this.disableTextSelection, // don't blindly prevent default action instead leave that handling for the handler stopEvent : false }; Ext.applyIf(disableKeyConfig, config); // Add event in 'globaldisable' keymap. this.register('globaldisable', disableKeyConfig); } } } } }, /** * Event handler for the keydown event of the {@link Ext.KeyMap KeyMap} * when the user presses ctrl + a on a field which doesn't have text selection. So we will prevent * default action of browser to select all text in the body. * @param {Number} key Key code * @param {Ext.EventObject} event The event * @param {Ext.Component} component The component on which key event is fired. */ disableTextSelection : function(key, event, component) { var target = event.getTarget().nodeName.toLowerCase(); if(target !== 'textarea' && target !== 'input') { // we don't want to select everything in the body event.stopEvent(); } }, /** * Adds the keymap to the specified Element. It will take the keys registered in {@link #keys keys} * under the specified mapId. If a keymap has already been registered under this element it will add new keys * to the same {@link Zarafa.core.KeyMap KeyMap}. * * If basic shortcuts are enabled we filter the bindings obtained from the mapId to only * enable the bindings which contain the basic key. * * @param {Ext.Component} component The component to which keymap should be bound and * will listen keypress events on {@link Ext.Component#el}. * @param {String} mapId The ID of the map keys are registered to. * @param {Ext.Element} element {optional} if component is not present and we need to register key events * on any element then we can pass it here (eg Ext.getBody()). Additionally if we want to register events on * different element then {@link Ext.Component#el} then we should pass the element on which keymap * will be registered (eg Zarafa.common.ui.HTMLEditor). */ activate : function(component, mapId, element) { // if element is not passed then get it from component if(!element) { element = component.getEl(); } // if element is passed as dom node then find its corresponding Ext.Element object element = Ext.get(element); var elementId = element.id; if(Ext.isEmpty(elementId)) { // without element id its not possible to activate keymap return; } var bindings = this.getKeyBindings(mapId); var setting = container.getSettingsModel().get('zarafa/v1/main/keycontrols'); // Filter basic shortcuts if (setting === Zarafa.settings.data.KeyboardSettings.BASIC_KEYBOARD_SHORTCUTS) { bindings = bindings.filter(function(binding) { return Ext.isDefined(binding.basic); }); } else if (setting === Zarafa.settings.data.KeyboardSettings.NO_KEYBOARD_SHORTCUTS) { bindings = []; } if(Ext.isEmpty(bindings)) { // if no bindings are found then ignore return; } // check if we already have a registered keymap on this component // if we have then add binding with that keymap. var keymap = this.getActive(element); if(keymap) { keymap.addBinding(bindings); } else { // register event that will remove keymap from KeyMapMgr if component is destroyed if(component instanceof Ext.Component) { component.on('beforedestroy', this.onComponentDestroy, this); } // create a new keymap and register it on component this.keyMaps.add(elementId, new Zarafa.core.KeyMap(component, bindings, element)); } }, /** * Function is used to get key bindings registered for a particular map id. * It will recursively get bindings for every string seperated using dot and then will * combine and return the bindings. * @param {String} mapId map id for which we need to get bindings * @return {Array} array of key bindings that be added in keymap */ getKeyBindings : function(mapId) { // Get the bindings registered for the mapId. Also see if any other bindings have been // registered on mapIds that are hierarchically-speaking its parent. So // "contentpanel.record.message" will also get keys for "contentpanel.record" and // "contentpanel". var bindings = []; while(!Ext.isEmpty(mapId)) { // get binding for particular map id if(this.keys[mapId]) { bindings = bindings.concat(this.keys[mapId]); } // change map id to point to its parent mapId = mapId.substr(0, mapId.lastIndexOf('.')); } return bindings; }, /** * Handler function that will be called when any component registered with {@link Zarafa.core.KeyMapMgr KeyMapMgr} * is going to be destroyed, so we can safely remove keymappings registered with that component. * @param {Ext.Component} component component that is going to be destroyed */ onComponentDestroy : function(component) { this.deactivate(component.getEl()); }, /** * Disables the {@link Zarafa.core.KeyMap keymap} and removes it from list of {@link Zarafa.core.KeyMapMgr#keyMaps}. * {@link Zarafa.core.KeyMap#disable} removes event listener which is used to check and fire handler events for bindings * but it doesn't remove bindings registered with the {@link Zarafa.core.KeyMap keymap} that will be done only when * element is destroyed from dom. * @param {Ext.Element} element The element on which keymap is bound. */ deactivate : function(element) { element = Ext.get(element); var keymap = this.keyMaps.get(element.id); if(keymap) { keymap.disable(); this.keyMaps.remove(keymap); } }, /** * Function returns {@link Zarafa.core.KeyMap keymap} bound to component or element. * @param {Ext.Element} element The element which should be used to check for bound keymaps. * @return {Zarafa.core.KeyMap} keymap object registered on component/element */ getActive: function(element) { // if element is passed as dom node then find its corresponding Ext.Element object element = Ext.get(element); return this.keyMaps.get(element.id); }, /** * Will enable the {@link Zarafa.core.KeyMap keymap} that is registered on the specified element. * @param {Ext.Element} element The element on which keymap is bound. */ enableKeyMap: function(element) { // if element is passed as dom node then find its corresponding Ext.Element object element = Ext.get(element); var keymap = this.keyMaps.get(element.id); if(keymap) { if(this.isGloballyEnabled()) { // We need to check whether we are not in a state where the keymaps have been disabled globally. keymap.enable(); } else { // Only set the originallyEnabled flag on the keymap when the keymaps have been disabled globally in the KeyMapMgr. keymap.originallyEnabled = true; } } }, /** * Will disable the {@link Zarafa.core.KeyMap keymap} that is registered on the specified element. * @param {Ext.Element} element The element on which keymap is bound. */ disableKeyMap: function(element) { // if element is passed as dom node then find its corresponding Ext.Element object element = Ext.get(element); var keymap = this.keyMaps.get(element.id); if(keymap) { keymap.disable(); // Only set the originallyEnabled flag on the keymap when the keymaps have been disabled globally in the KeyMapMgr. if(!this.isGloballyEnabled()) { keymap.originallyEnabled = false; } } }, /** * Can be used to enable the keymaps globally and works in conjucture with {@link #disable} that * is able to disable all the keymaps globally. * Will enable the all the registered keymaps that have an originallyEnabled flag set to true. * After that each keymap, including the ones that have the originallyEnabled flag set to false, * will have that flag cleared from the keymap object. */ enableAllKeymaps : function() { this.keyMaps.each(function(keymap) { if(keymap.originallyEnabled === true) { keymap.enable(); } // Unset the originallyEnabled flag on the keymap object delete keymap.originallyEnabled; }, this); }, /** * Can be used to disable the keymaps globally and works in conjucture with {@link #enable} that * is able to enable all the keymaps globally. * Will enable the all the registered keymaps that have an originallyEnabled flag set to true. * After that each keymap, including the ones that have the originallyEnabled flag set to false, * will have that flag cleared from the keymap object. */ disableAllKeymaps : function() { this.keyMaps.each(function(keymap) { keymap.originallyEnabled = keymap.isEnabled(); keymap.disable(); }, this); }, /** * Checks if the {@link Zarafa.core.KeyMap keymap} is enabled on the specified element. * @param {Ext.Element} element The element on which keymap is bound. * @return {Boolean} True when enabled, false otherwise. */ isEnabled : function(element) { var keymap = this.getActive(element); return keymap && keymap.isEnabled(); }, /** * Returns whether the keymaps are globally enabled or not. * return {Boolean} True when enabled, false otherwise. */ isGloballyEnabled : function() { return container.getSettingsModel().get('zarafa/v1/main/keycontrols') !== Zarafa.settings.data.KeyboardSettings.NO_KEYBOARD_SHORTCUTS; } }); Zarafa.core.KeyMapMgr = new Zarafa.core.KeyMapMgr();