JSDoc: Source: menu_store.js

# Source: menu_store.js

    /*************************************************************
     *
     *  Copyright (c) 2015-2016 The MathJax Consortium
     *
     *  Licensed under the Apache License, Version 2.0 (the "License");
     *  you may not use this file except in compliance with the License.
     *  You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     *  Unless required by applicable law or agreed to in writing, software
     *  distributed under the License is distributed on an "AS IS" BASIS,
     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *  See the License for the specific language governing permissions and
     *  limitations under the License.
     */
    /**
     * @fileoverview A store that maps HTML elements in a document to context menus.
     *
     * @author v.sorge@mathjax.org (Volker Sorge)
     */
    /// <reference path="context_menu.ts" />
    /// <reference path="menu_util.ts" />
    var ContextMenu;
    (function (ContextMenu) {
        class MenuStore {
            /**
             * @constructor
             * @param {ContextMenu} menu The context menu the store belongs to.
             */
            constructor(menu) {
                this.store = [];
                this.active = null;
                this.counter = 0;
                this.attachedClass = ContextMenu.HtmlClasses['ATTACHED'] + '_' +
                    ContextMenu.MenuUtil.counter();
                this.taborder = true;
                this.attrMap = {};
                this.menu = menu;
            }
            /**
             * Sets the new active store element if it exists in the store.
             * @param {HTMLElement} element Element to be activated.
             */
            setActive(element) {
                do {
                    if (this.store.indexOf(element) !== -1) {
                        this.active = element;
                        break;
                    }
                    element = element.parentNode;
                } while (element);
            }
            /**
             * @return {HTMLElement} The currently active store element, if one exists.
             */
            getActive() {
                return this.active;
            }
            /**
             * Returns next active element.
             * If store is empty returns null and also unsets active element.
             * If active is not set returns the first element of the store.
             * @return {HTMLElement} The next element if it exists.
             */
            next() {
                let length = this.store.length;
                if (length === 0) {
                    this.active = null;
                    return null;
                }
                let index = this.store.indexOf(this.active);
                index = index === -1 ? 0 : (index < length - 1 ? index + 1 : 0);
                this.active = this.store[index];
                return this.active;
            }
            /**
             * Returns previous active element.
             * If store is empty returns null and also unsets active element.
             * If active is not set returns the last element of the store.
             * @return {HTMLElement} The previous element if it exists.
             */
            previous() {
                let length = this.store.length;
                if (length === 0) {
                    this.active = null;
                    return null;
                }
                let last = length - 1;
                let index = this.store.indexOf(this.active);
                index = index === -1 ? last : (index === 0 ? last : index - 1);
                this.active = this.store[index];
                return this.active;
            }
            /**
             * Removes all elements in the store.
             */
            clear() {
                this.remove(this.store);
            }
            /**
             * Inserts DOM elements into the store.
             * @param {HTMLElement|Array.<HTMLElement>|NodeList} elementOrList Elements
             *     to insert.
             */
            insert(elementOrList) {
                let elements = elementOrList instanceof HTMLElement ?
                    [elementOrList] : elementOrList;
                for (let i = 0, element; element = elements[i]; i++) {
                    this.insertElement(element);
                }
                this.sort();
            }
            /**
             * Removes DOM elements from the store.
             * @param {HTMLElement|Array.<HTMLElement>|NodeList} elementOrList Elements
             *     to remove.
             */
            remove(elementOrList) {
                let elements = elementOrList instanceof HTMLElement ?
                    [elementOrList] : elementOrList;
                for (let i = 0, element; element = elements[i]; i++) {
                    this.removeElement(element);
                }
                this.sort();
            }
            /**
             * Sets if elements of the store are included in the taborder or not.
             * @param {boolean} flag If true elements are in taborder, o/w not.
             */
            inTaborder(flag) {
                if (this.taborder && !flag) {
                    this.removeTaborder();
                }
                if (!this.taborder && flag) {
                    this.insertTaborder();
                }
                this.taborder = flag;
            }
            /**
             * Inserts all elements in the store into the tab order.
             */
            insertTaborder() {
                if (this.taborder) {
                    this.insertTaborder_();
                }
            }
            /**
             * Removes all elements in the store from the tab order.
             */
            removeTaborder() {
                if (this.taborder) {
                    this.removeTaborder_();
                }
            }
            /**
             * Adds a DOM element to the store.
             * @param {HTMLElement} element The DOM element.
             */
            insertElement(element) {
                if (element.classList.contains(this.attachedClass)) {
                    return;
                }
                element.classList.add(this.attachedClass);
                if (this.taborder) {
                    this.addTabindex(element);
                }
                this.addEvents(element);
            }
            /**
             * Removes a DOM element from the store.
             * @param {HTMLElement} element The DOM element.
             */
            removeElement(element) {
                if (!element.classList.contains(this.attachedClass)) {
                    return;
                }
                element.classList.remove(this.attachedClass);
                if (this.taborder) {
                    this.removeTabindex(element);
                }
                this.removeEvents(element);
            }
            /**
             * Sorts the elements in the store in DOM order.
             */
            sort() {
                let nodes = document.getElementsByClassName(this.attachedClass);
                this.store = [].slice.call(nodes);
            }
            /**
             * Inserts all elements in the store into the tab order.
             */
            insertTaborder_() {
                this.store.forEach(x => x.setAttribute('tabindex', '0'));
            }
            /**
             * Removes all elements in the store from the tab order.
             */
            removeTaborder_() {
                this.store.forEach(x => x.setAttribute('tabindex', '-1'));
            }
            /**
             * Adds tabindex to an element and possibly safes an existing one.
             * @param {HTMLElement} element The DOM element.
             */
            addTabindex(element) {
                if (element.hasAttribute('tabindex')) {
                    element.setAttribute(ContextMenu.HtmlAttrs['OLDTAB'], element.getAttribute('tabindex'));
                }
                element.setAttribute('tabindex', '0');
            }
            /**
             * Removes tabindex from element or restores an old one.
             * @param {HTMLElement} element The DOM element.
             */
            removeTabindex(element) {
                if (element.hasAttribute(ContextMenu.HtmlAttrs['OLDTAB'])) {
                    element.setAttribute('tabindex', element.getAttribute(ContextMenu.HtmlAttrs['OLDTAB']));
                    element.removeAttribute(ContextMenu.HtmlAttrs['OLDTAB']);
                }
                else {
                    element.removeAttribute('tabindex');
                }
            }
            //// TODO: Need to add touch event.
            /**
             * Adds event listeners to activate the context menu to an element.
             *
             * To be able to remove event listeners we have to remember exactly which
             * listeners we have added. We safe them in the attribute mapping attrMap,
             * as a combination of event handler name and counter, which is unique for
             * each HTML element. The counter is stored on the HTML element in an
             * attribute.
             *
             * @param {HTMLElement} element The DOM element.
             */
            addEvents(element) {
                if (element.hasAttribute(ContextMenu.HtmlAttrs['COUNTER'])) {
                    return;
                }
                this.addEvent(element, 'contextmenu', this.menu.post.bind(this.menu));
                this.addEvent(element, 'keydown', this.keydown.bind(this));
                element.setAttribute(ContextMenu.HtmlAttrs['COUNTER'], this.counter.toString());
                this.counter++;
            }
            /**
             * Adds a single event listeners and stores them in the attribute mapping.
             * @param {HTMLElement} element The DOM element.
             * @param {string} name The name of the event handler.
             * @param {EventListener} func The event listener.
             */
            addEvent(element, name, func) {
                let attrName = ContextMenu.HtmlAttrs[name.toUpperCase() + 'FUNC'];
                this.attrMap[attrName + this.counter] = func;
                element.addEventListener(name, func);
            }
            /**
             * Removes event listeners that activate the context menu from an element.
             * @param {HTMLElement} element The DOM element.
             */
            removeEvents(element) {
                if (!element.hasAttribute(ContextMenu.HtmlAttrs['COUNTER'])) {
                    return;
                }
                let counter = element.getAttribute(ContextMenu.HtmlAttrs['COUNTER']);
                this.removeEvent(element, 'contextmenu', counter);
                this.removeEvent(element, 'keydown', counter);
                element.removeAttribute(ContextMenu.HtmlAttrs['COUNTER']);
            }
            /**
             * Removes a single event listeners from an HTML element.
             * @param {HTMLElement} element The DOM element.
             * @param {string} name The name of the event handler.
             * @param {string} counter The unique counter to identify the handler in the
             *     attribute mappings.
             */
            removeEvent(element, name, counter) {
                let attrName = ContextMenu.HtmlAttrs[name.toUpperCase() + 'FUNC'];
                let menuFunc = this.attrMap[attrName + counter];
                element.removeEventListener(name, menuFunc);
            }
            /**
             * Deals with key down keyboard events.
             * @param {KeyboardEvent} event The keyboard event.
             */
            keydown(event) {
                if (event.keyCode === ContextMenu.KEY.SPACE) {
                    this.menu.post(event);
                }
            }
        }
        ContextMenu.MenuStore = MenuStore;
    })(ContextMenu || (ContextMenu = {}));