Skip to content

Instantly share code, notes, and snippets.

@towerofnix
Last active November 2, 2016 18:50
Show Gist options
  • Save towerofnix/a6e7a74ecc32e13fbda6a30d92f9fd7e to your computer and use it in GitHub Desktop.
Save towerofnix/a6e7a74ecc32e13fbda6a30d92f9fd7e to your computer and use it in GitHub Desktop.
Basic menu items
const page = document.getElementById('page')
const initEditableElement = function(el) {
const menu = createContextMenu('Demo menu', [
['Insert new div', [
['Before', () => {
alert('Insert before')
}],
['After', () => {
alert('Insert after')
}]
]],
['Magical new menu item', menu => {
menu.addMenuItem(['Magic!', () => alert('Magic')])
menu.preventClose()
}]
])
el.addEventListener('contextmenu', evt => {
menu.invoke(evt.clientX, evt.clientY)
evt.preventDefault()
})
}
initEditableElement(page)
body {
font-family: 'Helvetica Neue';
}
.context-menu {
position: fixed;
display: none;
min-width: 100px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.7);
color: #EEE;
font-size: 0.8rem;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
overflow: hidden;
}
.context-menu.open {
display: inline-block;
}
.context-menu-title {
background: rgba(0, 0, 0, 0.3);
padding: 2px 5px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.context-menu-title .context-menu-close {
float: right;
font-family: 'Courier New';
padding: 0 3px;
border-radius: 2px;
}
.context-menu-title .context-menu-close:hover {
background: rgba(255, 255, 255, 0.1);
}
.context-menu-content, .context-menu-content ul {
padding: 0;
list-style: none;
margin: 0;
}
.context-menu-content li > span {
padding: 3px;
display: inline-block;
}
.context-menu-content > li > span {
padding-left: 5px;
}
.context-menu-content > li > ul > li > span {
padding-left: 15px;
}
.context-menu-content > li:not(:last-child) {
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
.context-menu-content > li > ul > li:not(:last-child) {
border-bottom: 1px solid rgba(127, 127, 127, 0.3);
}
.context-menu-content li:not(.no-action):hover {
background: rgba(0, 0, 0, 0.4);
}
'use strict'
const createContextMenu = function(title, items) {
let shouldClose = false
const menuObj = {
closeMenu: () => {
menu.classList.remove('open')
},
preventClose: () => {
shouldClose = false
},
addMenuItem: item => {
ul.appendChild(handleItem(item))
},
invoke: (x, y) => {
menu.classList.add('open')
menu.style.left = x + 'px'
menu.style.top = y + 'px'
}
}
const handleItem = (item) => {
const li = document.createElement('li')
const span = document.createElement('span')
span.appendChild(document.createTextNode(item[0]))
li.appendChild(span)
if (item[1] instanceof Array) {
// TODO: optionally have an event for clicking on a submenu, not sure
// if this is really necessary or good, or the syntax on how to do this,
// though.. Probably [name, (optional fn), array].
li.classList.add('no-action')
const ul = document.createElement('ul')
li.appendChild(ul)
for (let subitem of item[1]) {
ul.appendChild(handleItem(subitem))
}
} else if (item[1] instanceof Function) {
const callback = item[1]
li.addEventListener('click', () => {
shouldClose = true
callback(menuObj)
if (shouldClose) {
menuObj.closeMenu()
}
})
}
return li
}
const menu = document.createElement('div')
menu.classList.add('context-menu')
const menuTitle = document.createElement('div')
menuTitle.classList.add('context-menu-title')
menuTitle.appendChild(document.createTextNode(title))
menu.appendChild(menuTitle)
const close = document.createElement('div')
close.classList.add('context-menu-close')
close.appendChild(document.createTextNode('x'))
close.addEventListener('click', menuObj.closeMenu)
menuTitle.appendChild(close)
const ul = document.createElement('ul')
ul.classList.add('context-menu-content')
for (let item of items) {
ul.appendChild(handleItem(item))
}
menu.appendChild(ul)
document.body.appendChild(menu)
return menuObj
}
@towerofnix
Copy link
Author

Demo image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment