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', 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, 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) } } }