From 7c1d0a1097657fb604a69cd47375a7e94c0ced93 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Wed, 20 Sep 2017 14:19:10 +0200 Subject: [PATCH] Wrap our objects into IIFE --- js/src/dom/data.js | 96 +++--- js/src/dom/eventHandler.js | 568 ++++++++++++++++++----------------- js/src/dom/selectorEngine.js | 132 ++++---- 3 files changed, 419 insertions(+), 377 deletions(-) diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 68908d8f22..f3e4386fcc 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -5,56 +5,66 @@ * -------------------------------------------------------------------------- */ -const mapData = (() => { - const storeData = {} - let id = 1 - return { - set(element, key, data) { - if (typeof element.key === 'undefined') { - element.key = { - key, - id +const Data = (() => { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + + const mapData = (() => { + const storeData = {} + let id = 1 + return { + set(element, key, data) { + if (typeof element.key === 'undefined') { + element.key = { + key, + id + } + } + + storeData[id] = data + id++ + }, + get(element, key) { + if (typeof element === 'undefined' || typeof element.key === 'undefined') { + return null + } + + const keyProperties = element.key + if (keyProperties.key === key) { + return storeData[keyProperties.id] + } + return null + }, + delete(element, key) { + if (typeof element.key === 'undefined') { + return + } + + const keyProperties = element.key + if (keyProperties.key === key) { + delete storeData[keyProperties.id] + delete element.key } } + } + })() - storeData[id] = data - id++ + return { + setData(instance, key, data) { + mapData.set(instance, key, data) }, - get(element, key) { - if (typeof element === 'undefined' || typeof element.key === 'undefined') { - return null - } - - const keyProperties = element.key - if (keyProperties.key === key) { - return storeData[keyProperties.id] - } - return null + getData(instance, key) { + return mapData.get(instance, key) }, - delete(element, key) { - if (typeof element.key === 'undefined') { - return - } - - const keyProperties = element.key - if (keyProperties.key === key) { - delete storeData[keyProperties.id] - delete element.key - } + removeData(instance, key) { + mapData.delete(instance, key) } } })() -const Data = { - setData(instance, key, data) { - mapData.set(instance, key, data) - }, - getData(instance, key) { - return mapData.get(instance, key) - }, - removeData(instance, key) { - mapData.delete(instance, key) - } -} - export default Data diff --git a/js/src/dom/eventHandler.js b/js/src/dom/eventHandler.js index b72684f819..7854401c37 100644 --- a/js/src/dom/eventHandler.js +++ b/js/src/dom/eventHandler.js @@ -7,325 +7,347 @@ import Util from '../util' * -------------------------------------------------------------------------- */ -// defaultPrevented is broken in IE. -// https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called -const workingDefaultPrevented = (() => { - const e = document.createEvent('CustomEvent') - e.initEvent('Bootstrap', true, true) - e.preventDefault() - return e.defaultPrevented -})() +const EventHandler = (() => { -let defaultPreventedPreservedOnDispatch = true - -// CustomEvent polyfill for IE (see: https://mzl.la/2v76Zvn) -if (typeof window.CustomEvent !== 'function') { - window.CustomEvent = (event, params) => { - params = params || { - bubbles: false, - cancelable: false, - detail: null - } - const evt = document.createEvent('CustomEvent') - evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) - if (!workingDefaultPrevented) { - const origPreventDefault = Event.prototype.preventDefault - evt.preventDefault = () => { - if (!evt.cancelable) { - return - } - - origPreventDefault.call(evt) - Object.defineProperty(evt, 'defaultPrevented', { - get() { - return true - }, - configurable: true - }) - } - } - return evt - } - - window.CustomEvent.prototype = window.Event.prototype -} else { - // MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached - defaultPreventedPreservedOnDispatch = (() => { - const e = new CustomEvent('Bootstrap', { - cancelable: true - }) - - const element = document.createElement('div') - element.addEventListener('Bootstrap', () => null) + /** + * ------------------------------------------------------------------------ + * Polyfills + * ------------------------------------------------------------------------ + */ + // defaultPrevented is broken in IE. + // https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called + const workingDefaultPrevented = (() => { + const e = document.createEvent('CustomEvent') + e.initEvent('Bootstrap', true, true) e.preventDefault() - element.dispatchEvent(e) return e.defaultPrevented })() -} -// Event constructor shim -if (!window.Event || typeof window.Event !== 'function') { - const origEvent = window.Event - window.Event = (inType, params) => { - params = params || {} - const e = document.createEvent('Event') - e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable)) - return e - } - window.Event.prototype = origEvent.prototype -} + let defaultPreventedPreservedOnDispatch = true -const namespaceRegex = /[^.]*(?=\..*)\.|.*/ -const stripNameRegex = /\..*/ -const keyEventRegex = /^key/ -const stripUidRegex = /::\d+$/ + // CustomEvent polyfill for IE (see: https://mzl.la/2v76Zvn) + if (typeof window.CustomEvent !== 'function') { + window.CustomEvent = (event, params) => { + params = params || { + bubbles: false, + cancelable: false, + detail: null + } + const evt = document.createEvent('CustomEvent') + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) + if (!workingDefaultPrevented) { + const origPreventDefault = Event.prototype.preventDefault + evt.preventDefault = () => { + if (!evt.cancelable) { + return + } -// Events storage -const eventRegistry = {} -let uidEvent = 1 - -function getUidEvent(element, uid) { - return element.uidEvent = uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++ -} - -function getEvent(element) { - const uid = getUidEvent(element) - return eventRegistry[uid] = eventRegistry[uid] || {} -} - -const nativeEvents = [ - 'click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', - 'mousewheel', 'DOMMouseScroll', - 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', - 'keydown', 'keypress', 'keyup', - 'orientationchange', - 'touchstart', 'touchmove', 'touchend', 'touchcancel', - 'gesturestart', 'gesturechange', 'gestureend', - 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', - 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', - 'error', 'abort', 'scroll' -] - -const customEvents = { - mouseenter: 'mouseover', - mouseleave: 'mouseout' -} - -function fixEvent(event) { - // Add which for key events - if (event.which === null && keyEventRegex.test(event.type)) { - event.which = event.charCode !== null ? event.charCode : event.keyCode - } - return event -} - -function bootstrapHandler(element, fn) { - return function (event) { - event = fixEvent(event) - return fn.apply(element, [event]) - } -} - -function bootstrapDelegationHandler(element, selector, fn) { - return function (event) { - event = fixEvent(event) - const domElements = element.querySelectorAll(selector) - for (let target = event.target; target && target !== this; target = target.parentNode) { - for (let i = domElements.length; i--;) { - if (domElements[i] === target) { - return fn.apply(target, [event]) + origPreventDefault.call(evt) + Object.defineProperty(evt, 'defaultPrevented', { + get() { + return true + }, + configurable: true + }) } } + return evt } - // To please ESLint - return null + + window.CustomEvent.prototype = window.Event.prototype + } else { + // MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached + defaultPreventedPreservedOnDispatch = (() => { + const e = new CustomEvent('Bootstrap', { + cancelable: true + }) + + const element = document.createElement('div') + element.addEventListener('Bootstrap', () => null) + + e.preventDefault() + element.dispatchEvent(e) + return e.defaultPrevented + })() } -} -function removeHandler(element, events, typeEvent, handler) { - const uidEvent = handler.uidEvent - const fn = events[typeEvent][uidEvent] - element.removeEventListener(typeEvent, fn, fn.delegation) - delete events[typeEvent][uidEvent] -} - -function removeNamespacedHandlers(element, events, typeEvent, namespace) { - const storeElementEvent = events[typeEvent] || {} - for (const handlerKey in storeElementEvent) { - if (!Object.prototype.hasOwnProperty.call(storeElementEvent, handlerKey)) { - continue + // Event constructor shim + if (!window.Event || typeof window.Event !== 'function') { + const origEvent = window.Event + window.Event = (inType, params) => { + params = params || {} + const e = document.createEvent('Event') + e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable)) + return e } + window.Event.prototype = origEvent.prototype + } - if (handlerKey.indexOf(namespace) > -1) { - removeHandler(element, events, typeEvent, storeElementEvent[handlerKey].originalHandler) + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const TransitionEndEvent = { + WebkitTransition : 'webkitTransitionEnd', + transition : 'transitionend' + } + const namespaceRegex = /[^.]*(?=\..*)\.|.*/ + const stripNameRegex = /\..*/ + const keyEventRegex = /^key/ + const stripUidRegex = /::\d+$/ + const eventRegistry = {} // Events storage + let uidEvent = 1 + const customEvents = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + } + const nativeEvents = [ + 'click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', + 'mousewheel', 'DOMMouseScroll', + 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', + 'keydown', 'keypress', 'keyup', + 'orientationchange', + 'touchstart', 'touchmove', 'touchend', 'touchcancel', + 'gesturestart', 'gesturechange', 'gestureend', + 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', + 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', + 'error', 'abort', 'scroll' + ] + + /** + * ------------------------------------------------------------------------ + * Private methods + * ------------------------------------------------------------------------ + */ + + + function getUidEvent(element, uid) { + return element.uidEvent = uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++ + } + + function getEvent(element) { + const uid = getUidEvent(element) + return eventRegistry[uid] = eventRegistry[uid] || {} + } + + function fixEvent(event) { + // Add which for key events + if (event.which === null && keyEventRegex.test(event.type)) { + event.which = event.charCode !== null ? event.charCode : event.keyCode + } + return event + } + + function bootstrapHandler(element, fn) { + return function (event) { + event = fixEvent(event) + return fn.apply(element, [event]) } } -} -const EventHandler = { - on(element, originalTypeEvent, handler, delegationFn) { - if (typeof originalTypeEvent !== 'string' || - (typeof element === 'undefined' || element === null)) { - return - } - - const delegation = typeof handler === 'string' - const originalHandler = delegation ? delegationFn : handler - - // allow to get the native events from namespaced events ('click.bs.button' --> 'click') - let typeEvent = originalTypeEvent.replace(stripNameRegex, '') - - const custom = customEvents[typeEvent] - if (custom) { - typeEvent = custom - } - - const isNative = nativeEvents.indexOf(typeEvent) > -1 - if (!isNative) { - typeEvent = originalTypeEvent - } - const events = getEvent(element) - const handlers = events[typeEvent] || (events[typeEvent] = {}) - const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) - if (handlers[uid]) { - return - } - - const fn = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn) - fn.isDelegation = delegation - handlers[uid] = fn - originalHandler.uidEvent = uid - fn.originalHandler = originalHandler - element.addEventListener(typeEvent, fn, delegation) - }, - - one(element, event, handler) { - function complete(e) { - EventHandler.off(element, event, complete) - handler.apply(element, [e]) - } - EventHandler.on(element, event, complete) - }, - - off(element, originalTypeEvent, handler) { - if (typeof originalTypeEvent !== 'string' || - (typeof element === 'undefined' || element === null)) { - return - } - - const events = getEvent(element) - let typeEvent = originalTypeEvent.replace(stripNameRegex, '') - - const inNamespace = typeEvent !== originalTypeEvent - const custom = customEvents[typeEvent] - if (custom) { - typeEvent = custom - } - - const isNative = nativeEvents.indexOf(typeEvent) > -1 - if (!isNative) { - typeEvent = originalTypeEvent - } - - if (typeof handler !== 'undefined') { - // Simplest case: handler is passed, remove that listener ONLY. - if (!events || !events[typeEvent]) { - return - } - - removeHandler(element, events, typeEvent, handler) - return - } - - const isNamespace = originalTypeEvent.charAt(0) === '.' - if (isNamespace) { - for (const elementEvent in events) { - if (!Object.prototype.hasOwnProperty.call(events, elementEvent)) { - continue + function bootstrapDelegationHandler(element, selector, fn) { + return function (event) { + event = fixEvent(event) + const domElements = element.querySelectorAll(selector) + for (let target = event.target; target && target !== this; target = target.parentNode) { + for (let i = domElements.length; i--;) { + if (domElements[i] === target) { + return fn.apply(target, [event]) + } } - - removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.substr(1)) } + // To please ESLint + return null } + } + function removeHandler(element, events, typeEvent, handler) { + const uidEvent = handler.uidEvent + const fn = events[typeEvent][uidEvent] + element.removeEventListener(typeEvent, fn, fn.delegation) + delete events[typeEvent][uidEvent] + } + + function removeNamespacedHandlers(element, events, typeEvent, namespace) { const storeElementEvent = events[typeEvent] || {} - for (const keyHandlers in storeElementEvent) { - if (!Object.prototype.hasOwnProperty.call(storeElementEvent, keyHandlers)) { + for (const handlerKey in storeElementEvent) { + if (!Object.prototype.hasOwnProperty.call(storeElementEvent, handlerKey)) { continue } - const handlerKey = keyHandlers.replace(stripUidRegex, '') - if (!inNamespace || originalTypeEvent.indexOf(handlerKey) > -1) { - removeHandler(element, events, typeEvent, storeElementEvent[keyHandlers].originalHandler) + if (handlerKey.indexOf(namespace) > -1) { + removeHandler(element, events, typeEvent, storeElementEvent[handlerKey].originalHandler) } } - }, + } - trigger(element, event, args) { - if (typeof event !== 'string' || + return { + on(element, originalTypeEvent, handler, delegationFn) { + if (typeof originalTypeEvent !== 'string' || + (typeof element === 'undefined' || element === null)) { + return + } + + const delegation = typeof handler === 'string' + const originalHandler = delegation ? delegationFn : handler + + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + let typeEvent = originalTypeEvent.replace(stripNameRegex, '') + + const custom = customEvents[typeEvent] + if (custom) { + typeEvent = custom + } + + const isNative = nativeEvents.indexOf(typeEvent) > -1 + if (!isNative) { + typeEvent = originalTypeEvent + } + const events = getEvent(element) + const handlers = events[typeEvent] || (events[typeEvent] = {}) + const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) + if (handlers[uid]) { + return + } + + const fn = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn) + fn.isDelegation = delegation + handlers[uid] = fn + originalHandler.uidEvent = uid + fn.originalHandler = originalHandler + element.addEventListener(typeEvent, fn, delegation) + }, + + one(element, event, handler) { + function complete(e) { + EventHandler.off(element, event, complete) + handler.apply(element, [e]) + } + EventHandler.on(element, event, complete) + }, + + off(element, originalTypeEvent, handler) { + if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) { - return null - } + return + } - const typeEvent = event.replace(stripNameRegex, '') - const inNamespace = event !== typeEvent - const isNative = nativeEvents.indexOf(typeEvent) > -1 + const events = getEvent(element) + let typeEvent = originalTypeEvent.replace(stripNameRegex, '') - const $ = Util.jQuery - let jQueryEvent + const inNamespace = typeEvent !== originalTypeEvent + const custom = customEvents[typeEvent] + if (custom) { + typeEvent = custom + } - let bubbles = true - let nativeDispatch = true - let defaultPrevented = false + const isNative = nativeEvents.indexOf(typeEvent) > -1 + if (!isNative) { + typeEvent = originalTypeEvent + } - if (inNamespace && typeof $ !== 'undefined') { - jQueryEvent = new $.Event(event, args) + if (typeof handler !== 'undefined') { + // Simplest case: handler is passed, remove that listener ONLY. + if (!events || !events[typeEvent]) { + return + } - $(element).trigger(jQueryEvent) - bubbles = !jQueryEvent.isPropagationStopped() - nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() - defaultPrevented = jQueryEvent.isDefaultPrevented() - } + removeHandler(element, events, typeEvent, handler) + return + } - let evt = null + const isNamespace = originalTypeEvent.charAt(0) === '.' + if (isNamespace) { + for (const elementEvent in events) { + if (!Object.prototype.hasOwnProperty.call(events, elementEvent)) { + continue + } - if (isNative) { - evt = document.createEvent('HTMLEvents') - evt.initEvent(typeEvent, bubbles, true) - } else { - evt = new CustomEvent(event, { - bubbles, - cancelable: true - }) - } + removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.substr(1)) + } + } - // merge custom informations in our event - if (typeof args !== 'undefined') { - evt = Util.extend(evt, args) - } + const storeElementEvent = events[typeEvent] || {} + for (const keyHandlers in storeElementEvent) { + if (!Object.prototype.hasOwnProperty.call(storeElementEvent, keyHandlers)) { + continue + } - if (defaultPrevented) { - evt.preventDefault() + const handlerKey = keyHandlers.replace(stripUidRegex, '') + if (!inNamespace || originalTypeEvent.indexOf(handlerKey) > -1) { + removeHandler(element, events, typeEvent, storeElementEvent[keyHandlers].originalHandler) + } + } + }, - if (!defaultPreventedPreservedOnDispatch) { - Object.defineProperty(evt, 'defaultPrevented', { - get: () => true + trigger(element, event, args) { + if (typeof event !== 'string' || + (typeof element === 'undefined' || element === null)) { + return null + } + + const typeEvent = event.replace(stripNameRegex, '') + const inNamespace = event !== typeEvent + const isNative = nativeEvents.indexOf(typeEvent) > -1 + + const $ = Util.jQuery + let jQueryEvent + + let bubbles = true + let nativeDispatch = true + let defaultPrevented = false + + if (inNamespace && typeof $ !== 'undefined') { + jQueryEvent = new $.Event(event, args) + + $(element).trigger(jQueryEvent) + bubbles = !jQueryEvent.isPropagationStopped() + nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() + defaultPrevented = jQueryEvent.isDefaultPrevented() + } + + let evt = null + + if (isNative) { + evt = document.createEvent('HTMLEvents') + evt.initEvent(typeEvent, bubbles, true) + } else { + evt = new CustomEvent(event, { + bubbles, + cancelable: true }) } - } - if (nativeDispatch) { - element.dispatchEvent(evt) - } + // merge custom informations in our event + if (typeof args !== 'undefined') { + evt = Util.extend(evt, args) + } - if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { - jQueryEvent.preventDefault() - } + if (defaultPrevented) { + evt.preventDefault() - return evt + if (!defaultPreventedPreservedOnDispatch) { + Object.defineProperty(evt, 'defaultPrevented', { + get: () => true + }) + } + } + + if (nativeDispatch) { + element.dispatchEvent(evt) + } + + if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { + jQueryEvent.preventDefault() + } + + return evt + } } -} +})() // focusin and focusout polyfill if (typeof window.onfocusin === 'undefined') { diff --git a/js/src/dom/selectorEngine.js b/js/src/dom/selectorEngine.js index 0cff83ef97..99dc26b797 100644 --- a/js/src/dom/selectorEngine.js +++ b/js/src/dom/selectorEngine.js @@ -5,76 +5,86 @@ * -------------------------------------------------------------------------- */ -// matches polyfill (see: https://mzl.la/2ikXneG) -let fnMatches = null -if (!Element.prototype.matches) { - fnMatches = - Element.prototype.msMatchesSelector || - Element.prototype.webkitMatchesSelector -} else { - fnMatches = Element.prototype.matches -} +const SelectorEngine = (() => { -// closest polyfill (see: https://mzl.la/2vXggaI) -let fnClosest = null -if (!Element.prototype.closest) { - fnClosest = (element, selector) => { - let ancestor = element - if (!document.documentElement.contains(element)) { - return null - } - do { - if (fnMatches.call(ancestor, selector)) { - return ancestor + /** + * ------------------------------------------------------------------------ + * Polyfills + * ------------------------------------------------------------------------ + */ + + // matches polyfill (see: https://mzl.la/2ikXneG) + let fnMatches = null + if (!Element.prototype.matches) { + fnMatches = + Element.prototype.msMatchesSelector || + Element.prototype.webkitMatchesSelector + } else { + fnMatches = Element.prototype.matches + } + + // closest polyfill (see: https://mzl.la/2vXggaI) + let fnClosest = null + if (!Element.prototype.closest) { + fnClosest = (element, selector) => { + let ancestor = element + if (!document.documentElement.contains(element)) { + return null } - ancestor = ancestor.parentElement - } while (ancestor !== null) + do { + if (fnMatches.call(ancestor, selector)) { + return ancestor + } - return null - } -} else { - // eslint-disable-next-line arrow-body-style - fnClosest = (element, selector) => { - return element.closest(selector) - } -} + ancestor = ancestor.parentElement + } while (ancestor !== null) -const SelectorEngine = { - matches(element, selector) { - return fnMatches.call(element, selector) - }, - - find(selector, element = document) { - if (typeof selector !== 'string') { return null } - - if (selector.indexOf('#') === 0) { - return SelectorEngine.findOne(selector, element) + } else { + // eslint-disable-next-line arrow-body-style + fnClosest = (element, selector) => { + return element.closest(selector) } - - return element.querySelectorAll(selector) - }, - - findOne(selector, element = document) { - if (typeof selector !== 'string') { - return null - } - - let selectorType = 'querySelector' - if (selector.indexOf('#') === 0) { - selectorType = 'getElementById' - selector = selector.substr(1, selector.length) - } - - return element[selectorType](selector) - }, - - closest(element, selector) { - return fnClosest(element, selector) } -} + + return { + matches(element, selector) { + return fnMatches.call(element, selector) + }, + + find(selector, element = document) { + if (typeof selector !== 'string') { + return null + } + + if (selector.indexOf('#') === 0) { + return SelectorEngine.findOne(selector, element) + } + + return element.querySelectorAll(selector) + }, + + findOne(selector, element = document) { + if (typeof selector !== 'string') { + return null + } + + let selectorType = 'querySelector' + if (selector.indexOf('#') === 0) { + selectorType = 'getElementById' + selector = selector.substr(1, selector.length) + } + + return element[selectorType](selector) + }, + + closest(element, selector) { + return fnClosest(element, selector) + } + } +})() export default SelectorEngine