let overflow = null
const ITEM = 'item',
SEPARATOR = 'separator'
const dom = function(elem = '') {
return document.querySelector('.basicContext ' + elem)
}
const valid = function(item = {}) {
let emptyItem = (Object.keys(item).length===0 ? true : false)
if (emptyItem===true) item.type = SEPARATOR
if (item.type==null) item.type = ITEM
if (item.class==null) item.class = ''
if (item.visible!==false) item.visible = true
if (item.icon==null) item.icon = null
if (item.title==null) item.title = 'Undefined'
// Add disabled class when item disabled
if (item.disabled!==true) item.disabled = false
if (item.disabled===true) item.class += ' basicContext__item--disabled'
// Item requires a function when
// it's not a separator and not disabled
if (item.fn==null && item.type!==SEPARATOR && item.disabled===false) {
console.warn(`Missing fn for item '${ item.title }'`)
return false
}
return true
}
const buildItem = function(item, num) {
let html = '',
span = ''
// Parse and validate item
if (valid(item)===false) return ''
// Skip when invisible
if (item.visible===false) return ''
// Give item a unique number
item.num = num
// Generate span/icon-element
if (item.icon!==null) span = ``
// Generate item
if (item.type===ITEM) {
html = `
| ${ span }${ item.title } |
`
} else if (item.type===SEPARATOR) {
html = `
`
}
return html
}
const build = function(items) {
let html = ''
html += `
`
items.forEach((item, i) => html += buildItem(item, i))
html += `
`
return html
}
const getNormalizedEvent = function(e = {}) {
let pos = {
x : e.clientX,
y : e.clientY
}
if (e.type==='touchend' && (pos.x==null || pos.y==null)) {
// We need to capture clientX and clientY from original event
// when the event 'touchend' does not return the touch position
let touches = e.changedTouches
if (touches!=null&&touches.length>0) {
pos.x = touches[0].clientX
pos.y = touches[0].clientY
}
}
// Position unknown
if (pos.x==null || pos.x < 0) pos.x = 0
if (pos.y==null || pos.y < 0) pos.y = 0
return pos
}
const getPosition = function(e, context) {
// Get the click position
let normalizedEvent = getNormalizedEvent(e)
// Set the initial position
let x = normalizedEvent.x,
y = normalizedEvent.y
// Get size of browser
let browserSize = {
width : window.innerWidth,
height : window.innerHeight
}
// Get size of context
let contextSize = {
width : context.offsetWidth,
height : context.offsetHeight
}
// Fix position based on context and browser size
if ((x + contextSize.width) > browserSize.width) x = x - ((x + contextSize.width) - browserSize.width)
if ((y + contextSize.height) > browserSize.height) y = y - ((y + contextSize.height) - browserSize.height)
// Make context scrollable and start at the top of the browser
// when context is higher than the browser
if (contextSize.height > browserSize.height) {
y = 0
context.classList.add('basicContext--scrollable')
}
// Calculate the relative position of the mouse to the context
let rx = normalizedEvent.x - x,
ry = normalizedEvent.y - y
return { x, y, rx, ry }
}
const bind = function(item = {}) {
if (item.fn==null) return false
if (item.visible===false) return false
if (item.disabled===true) return false
dom(`td[data-num='${ item.num }']`).onclick = item.fn
dom(`td[data-num='${ item.num }']`).oncontextmenu = item.fn
return true
}
const show = function(items, e, fnClose, fnCallback) {
// Build context
let html = build(items)
// Add context to the body
document.body.insertAdjacentHTML('beforeend', html)
// Save current overflow and block scrolling of site
if (overflow==null) {
overflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
}
// Cache the context
let context = dom()
// Calculate position
let position = getPosition(e, context)
// Set position
context.style.left = `${ position.x }px`
context.style.top = `${ position.y }px`
context.style.transformOrigin = `${ position.rx }px ${ position.ry }px`
context.style.opacity = 1
// Close fn fallback
if (fnClose==null) fnClose = close
// Bind click on background
context.parentElement.onclick = fnClose
context.parentElement.oncontextmenu = fnClose
// Bind click on items
items.forEach(bind)
// Do not trigger default event or further propagation
if (typeof e.preventDefault === 'function') e.preventDefault()
if (typeof e.stopPropagation === 'function') e.stopPropagation()
// Call callback when a function
if (typeof fnCallback === 'function') fnCallback()
return true
}
const visible = function() {
let elem = dom()
if (elem==null || elem.length===0) return false
else return true
}
const close = function() {
if (visible()===false) return false
let container = document.querySelector('.basicContextContainer')
container.parentElement.removeChild(container)
// Reset overflow to its original value
if (overflow!=null) {
document.body.style.overflow = overflow
overflow = null
}
return true
}
return {
ITEM,
SEPARATOR,
show,
visible,
close
}