UNPKG

owtlab-tracking

Version:
298 lines (260 loc) 7.37 kB
import each from 'keen-core/lib/utils/each'; /* // Create a new element listner var myClickerCatcher = Keen.utils.listener(".nav li > a"); // Listen for a given event myClicker.on("click", function(e){ // do stuff! }); // Listen for event once myClicker.once("click", function(e){ }); // Cancel a given event listener myClicker.off("click"); // Cancel all event listners myClicker.off(); */ export const listenerCore = function (ctx) { // Make sure this object exists ctx.domListeners = ctx.domListeners || { /* 'click': { '.nav li > a': [fn, fn, fn] } */ }; function listener(str) { if (!str) return; if (this instanceof listener === false) { return new listener(str); } this.selector = str; return this; } listener.prototype.on = function (str, fn) { const self = this; if ( arguments.length !== 2 || typeof str !== 'string' || typeof fn !== 'function' ) { return this; } // Set each listener on a parent dictionary, indexed by event: if (typeof ctx.domListeners[str] === 'undefined') { addListener(str, eventHandler(str)); ctx.domListeners[str] = {}; } ctx.domListeners[str][self.selector] = ctx.domListeners[str][self.selector] || []; ctx.domListeners[str][self.selector].push(fn); return self; }; listener.prototype.once = function (str, fn) { const self = this; function on() { self.off(str, on); return fn.apply(self, arguments); } on.fn = fn; self.on(str, on); return self; }; listener.prototype.off = function (str, fn) { const self = this; const survivors = []; if (arguments.length === 2) { each(ctx.domListeners[str][self.selector], (handler, i) => { if (handler === fn || handler.fn === fn) return; survivors.push(handler); }); ctx.domListeners[str][self.selector] = survivors; } else if (arguments.length === 1) { try { delete ctx.domListeners[str][self.selector]; } catch (e) { ctx.domListeners[str][self.selector] = []; } } else { // loop over every eventType and delete handlers each(ctx.domListeners, (hash, eventType) => { // if ('undefined' === typeof hash[str]) return; try { delete ctx.domListeners[eventType][self.selector]; } catch (e) { ctx.domListeners[eventType][self.selector] = function () { }; } }); } return self; }; function eventHandler(eventType) { return function (e) { let evt; let target; evt = e || window.event; target = evt.target || evt.srcElement; // If nothing assigned to this event type, let it go if (ctx.domListeners[eventType] === 'undefined') return; each(ctx.domListeners[eventType], (handlers, key) => { if (matches(target, key)) { // Call all handlers for this eventType + node each(handlers, (fn, i) => { if (eventType === 'click' && target.nodeName === 'A') { deferClickEvent(evt, target, fn); } else if (eventType === 'submit' && target.nodeName === 'FORM') { deferFormSubmit(evt, target, fn); } else { fn(evt); } }); } else if (key === 'window') { // Call all handlers each(handlers, (fn, i) => { fn(evt); }); } }); }; } return listener; }; // ------------------------------ // Attach global event listener // ------------------------------ function addListener(eventType, fn) { if (document.addEventListener) { document.addEventListener(eventType, fn, false); } else { document.attachEvent(`on${eventType}`, fn); } } // ------------------------------ // Match DOM element to selector // ------------------------------ function matches(elem, selector) { // We'll use querySelectorAll to find all element matching the selector, // then check if the given element is included in that list. // Executing the query on the parentNode reduces the resulting nodeList, // document doesn't have a parentNode, though. const nodeList = (elem.parentNode || document).querySelectorAll(selector) || []; let i = nodeList.length; // loop on the nodeList while (i--) { if (nodeList[i] == elem) { return true; } } return false; } // ------------------------------ // Handle 'click' events (A) // ------------------------------ function deferClickEvent(evt, anchor, callback) { const timeout = 500; let targetAttr; let cbResponse; // Get 'target' attribute from anchor if (anchor.getAttribute !== void 0) { targetAttr = anchor.getAttribute('target'); } else if (anchor.target) { targetAttr = anchor.target; } // Fire listener and catch possible response (return false) cbResponse = callback(evt); // If prevented within callback, bail: if ( (typeof cbResponse === 'boolean' && cbResponse === false) || evt.defaultPrevented || evt.returnValue === false ) { if (evt.preventDefault) { evt.preventDefault(); } evt.returnValue = false; return false; } // Else if anchor doesn't kick off a new window or tab.. defer and replay the event: else if ( targetAttr !== '_blank' && targetAttr !== 'blank' && !evt.metaKey && !anchor.hasAttribute('download') ) { if (evt.preventDefault) { evt.preventDefault(); } evt.returnValue = false; if ( anchor.href && anchor.href !== '#' && anchor.href !== `${window.location}#` ) { if (typeof cbResponse !== 'undefined') { if (navigator && navigator.sendBeacon) { window.location = anchor.href; return; } // promise cbResponse .then(() => { window.location = anchor.href; }) .catch((err) => { // change location anyway - to not let user hanging window.location = anchor.href; }); } else { setTimeout(() => { window.location = anchor.href; }, timeout); } } } return false; } // ------------------------------ // Handle 'submit' events (FORM) // ------------------------------ function deferFormSubmit(evt, form, callback) { const timeout = 500; // Fire listener and catch possible response (return false) const cbResponse = callback(evt); // If prevented within callback, bail if ( (typeof cbResponse === 'boolean' && cbResponse === false) || evt.defaultPrevented || evt.returnValue === false ) { if (evt.preventDefault) { evt.preventDefault(); } evt.returnValue = false; return false; } // Defer and replay event if (evt.preventDefault) { evt.preventDefault(); } evt.returnValue = false; if (typeof cbResponse !== 'undefined') { if (navigator && navigator.sendBeacon) { form.submit(); return; } // promise cbResponse .then(() => { form.submit(); }) .catch((err) => { // submit form anyway - to not let user hanging form.submit(); }); } else { setTimeout(() => { form.submit(); }, timeout); } return false; }