nw-skeleton

Source: app-wrapper/js/helper/keyboardHelper.js

/**
 * @fileOverview KeyboardHelper class file
 * @author Dino Ivankov <dinoivankov@gmail.com>
 * @version 1.3.1
 */

/**
 * Object that contains key codes for keyboardHelper
 * @typedef  {Object}    KeyboardHelperKeyCodes
 *
 * @property {Number[]} debugKeys           An array of key codes for turning debug off or on
 * @property {Number[]} commandKeyCodes     An array of key codes for setting ctrlPressed
 * @property {Number[]} shiftKeyCodes       An array of key codes for setting shiftPressed
 * @property {Number[]} altKeyCodes         An array of key codes for setting altPressed
 * @property {Number[]} reloadKeyCodes      An array of key codes for window reloading
 * @property {Number[]} closeKeyCodes       An array of key codes for window closing
 * @property {Number[]} escKeyCodes         An array of key codes for "ESC" key
 * @property {Number[]} reloadCssKeyCodes   An array of key codes for CSS reloading
 */

const _ = require('lodash');
const AppBaseClass = require('../lib/appBase').AppBaseClass;

var _appWrapper;
var appState;

/**
 * KeyboardHelper class - handles and manages keyboard events and operations
 *
 * @class
 * @extends {appWrapper.AppBaseClass}
 * @memberof appWrapper.helpers
 * @property {KeyboardHelperKeyCodes}   keyCodes        Key code definitions for keyboard helper
 * @property {Object}                   keyCodeNames    Hash for detecting 'ctrl', 'alt' and 'shift' buttons, mapping them to this.keyCodes members
 * @property {Object}                   globalKeyData   Data from configuration for global key handlers
 * @property {Number[]}                 pressedKeys     An array of previously pressed keys
 */
class KeyboardHelper extends AppBaseClass {

    /**
     * Creates KeyboardHelper instance
     *
     * @constructor
     * @return {KeyboardHelper}              Instance of KeyboardHelper class
     */
    constructor() {
        super();

        _appWrapper = this.getAppWrapper();
        appState = this.getAppState();

        this.keyCodes = {
            debugKeys: [68,69,66,85,71], // d,e,b,u,g
            commandKeyCodes: [224, 17, 91, 93],
            shiftKeyCodes: [16],
            altKeyCodes: [18],
            reloadKeyCodes: [82], // 'r'
            closeKeyCodes: [87], // 'w'
            escKeyCodes: [27],
            reloadCssKeyCodes: [85], // 'u'
            reinitializeFeAppKeyCodes: [85], // 'u'
        };

        this.keyCodeNames = {
            'ctrl': 'commandKeyCodes',
            'shift': 'shiftKeyCodes',
            'alt': 'altKeyCodes'
        };

        this.globalKeyData = [];

        this.pressedKeys = [];

        this.boundMethods = {
            handleKeyDown: 'handleKeyDown'
        };

        return this;
    }

    /**
     * Initializes keyboardHelper, adding event listeners
     *
     * @async
     * @param {Object} options  Initialization options
     * @return {KeyboardHelper} Instance of KeyboardHelper class
     */
    async initialize (options) {
        await super.initialize(options);
        this.addEventListeners();
        return this;
    }

    /**
     * Adds keyboard helper event listeners
     *
     * @return {undefined}
     */
    addEventListeners (){
        window.addEventListener('keydown', this.boundMethods.handleKeyDown);
        window.addEventListener('keyup', this.boundMethods.handleKeyDown);
    }

    /**
     * Removes keyboard helper event listeners
     *
     * @return {undefined}
     */
    removeEventListeners (){
        window.removeEventListener('keydown', this.boundMethods.handleKeyDown);
        window.removeEventListener('keyup', this.boundMethods.handleKeyDown);
    }

    /**
     * Registers global shortcut from global keys configuration
     *
     * @param  {Object} keyHandler Shortcut configuration
     * @return {undefined}
     */
    registerGlobalShortcut (keyHandler){
        this.globalKeyData.push(keyHandler);
    }

    /**
     * Unregisters global shortcut from global keys configuration
     *
     * @param  {Object} keyHandler Shortcut configuration
     * @return {undefined}
     */
    unRegisterGlobalShortcut (keyHandler){
        this.globalKeyData = _.filter(this.globalKeyData, {
            keyCode: keyHandler.keyCode,
            handler: keyHandler.handler
        });
    }

    /**
     * Handles key down (and up) events
     *
     * @param  {Event} e Event that triggered the method
     * @return {undefined}
     */
    handleKeyDown (e){
        if (!this.handleAppKeyDown(e)){
            this.handleGlobalKeyDown(e);
        }
    }

    /**
     * Handles key down (and up) events for app shortcuts
     *
     * @param  {Event} e    Event that triggered the method
     * @return {Boolean}    True if event handler is found, false otherwise
     */
    handleAppKeyDown(e){
        var fulfilled = false;
        var keyCode = e.keyCode;
        if (_.includes(this.keyCodes.commandKeyCodes, keyCode)){
            e.stopImmediatePropagation();
            this.pressedKeys = [];
            if (e.type == 'keydown') {
                appState.status.ctrlPressed = true;
            } else if (e.type == 'keyup'){
                appState.status.ctrlPressed = false;
            }
            fulfilled = true;
        } else if (_.includes(this.keyCodes.shiftKeyCodes, keyCode)){
            e.stopImmediatePropagation();
            this.pressedKeys = [];
            if (e.type == 'keydown') {
                appState.status.shiftPressed = true;
            } else if (e.type == 'keyup'){
                appState.status.shiftPressed = false;
            }
            fulfilled = true;
        } else if (_.includes(this.keyCodes.altKeyCodes, keyCode)){
            e.stopImmediatePropagation();
            this.pressedKeys = [];
            if (e.type == 'keydown') {
                appState.status.altPressed = true;
            } else if (e.type == 'keyup'){
                appState.status.altPressed = false;
            }
            fulfilled = true;
        } else {
            if (e.type == 'keydown') {
                if (appState && appState.modalData.currentModal && !appState.modalData.currentModal.preventEscClose && appState.modalData.modalVisible && _.includes(this.keyCodes.escKeyCodes, keyCode)){
                    if (!this.checkNoHandlingKeys()){
                        if (_appWrapper.cancelModalAction && _.isFunction(_appWrapper.cancelModalAction)){
                            _appWrapper.cancelModalAction();
                        } else {
                            _appWrapper.helpers.modalHelper.closeCurrentModal();
                        }
                        fulfilled = true;
                    } else {
                        e.stopImmediatePropagation();
                    }
                } else if (appState && appState.status.ctrlPressed && _.includes(this.keyCodes.closeKeyCodes, keyCode)){
                    if (!this.checkNoHandlingKeys()){
                        _appWrapper.windowManager.closeWindow();
                        fulfilled = true;
                    } else {
                        e.stopImmediatePropagation();
                    }
                } else if (appState && appState.status.ctrlPressed && appState.status.shiftPressed && _.includes(this.keyCodes.reinitializeFeAppKeyCodes, keyCode)){
                    if (!this.checkNoHandlingKeys()){
                        _appWrapper.reinitializeFeApp();
                        fulfilled = true;
                    } else {
                        e.stopImmediatePropagation();
                    }
                } else if (appState && appState.status.ctrlPressed && _.includes(this.keyCodes.reloadCssKeyCodes, keyCode)){
                    this.getHelper('staticFiles').reloadCss();
                    fulfilled = true;
                } else if ( appState.status.ctrlPressed && appState && appState.config.debug.enabled && e.type == 'keydown' && _.includes(this.keyCodes.reloadKeyCodes, keyCode)){
                    if (!this.checkNoHandlingKeys()){
                        this.pressedKeys = [];
                        _appWrapper.windowManager.reloadWindow();
                        fulfilled = true;
                    } else {
                        e.stopImmediatePropagation();
                    }
                } else {
                    var nextDebugCode = this.keyCodes.debugKeys[this.pressedKeys.length];
                    if (keyCode == nextDebugCode){
                        this.pressedKeys.push(keyCode);
                    } else {
                        this.pressedKeys = [];
                    }

                    if (this.pressedKeys.length == this.keyCodes.debugKeys.length){
                        this.pressedKeys = [];
                        appState.config.debug.enabled = !appState.config.debug.enabled;
                        fulfilled = true;
                        _appWrapper.appConfig.setConfigVar('debug.enabled', appState.config.debug.enabled);
                        if (appState.config.debug.enabled){
                            this.addUserMessage('Debug mode enabled.', 'info', [], false);
                        } else {
                            this.addUserMessage('Debug mode disabled.', 'info', [], false);
                        }
                    }
                }
            }
        }
        return fulfilled;
    }

    /**
     * Handles key down (and up) events for global shortcuts
     *
     * @param  {Event} e    Event that triggered the method
     * @return {Boolean}    True if event handler is found, false otherwise
     */
    handleGlobalKeyDown (e) {
        var keyCode = e.keyCode;
        var key = e.key;
        var type = e.type;
        var globalKeys = _.flattenDeep(_.map(this.globalKeyData, function(item){
            return item.keyCodes;
        }));

        if (_.includes('*', globalKeys) || _.intersection(globalKeys, [keyCode]).length){
            var allHandlers = _.filter(this.globalKeyData, function(item){
                var found = false;
                if (item.key && item.key == key && item.event == type){
                    found = true;
                }
                if (item.event == type && (_.includes('*', globalKeys) || (item.keyCodes && _.includes(item.keyCodes, keyCode)))){
                    found = true;
                }
                return found;
            });
            var shortcutData = [];
            if (allHandlers && allHandlers.length){
                for(let i=0; i<allHandlers.length;i++){
                    var handler = allHandlers[i];
                    if (handler){
                        var handlerFound = true;
                        if (handler.modifiers) {
                            if (handler.modifiers.ctrl && !appState.status.ctrlPressed){
                                handlerFound = false;
                            }
                            if (handler.modifiers.shift && !appState.status.shiftPressed){
                                handlerFound = false;
                            }
                            if (handler.modifiers.alt && !appState.status.altPressed){
                                handlerFound = false;
                            }
                        }
                        if (handlerFound){
                            shortcutData.push(handler);
                        }
                    }
                }
            }
            for (let j=0; j<shortcutData.length; j++){
                var shortcut = shortcutData[j];
                if (shortcut.handler){
                    if (_.isFunction(shortcut.handler)){
                        if (!this.checkNoHandlingKeys()){
                            shortcut.handler(e);
                        }
                    } else if (_.isString(shortcut.handler)){
                        if (!this.checkNoHandlingKeys()){
                            _appWrapper.callObjMethod(shortcut.handler, [e]);
                        }
                    }
                }
            }
        }
    }

    /**
     * Destroys this KeyboardHelper instance, removing its event listeners
     *
     * @return {undefined}
     */
    destroy () {
        super.destroy();
        this.removeEventListeners();
    }

    /**
     * Checks whether global key handlers are enabled
     *
     * @return {Boolean} True if global key handlers are enabled, false otherwise
     */
    checkNoHandlingKeys () {
        if (appState.status.noHandlingKeys){
            if (appState.modalData.modalVisible){
                _appWrapper.addModalMessage('Action prevented because modal is open', 'warning', [], false, false, true, true);
            }
            return true;
        } else {
            return false;
        }
    }
}

exports.KeyboardHelper = KeyboardHelper;