import Data from './Data.js'
import {
camelCase,
hasProp,
defineProperty,
extend,
empty,
not,
isFunction,
isObject,
normName,
strToArr
} from "../Utils/Utils.js"
const ListEvents = [
'blur', 'focus', 'resize', 'scroll', 'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
'mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'change', 'select', 'submit', 'keydown',
'keypress', 'keyup', 'contextmenu', 'touchstart', 'touchend', 'touchmove', 'touchcancel'
]
/**
* Maneja y manipula los Eventos del DOM
* @namespace Event
* @memberOf module:Core
* @requires Utils
* @class
* @tutorial Events
*/
export default class Event {
_ds = new Data()
_eventName = "fsEvent"
constructor() {
}
/**
* Añada un Evento Escucha al elemento
* @private
* @memberOf Core.Event
* @param {Element} el El Elemento
* @param {String} event Nombre del Evento
* @param {Function} fn Función a asignar
* @param {Boolean} capture use capture
*/
_add(el, event, fn, useCapture = false) {
return el.addEventListener(event, fn, useCapture)
}
/**
* Remueve un evento asignado
* @private
* @memberOf Core.Event
* @param {Element} el El Elemento
* @param {String} event el nombre del evento
* @param {Function} fn Función asignada
* @param {(Boolean|Object)} capture Use capture ó opciones de captura
*/
_remove(el, event, fn, capture) {
if (not(capture)) {
capture = true
}
return el.removeEventListener(event, fn, capture)
}
/**
* Dispara un Evento
* @private
* @memberOf Core.Event
* @param {Element} el El elemento
* @param {(Event|CustomEvent)} newEvent Nombre del Evento a disparar
* @return {Element}
*/
_dispatch(el, newEvent) {
return el.dispatchEvent(newEvent)
}
/**
* Crea un Evento
* @private
* @memberOf Core.Event
* @param {string} name Nombre del Evento
* @param {(Boolean|String|Object|Array|Fascino)} customEventInit Acciones del evento capturarle, estas acciones se captura con e.detail
*/
_createEvent(name, customEventInit = {}) {
var o;
if(hasProp(customEventInit, 'detail')) {
o = extend({},{
bubbles: true,
cancelable: true,
detail: null
}, customEventInit)
} else {
o = {
bubbles: true,
cancelable: true,
detail: customEventInit
}
}
return new CustomEvent(name, o)
}
/**
* Almacena los eventos asignado a cada elemento en memoria del elemento
* @private
* @memberOf Core.Event
* @param {Element} el El Elemento
* @param {String} name Nombre del Evento
* @param {(Boolean|String|Object|Array|Fascino|Function)} data información del evento
* @return {Object} Lista de Eventos Asignado
*/
_setData(el, event, data) {
if (this._ds.acceptData(el) && el.self !== window) {
let dataEv = this._ds.has(el, this._eventName) ? this._ds.get(el, this._eventName) : this._ds.access(el, this._eventName, {}),
name = camelCase(event),
nameToArr = strToArr(event, '.'),
e = {}
e[this._eventName] = dataEv
e[this._eventName][name] = name in dataEv === true ? e[this._eventName][name] : [];
e[this._eventName][name].push(data)
return this._ds.set(el, e)
}
}
/**
* Obtiene información sobre un evento asignado
* @private
* @memberOf Core.Event
* @param {Element} el El Elemento
* @param {String} name Nombre del Evento, se puede asignar namespace ejm: click.bs
* @param {Number} index posición del evento
* @return {(Object|Array)}
*/
_getData(el, name = null, index = -1) {
var evList = this._ds.get(el, this._eventName)
if(not(evList)) {
return {}
}
if (not(name)) {
return evList
}
name = camelCase(name)
if (name in evList === false) {
return false
}
return index === -1 ? evList[name] : evList[name][index]
}
/**
* Verifica que un evento exista
* @private
* @memberOf Core.Event
* @param {Element} el El elemento
* @param {String} name Nombre
* @param {Number} index Indice del evento
* @return {Boolean}
*/
_hasData(el, name, index = -1) {
if (this._ds.has(el, this._eventName)) {
var evList = this._ds.get(el, this._eventName)
if (!empty(evList)) {
if (camelCase(name) in evList) {
return index > -1 ? evList[camelCase(name)].indexOf(index) > -1 : true
}
}
}
return false
}
/**
* Remueve un Evento Asignado
* @private
* @memberOf Core.Event
* @param {Element} el Elemento
* @param {String} name Nombre
* @param {Number} index Indice del Evento
* @return {Boolean}
*/
_removeData(el, name, index = -1) {
if (this._ds.has(el, this._eventName)) {
let ds = this._getData(el, name, index)
if (not(name)) {
this._ds.set(el, this._eventName, {})
return true
}
let camelName = camelCase(name)
if (camelName in ds) {
if (index > -1) {
delete ds[camelName][index]
} else {
delete ds[camelName]
}
this._ds.set(el, this._eventName, d)
return this
}
}
return false
}
/**
* Remueve todos los Eventos de todos los elementos seleccionado
* @private
* @memberOf Core.Event
* @return {Core.Event}
*/
_off(el) {
this._ds.remove(el, this._eventName)
return this
}
/**
* Obtiene los NameSpaces del Evento
* @private
* @memberOf Core.Event
* @param {Array} NS Nombre separado en array
* @param {Boolean} onlyNS Si solo se retorna el NS ó se retorna Nombre y NS
* @return {(String|Object)}
*/
_getNS(NS, onlyNS = true) {
let name = NS.shift(),
ns = NS.join('.')
return onlyNS ? ns : {
name,
ns
}
}
// Public
/**
* Obtiene los eventos almacenados
* @public
* @memberOf Core.Event
* @see Core.Event._getData
* @return {Object}
*/
getEvents(el, name = null, index = -1) {
return this._getData(el, name, index)
}
/**
* Ejecuta los eventos dados
* @memberOf Core.Event
* @public
* @param {Element} el El elemento
* @param {(String|Array)} eventList Lista de eventos con su namespace si es necesario
* @param {String} selector Selector a iterar o null
* @param {Function} callback Función a ejecutar
* @param {Object} options Opciones de addEventListiner
* @return {(Element|Object|Core.Event)}
*/
on(el, eventList, selector, callback, options) {
if (not(el)) { return this }
if (isFunction(selector)) {
options = callback
callback = selector
selector = undefined
}
if (!isObject(options)) {
options = {}
}
var EVL = strToArr(eventList, ' ')
EVL.forEach((e) => {
let nameAndNs = strToArr(e, '.'),
nameAndNSObj = this._getNS(nameAndNs, false),
ns = nameAndNSObj.ns,
name = nameAndNSObj.name,
handler = (ev) => {
let target = ev.target
if(!selector) {
callback.call(el, ev)
} else {
while (target && target !== el) {
if (Element.prototype.matches.call(target, selector) ) {
callback.call(target, ev)
if (ev.isPropagationStopped) {
ev.stopImmediatePropagation()
break
}
}
target = target.parentNode
}
}
if (!not(options.once)) {
this.off(el, eventList, selector, options)
}
}
defineProperty(
handler,
'name',
{
value: callback.name && callback.name !== '' ? callback.name : `func_event_${name}_${not(ns) ? new Date().getTime() : ns}`
}
)
let NameEvents = ListEvents.indexOf(name) > -1 ? name : e
this._add(el, NameEvents, handler, !not(options.capture) ? options.capture : false)
this._setData(el, e, {
event: e,
handler: handler,
selector: selector,
ns: ns,
options: !not(options) ? options : false
})
})
return el
}
/**
* Ejecuta solo el primer eventos del elemento
* @memberOf Core.Event
* @public
* @param {Element} el El Elemento
* @param {String} events Eventos
* @param {String} sel Selector o null
* @param {Function} handler Función a ejecutar
* @param {Object} options Opciones de addEventListiner
* @return {(Element|Object|Core.Event)}
*/
one(el, events, sel, handler, options) {
if (!isObject(options)) {
options = {}
}
options.once = true
return this.on.apply(this, [el, events, sel, handler, options])
}
/**
* Remueve los Eventos de los elementos dado
* @memberOf Core.Event
* @public
* @param {Element} el Elemento
* @param {String} eventsList Lista de Eventos
* @param {String} sel Selecotr o null
* @param {Object} options Opciones de removeEventListiner
* @return {(Element|Object|Core.Event)}
*/
off(el, eventsList, sel, options, index = 0) {
if (isObject(sel)) {
options = sel
sel = null
}
if(!isNaN(options)) {
index = options
options = {}
}
if (!isObject(options)) {
options = {}
}
let Dev = this._getData(el)
if (not(eventsList) || eventsList.toLowerCase() === 'all' || eventsList === '*') {
let _this = this
if (Dev) {
for(let prop in Dev) {
if (hasProp(Dev, prop)) {
let e = Dev[prop]
e.forEach((i) => {
_this._remove(el, i.event, i.handler, i.options)
})
}
}
this._off()
}
return el
}
var EvL = strToArr(eventsList, ' ');
EvL.forEach((e) => {
let nMap = strToArr(e, '.'),
evMap = nMap.length > 1 ? this._getNS(nMap, true) : {name: nMap[0], ns: ''},
name = normName(evMap.name),
ns = options.ns ? options.ns : evMap.ns
if (hasProp(Dev, name)) {
let ev = Dev[name][index]
this._remove(el, ev.event, ev.handler, ev.options)
this._removeData(el, e, index)
}
})
return el
}
/**
* Dispara un Evento
* @public
* @memberOf Core.Event
* @param {Element} el Elemento HTMLELEMENT
* @param {String} event Nombre del Evento
* @return {Element}
*/
fire(el, event) {
return this._dispatch(el, event)
}
/**
* Crea un Evento, versión publica
* @public
* @memberOf Core.Event
* @see Core.Event._createEvent
*/
createEv(name, data) {
return this._createEvent(name, data)
}
}