244 lines
8.2 KiB
JavaScript
244 lines
8.2 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(key)
|
||
|
}
|
||
|
item.ontap = event => {
|
||
|
this.perform(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 {string} key - The selected key.
|
||
|
*/
|
||
|
perform(key) {
|
||
|
let func = this.commands[key]
|
||
|
if (this.autoClose) {
|
||
|
this.close()
|
||
|
}
|
||
|
setTimeout(() => {
|
||
|
func.call()
|
||
|
}, 20)
|
||
|
}
|
||
|
|
||
|
/** 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)
|
||
|
}
|
||
|
}
|
||
|
}
|