UNPKG

domtastic

Version:

Small, fast, and modular DOM and event library for modern browsers.

182 lines (157 loc) 5.34 kB
/** * @module trigger */ import { win, each } from '../util'; import { contains } from '../dom/contains'; const reMouseEvent = /^(mouse(down|up|over|out|enter|leave|move)|contextmenu|(dbl)?click)$/; const reKeyEvent = /^key(down|press|up)$/; /** * Trigger event at element(s) * * @param {String} type Type of the event * @param {Object} data Data to be sent with the event (`params.detail` will be set to this). * @param {Object} [params] Event parameters (optional) * @param {Boolean} params.bubbles=true Does the event bubble up through the DOM or not. * @param {Boolean} params.cancelable=true Is the event cancelable or not. * @param {Mixed} params.detail=undefined Additional information about the event. * @return {Object} The wrapped collection * @chainable * @example * $('.item').trigger('anyEventType'); */ export const trigger = function(type, data, {bubbles = true, cancelable = true, preventDefault = false} = {}) { const EventConstructor = getEventConstructor(type); const event = new EventConstructor(type, { bubbles, cancelable, preventDefault, detail: data }); event._preventDefault = preventDefault; return each(this, element => { if(!bubbles || isEventBubblingInDetachedTree || isAttachedToDocument(element)) { dispatchEvent(element, event); } else { triggerForPath(element, type, { bubbles, cancelable, preventDefault, detail: data }); } }); }; const getEventConstructor = type => isSupportsOtherEventConstructors ? (reMouseEvent.test(type) ? MouseEvent : (reKeyEvent.test(type) ? KeyboardEvent : CustomEvent)) : CustomEvent; /** * Trigger event at first element in the collection. Similar to `trigger()`, except: * * - Event does not bubble * - Default event behavior is prevented * - Only triggers handler for first matching element * * @param {String} type Type of the event * @param {Object} data Data to be sent with the event * @example * $('form').triggerHandler('submit'); */ export const triggerHandler = function(type, data) { if(this[0]) { trigger.call(this[0], type, data, { bubbles: false, preventDefault: true }); } }; /** * Check whether the element is attached to or detached from) the document * * @private * @param {Node} element Element to test * @return {Boolean} */ const isAttachedToDocument = element => { if(element === window || element === document) { return true; } return contains(element.ownerDocument.documentElement, element); }; /** * Dispatch the event at the element and its ancestors. * Required to support delegated events in browsers that don't bubble events in detached DOM trees. * * @private * @param {Node} element First element to dispatch the event at * @param {String} type Type of the event * @param {Object} [params] Event parameters (optional) * @param {Boolean} params.bubbles=true Does the event bubble up through the DOM or not. * Will be set to false (but shouldn't matter since events don't bubble anyway). * @param {Boolean} params.cancelable=true Is the event cancelable or not. * @param {Mixed} params.detail=undefined Additional information about the event. */ const triggerForPath = (element, type, params = {}) => { params.bubbles = false; const event = new CustomEvent(type, params); event._target = element; do { dispatchEvent(element, event); } while(element = element.parentNode); // eslint-disable-line no-cond-assign }; /** * Dispatch event to element, but call direct event methods instead if available * (e.g. "blur()", "submit()") and if the event is non-cancelable. * * @private * @param {Node} element Element to dispatch the event at * @param {Object} event Event to dispatch */ const directEventMethods = ['blur', 'focus', 'select', 'submit']; const dispatchEvent = (element, event) => { if(directEventMethods.indexOf(event.type) !== -1 && typeof element[event.type] === 'function' && !event._preventDefault && !event.cancelable) { element[event.type](); } else { element.dispatchEvent(event); } }; /** * Polyfill for CustomEvent, borrowed from [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill). * Needed to support IE (9, 10, 11) & PhantomJS */ (() => { const CustomEvent = function(event, params = { bubbles: false, cancelable: false, detail: undefined }) { let customEvent = document.createEvent('CustomEvent'); customEvent.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); return customEvent; }; CustomEvent.prototype = win.CustomEvent && win.CustomEvent.prototype; win.CustomEvent = CustomEvent; })(); /* * Are events bubbling in detached DOM trees? * @private */ const isEventBubblingInDetachedTree = (() =>{ let isBubbling = false; const doc = win.document; if(doc) { const parent = doc.createElement('div'); const child = parent.cloneNode(); parent.appendChild(child); parent.addEventListener('e', function() { isBubbling = true; }); child.dispatchEvent(new CustomEvent('e', {bubbles: true})); } return isBubbling; })(); const isSupportsOtherEventConstructors = (() => { try { new MouseEvent('click'); } catch(e) { return false; } return true; })();