Source: composer/key-composer.js

/**
 * @module montage/composer/key-composer
 * @requires montage/core/core
 * @requires montage/composer/composer
 */
var Montage = require("../core/core").Montage,
    Composer = require("./composer").Composer;


/**
 * @class KeyManagerProxy
 * @classdesc Provide a proxy for lazy load of KeyManager.
 * @extends Montage
 * @private
 */
var _keyManagerProxy = null;

var KeyManagerProxy = Montage.specialize({

    /**
     * @private
     */
    _defaultKeyManager: {
        value: null
    },

    /**
     * @private
     */
    _loadingDefaultKeyManager: {
        value: false
    },

    /**
     * @private
     */
    _keysToRegister : {
        value: []
    },

    /**
     * Register a `KeyComposer` with the default `KeyManager`.
     * @function
     * @param {Object} keyComposer. A key composer object.
     */
    registerKey: {
        value: function (keyComposer) {
            var thisRef = this;

            if (!this._defaultKeyManager) {
                this._keysToRegister.push(keyComposer);
                if (!this._loadingDefaultKeyManager) {
                    this._loadingDefaultKeyManager = true;

                    require.async("core/event/key-manager")
                    .then(function (module) {
                        var keyManager = thisRef._defaultKeyManager = module.defaultKeyManager;
                        thisRef._keysToRegister.forEach(function (keyComposer) {
                            keyManager.registerKey(keyComposer);
                        });
                        thisRef._keysToRegister.length = 0;
                    });
                }
            } else {
                // This will happend only if somebody uses a cached return
                // value from KeyManagerProxy.defaultKeyManager
                this._defaultKeyManager.registerKey(keyComposer);
            }
        }
    },

    /**
     * Unregister a `KeyComposer` with the default `KeyManager`.
     * @function
     * @param {Object} keyComposer. A key composer object.
     */
    unregisterKey: {
        value: function (keyComposer) {
            if (this._defaultKeyManager) {
                this._defaultKeyManager.unregisterKey(keyComposer);
            }
        }
    }

}, {

    /**
     * Return either the default `KeyManager` or its `KeyManagerProxy`.
     * @function
     * @returns {Object} `KeyManager` or `KeyManagerProxy`.
     */
    defaultKeyManager: {
        get: function () {
            if (!_keyManagerProxy) {
                _keyManagerProxy = new KeyManagerProxy();
            }
            if (this._defaultKeyManager) {
                return this._defaultKeyManager;
            } else {
                return _keyManagerProxy;
            }
        }
    }
});


// Event types dispatched by KeyComposer
var KEYPRESS_EVENT_TYPE = "keyPress",
    LONGKEYPRESS_EVENT_TYPE = "longKeyPress",
    KEYRELEASE_EVENT_TYPE = "keyRelease";

/**
 * @class KeyComposer
 * @classdesc A `Composer` that makes it easy to listen for specific key
 * combinations and react to them.
 * @extends Composer
 * @fires keyPress
 * @fires longKeyPress
 * @fires keyRelease
 * @example
 * keyComposer = KeyComposer.createKey(textComponent, "command+z", "undo");
 * keyComposer.addEventListener("keyPress", undoManager);
 * // when command+z is pressed inside textComponent,
 * // undoManager.handleUndoKeyPress() will be called.
 */
var KeyComposer = exports.KeyComposer = Composer.specialize( /** @lends KeyComposer# */ {

    _shouldDispatchEvent: {
        value: false
    },

    shouldDispatchLongPress: {
        value: false
    },

    _longPressTimeout: {
        value: null
    },

    _keyRegistered: {
        value: false
    },

    _keys:{
        value: null
    },

    /**
     * The keyboard shortcut to listen to. One alphanumeric character or named
     * non-alphanumeric key, possibly with modifiers connected by '+'. The full
     * list of normalized keys and modifiers is in `KeyManager`.  @example "j",
     * "shift+j", "command+shift+j", "backspace", "win+pipe"
     * @type {string}
     * @default null
     */
    keys: {
        get: function () {
            return this._keys;
        },
        set: function (keys) {
            if (this._keyRegistered) {
                KeyManagerProxy.defaultKeyManager.unregisterKey(this);
                this._keys = keys;
                KeyManagerProxy.defaultKeyManager.registerKey(this);
            } else {
                this._keys = keys;
            }
        }
    },

    load: {
        value: function () {
            // Only register the key if somebody is listening for, else let do
            // it later.
            // console.log("--- load", this.identifier);
            if (this._shouldDispatchEvent && !this._keyRegistered) {
                KeyManagerProxy.defaultKeyManager.registerKey(this);
                this._keyRegistered = true;
            }
        }
    },

    unload: {
        value: function () {
            KeyManagerProxy.defaultKeyManager.unregisterKey(this);
            this._keyRegistered = false;
        }
    },

    /**
     * Listen to find out when this `KeyComposer` detects a matching key press.
     * @function
     * @param {string} type Any of the following types: keyPress, longKeyPress
     * and keyRelease.
     * @param {Object|function} listener The listener object or function to
     * call when dispatching the event.
     * @param {boolean} useCapture Specify if the listener want to be called
     * during the capture phase of the event.
     */
    addEventListener: {
        value: function (type, listener, useCapture) {
            // Optimisation so that we don't dispatch an event if we do not need to
            // console.log("--- addEventListener", this.identifier);
            var component = this.component;

            Composer.prototype.addEventListener.call(this, type, listener, useCapture);

            if (type === KEYPRESS_EVENT_TYPE || type === LONGKEYPRESS_EVENT_TYPE || type === KEYRELEASE_EVENT_TYPE) {
                this._shouldDispatchEvent = true;
                if (type === LONGKEYPRESS_EVENT_TYPE) {
                    this._shouldDispatchLongPress = true;
                }

                if (this._isLoaded) {
                    if (!this._keyRegistered) {
                        KeyManagerProxy.defaultKeyManager.registerKey(this);
                        this._keyRegistered = true;
                    }
                } else if (component && typeof component.addComposer !== "function") {
                    // this keyComposer is not associated with an element,
                    // let's make it a global key
                    if (!this.element) {
                        this.element = window;
                    }
                    // this keyComposer is not attached to a UI Component,
                    // let's load it manually
                    this.component.loadComposer(this);
                }
            }
        }
    },

    /**
     * Called when a composer is part of a template serialization. Responsible
     * for calling `addComposer` on the component or calling `load` on the
     * composer.
     * @private
     */
    deserializedFromTemplate: {
        value: function () {
            var component = this.component;

            if (this.identifier === null) {
                this.identifier = Montage.getInfoForObject(this).label;
            }

            if (component) {
                if (typeof component.addComposer === "function") {
                    component.addComposer(this);
                } else if (!this._isLoaded) {
                    // this keyComposer is not associated with an element,
                    // let's make it a global key
                    if (!this.element) {
                        this.element = window;
                    }
                    // this keyComposer is not attached to a UI Component,
                    // let's load it manually
                    this.component.loadComposer(this);
                }
            }
        }
    }
}, {

    /**
     * Constructs a `KeyComposer` to listen for a key combination on a
     * component.
     *
     * The composer will only respond to key events triggered by the DOM
     * elements inside its component or when its component is set as the
     * `activeTarget`.
     *
     * @param {Object} component The component to attach the `KeyComposer` to.
     * @param {Object} keys The key combination, possibly including modifier
     * keys.
     * @param {Object} identifier The identifier for events triggered by this
     * composer.
     * @returns {Object} the newly created `KeyComposer` Object
     */
    createKey: {
        value: function (component, keys, identifier) {
            var key = this;

            if (this === KeyComposer) {
                // This function has been called without creating a new
                // instance of KeyComposer first.
                key = new KeyComposer();
            }

            if (!identifier) {
                if (component.identifier) {
                    identifier = component.identifier + keys.toLowerCase().replace(/[ +]/g).toCapitalized();
                } else {
                    identifier = keys.toLowerCase().replace(/[ +]/g);
                }
            }
            key.keys = keys;
            key.identifier = identifier;

            // console.log("CREATING KEY:", component, key, key.identifier);

            component.addComposer(key);

            return key;
        }
    },

    /**
     * Constructs a `KeyComposer` listening for a key combination anywhere on
     * the page.
     *
     * The composer will respond to key events that bubble up to the `window`.
     *
     * @function
     * @param {Object} component. The component to attach the keyComposer to.
     * @param {Object} keys. The key sequence.
     * @param {Object} identifier. The identifier.
     * @returns {Object} the newly created KeyComposer Object
     */
    createGlobalKey: {
        value: function (component, keys, identifier) {
            var key = this;

            if (this === KeyComposer) {
                // This function has been called without creating a new
                // instance of KeyComposer first
                key = new KeyComposer();
            }

            key.keys = keys;
            key.identifier = identifier;
            // console.log("CREATING GLOBAL KEY:", component, key);

            component.addComposerForElement(key, window);

            return key;
        }
    }

});