252 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import Poppable from './poppable.js'
 | 
						|
import Popup from './popup.js'
 | 
						|
import { Elements } from './utils.js'
 | 
						|
 | 
						|
/** A Popup Menu that shows text labels in a vertical row.
 | 
						|
 */
 | 
						|
export default class PopupMenu extends Popup {
 | 
						|
    /**
 | 
						|
     * The constructor.
 | 
						|
     * @constructor
 | 
						|
     * @param {DOM Element} parent - The DOM parent element.
 | 
						|
     * @param {Object} commands - A dict object with command label strings as keys
 | 
						|
     *                            and command functions as values.
 | 
						|
     * @param {string} fontSize - Describes the font size as CSS value
 | 
						|
     * @param {number || string} padding - Describes the padding as CSS value
 | 
						|
     * @param {number || string} notchSize - Describes the size of the notch (callout) as CSS value
 | 
						|
     * @param {string} highlightColor  - The color of highlighted menu items as CSS value
 | 
						|
     * @param {string} backgroundColor  - The color of the background as CSS value
 | 
						|
     * @param {string} normalColor  - The color of normal menu items as CSS value
 | 
						|
     * @param {DOM Element} keepWithin  - The container to stay within
 | 
						|
     * @param {boolean} autoClose  - Autoclose the menu after selecting an item
 | 
						|
     */
 | 
						|
    constructor({
 | 
						|
        parent = null,
 | 
						|
        commands = null,
 | 
						|
        fontSize = '1em',
 | 
						|
        fontFamily = 'Arial',
 | 
						|
        padding = 16,
 | 
						|
        zIndex = 1,
 | 
						|
        spacing = '0px',
 | 
						|
        switchPos = false,
 | 
						|
        notchSize = 10,
 | 
						|
        maxWidth = 800,
 | 
						|
        backgroundColor = '#EEE',
 | 
						|
        normalColor = '#444',
 | 
						|
        highlightColor = 'black',
 | 
						|
        notchPosition = 'bottomLeft',
 | 
						|
        keepWithin = null,
 | 
						|
        autoClose = true,
 | 
						|
    } = {}) {
 | 
						|
        super({
 | 
						|
            parent,
 | 
						|
            fontSize,
 | 
						|
            fontFamily,
 | 
						|
            padding,
 | 
						|
            notchSize,
 | 
						|
            notchPosition,
 | 
						|
            backgroundColor,
 | 
						|
            keepWithin,
 | 
						|
            normalColor,
 | 
						|
            autoClose,
 | 
						|
        })
 | 
						|
        this.commands = commands
 | 
						|
        this.zIndex = zIndex
 | 
						|
        this.switchPos = switchPos
 | 
						|
        this.spacing = spacing
 | 
						|
        this.highlightColor = highlightColor
 | 
						|
    }
 | 
						|
 | 
						|
    /** Setup menu with a dictionary of command labels and command functions.
 | 
						|
     * @param {Object} commands - A dict object with command label strings as keys
 | 
						|
     *                            and command functions as values.
 | 
						|
     * @return {PopupMenu} this
 | 
						|
     */
 | 
						|
    setup(commands) {
 | 
						|
        this.commands = commands
 | 
						|
        this.items = {}
 | 
						|
        this.element = document.createElement('div')
 | 
						|
        this.element.style.zIndex = this.zIndex
 | 
						|
        Elements.addClass(this.element, 'unselectable')
 | 
						|
        this.notch = document.createElement('div')
 | 
						|
        Elements.setStyle(this.notch, this.notchStyle())
 | 
						|
        for (let key in commands) {
 | 
						|
            let item = document.createElement('div')
 | 
						|
            this.element.appendChild(item)
 | 
						|
            item.innerHTML = key
 | 
						|
            item.style.paddingBottom = item.style.paddingTop = this.spacing
 | 
						|
            Elements.setStyle(item, {
 | 
						|
                color: this.normalColor,
 | 
						|
                cursor: 'default',
 | 
						|
            })
 | 
						|
            Elements.addClass(item, 'unselectable')
 | 
						|
            Elements.addClass(item, 'popupMenuItem')
 | 
						|
            this.items[key] = item
 | 
						|
            item.onclick = (event) => {
 | 
						|
                this.perform(event, key)
 | 
						|
            }
 | 
						|
            item.ontap = (event) => {
 | 
						|
                this.perform(event, key)
 | 
						|
            }
 | 
						|
            item.onmouseover = (event) => {
 | 
						|
                this.over(event, key)
 | 
						|
            }
 | 
						|
            item.onmouseout = (event) => {
 | 
						|
                this.out(event, key)
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        this.element.appendChild(this.notch)
 | 
						|
        this.parent.appendChild(this.element)
 | 
						|
        this.insertedNode = this.element
 | 
						|
        Elements.setStyle(this.element, this.defaultStyle())
 | 
						|
        this.layout()
 | 
						|
        return this
 | 
						|
    }
 | 
						|
 | 
						|
    /** Execute a menu command.
 | 
						|
     * @param {object} event - The trigger event.
 | 
						|
     * @param {string} key - The selected key.
 | 
						|
     */
 | 
						|
    perform(event, key) {
 | 
						|
        let func = this.commands[key]
 | 
						|
        if (this.autoClose) {
 | 
						|
            this.close()
 | 
						|
        }
 | 
						|
        setTimeout(
 | 
						|
            (event, key) => {
 | 
						|
                func(event, key)
 | 
						|
            },
 | 
						|
            20,
 | 
						|
            event,
 | 
						|
            key
 | 
						|
        )
 | 
						|
    }
 | 
						|
 | 
						|
    /** Update the menu item denoted by key.
 | 
						|
     * @param {string} key - The selected key.
 | 
						|
     * @param {boolean} highlight - Show the item highlighted.
 | 
						|
     */
 | 
						|
    update(key, highlight = false) {
 | 
						|
        let text = this.items[key]
 | 
						|
        text.style.color = highlight ? this.highlightColor : this.normalColor
 | 
						|
    }
 | 
						|
 | 
						|
    /** Mouse over handöer.
 | 
						|
     * @param {Event} event - The mouse event.
 | 
						|
     * @param {boolean} key - The selected key.
 | 
						|
     */
 | 
						|
    over(event, key) {
 | 
						|
        for (let k in this.items) {
 | 
						|
            this.update(k, k == key)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /** Mouse out handöer.
 | 
						|
     * @param {Event} event - The mouse event.
 | 
						|
     * @param {boolean} key - The selected key.
 | 
						|
     */
 | 
						|
    out(event, key) {
 | 
						|
        this.update(key)
 | 
						|
    }
 | 
						|
 | 
						|
    /** Shows the PopupMenu with the given commands at the specified point.
 | 
						|
     * @param {Object} commands - A dict object with command label strings as keys
 | 
						|
     *                            and command functions as values.
 | 
						|
     * @param {Point} point - The position as x, y coordinates {px}.
 | 
						|
     * @return {PopupMenu} this
 | 
						|
     */
 | 
						|
    showAt(commands, point) {
 | 
						|
        this.show(commands)
 | 
						|
        this.placeAt(point)
 | 
						|
        return this
 | 
						|
    }
 | 
						|
 | 
						|
    /** Convenient static methods to show and reuse a PopupMenu implemented
 | 
						|
     * as a class variable.
 | 
						|
     * @param {Object} commands - A dict object with command label strings as keys
 | 
						|
     *                            and command functions as values.
 | 
						|
     * @param {Point} point - The position as x, y coordinates {px}.
 | 
						|
     * @param {string} fontSize - Describes the font size as CSS value
 | 
						|
     * @param {number || string} padding - Describes the padding as CSS value
 | 
						|
     * @param {number || string} notchSize - Describes the size of the notch (callout) as CSS value
 | 
						|
     * @param {string} highlightColor  - The color of highlighted menu items as CSS value
 | 
						|
     * @param {string} backgroundColor  - The color of the background as CSS value
 | 
						|
     * @param {string} normalColor  - The color of normal menu items as CSS value
 | 
						|
     * @param {boolean} autoClose  - Autoclose the menu after selecting an item
 | 
						|
     */
 | 
						|
    static open(
 | 
						|
        commands,
 | 
						|
        point,
 | 
						|
        {
 | 
						|
            parent = null,
 | 
						|
            context = window,
 | 
						|
            fontSize = '1em',
 | 
						|
            fontFamily = 'Arial',
 | 
						|
            padding = 16,
 | 
						|
            zIndex = 1,
 | 
						|
            spacing = '0px',
 | 
						|
            switchPos = false,
 | 
						|
            notchSize = 10,
 | 
						|
            maxWidth = 800,
 | 
						|
            keepWithin = null,
 | 
						|
            backgroundColor = '#EEE',
 | 
						|
            normalColor = '#444',
 | 
						|
            highlightColor = 'black',
 | 
						|
            autoClose = true,
 | 
						|
        } = {}
 | 
						|
    ) {
 | 
						|
        let registered = Poppable.get(context)
 | 
						|
        if (registered) {
 | 
						|
            this.closePopup()
 | 
						|
            return
 | 
						|
        }
 | 
						|
        console.log('open', point)
 | 
						|
        let notchPosition = point.y < 50 && switchPos ? 'topCenter' : 'bottomCenter'
 | 
						|
        let popup = new PopupMenu({
 | 
						|
            parent,
 | 
						|
            fontSize,
 | 
						|
            padding,
 | 
						|
            zIndex,
 | 
						|
            spacing,
 | 
						|
            switchPos,
 | 
						|
            notchSize,
 | 
						|
            notchPosition,
 | 
						|
            maxWidth,
 | 
						|
            backgroundColor,
 | 
						|
            normalColor,
 | 
						|
            highlightColor,
 | 
						|
            notchPosition,
 | 
						|
            keepWithin,
 | 
						|
            autoClose,
 | 
						|
        })
 | 
						|
        popup.showAt(commands, point)
 | 
						|
        popup.register(context)
 | 
						|
        popup.closeEventListener = (e) => {
 | 
						|
            if (this.eventOutside(e)) this.closePopup(context)
 | 
						|
        }
 | 
						|
        if (autoClose) {
 | 
						|
            context.addEventListener('mousedown', popup.closeEventListener, true)
 | 
						|
            context.addEventListener('touchstart', popup.closeEventListener, true)
 | 
						|
            context.addEventListener('pointerdown', popup.closeEventListener, true)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    static eventOutside(e) {
 | 
						|
        return !Elements.hasClass(e.target, 'popupMenuItem')
 | 
						|
    }
 | 
						|
 | 
						|
    /** Convenient static methods to close the PopupMenu implemented
 | 
						|
     * as a class variable.
 | 
						|
     */
 | 
						|
    static closePopup(context = window) {
 | 
						|
        let registered = Poppable.get(context)
 | 
						|
        if (registered) {
 | 
						|
            registered.close()
 | 
						|
            context.removeEventListener('mousedown', registered.closeEventListener)
 | 
						|
            context.removeEventListener('touchstart', registered.closeEventListener)
 | 
						|
            context.removeEventListener('pointerdown', registered.closeEventListener)
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |