import Delegate from 'dom-delegate'
import * as R from 'ramda'
/**
* @namespace createEvents
*/
// do not use an array function... we need to be able to bind
export const createEvents = R.curry(function(context, obj) {
const events = Object.entries(obj).map(([key, fn]) => {
const eventAndNode = R.compose(R.map(R.trim), R.split(' '))(key)
const capture = !!R.compose(R.length, R.match(/(blur|mouse)/g), R.head)(
eventAndNode
)
const funk = typeof fn === 'string' ? this[fn] : fn
return [...eventAndNode, funk, capture]
})
let $delegate
let emit
const handleFunctions = R.curry((evt, transform, fns) => {
R.compose(
R.forEach(event => {
$delegate[transform](...event)
}),
R.map(item =>
R.find(([a, b]) => {
// alls add/remove of events without a selector - things like keydown/keyup
if (typeof b === 'function') {
return a === item
}
return [a, b].join(' ') === item
})(evt)
)
)(fns)
})(events)
return {
/**
* @memberof createEvents
* @function attachAll
* @param {HTMLElement} root - the element to delegate to
*/
attachAll(root = context) {
$delegate = $delegate || new Delegate(root)
try {
R.forEach(event => $delegate.on(...event))(events)
} catch (err) {
// eslint-disable-next-line
console.error(
'Handler must be a type of Function, careful with arrow functions, they will need to be above the events object:',
err
)
}
},
/**
* @memberof createEvents
* @function attach
* @param {Array} fns - an array of cuntions
* @param {HTMLElement} root - the element to delegate to
* @return {void}
*/
attach(fns, root = context) {
$delegate = $delegate || new Delegate(root)
handleFunctions('on', fns)
},
/**
* @memberof createEvents
* @function remove
* @param {Array} fns - an array of cuntions
* @return {void}
*/
remove(fns) {
if (!$delegate) return
handleFunctions('off', fns)
},
/**
* @memberof createEvents
* @function destroy
* @return {void}
*/
destroy() {
if (!$delegate) return
R.forEach(event => $delegate.off(...event))(events)
},
/**
* @memberof createEvents
* @function destroy
* @param {HTMLElement} root - the element to trigger an event on
* @param {String} event - the name of the event to trigger
* @return {void}
*/
emit($node, event) {
emit = emit || document.createEvent('HTMLEvents')
emit.initEvent(event, true, false)
$node.dispatchEvent(emit)
}
}
})
/**
* Create a router
* @mixin EventsMixin
* @description class used to manage adding/removing delegated dom events
*
* @example
* import Behaviour, { mix } from '@/core/Behaviour'
* import {
* EventsMixin,
* } from '@/core/modules/'
*
* export default class ExampleWithAllTheThings extends mix(Behaviour).with(
* EventsMixin
* ) {
* events = {
* 'click [data-link]': 'onClick'
* }
*
* mount = () => {
* // attach all the events
* this.$$events.attachAll()
* // attach events by key
* this.$$events.attach(['click [data-link]'])
* // remove events by key
* this.$$events.remove(['click [data-link]'])
* // destroy all the events
* this.$$events.destroy()
* }
*
* onClick = (e, elm) => {
* e.preventDefault()
* elm.classList.toggle('huzzah')
* }
* }
* @return {EventsMixin}
*/
export const EventsMixin = superclass =>
class extends superclass {
init() {
this.$$events = createEvents.call(this, this.$el, this.events)
if (super.init) super.init()
return this
}
destroy() {
this.$$events.destroy()
if (super.destroy) super.destroy()
}
}