- /*globals jQuery,Window,HTMLElement,HTMLDocument,HTMLCollection,NodeList,MutationObserver */
- /*exported Arrive*/
- /*jshint latedef:false */
-
- /*
- * arrive.js
- * v2.5.2
- * https://github.com/uzairfarooq/arrive
- * MIT licensed
- *
- * Copyright (c) 2014-2024 Uzair Farooq
- */
- var Arrive = (function(window, $, undefined) {
-
- "use strict";
-
- if(!window.MutationObserver || typeof HTMLElement === 'undefined'){
- return; //for unsupported browsers
- }
-
- var arriveUniqueId = 0;
-
- var utils = (function() {
- var matches = HTMLElement.prototype.matches || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector
- || HTMLElement.prototype.msMatchesSelector;
-
- return {
- matchesSelector: function(elem, selector) {
- return elem instanceof HTMLElement && matches.call(elem, selector);
- },
- // to enable function overloading - By John Resig (MIT Licensed)
- addMethod: function (object, name, fn) {
- var old = object[ name ];
- object[ name ] = function(){
- if ( fn.length == arguments.length ) {
- return fn.apply( this, arguments );
- }
- else if ( typeof old == 'function' ) {
- return old.apply( this, arguments );
- }
- };
- },
- callCallbacks: function(callbacksToBeCalled, registrationData, mutationEvents) {
- if (!callbacksToBeCalled.length) return;
-
- if (registrationData && registrationData.options.onceOnly) {
- // as onlyOnce param is true, make sure we fire the event for only one item
- callbacksToBeCalled = [callbacksToBeCalled[0]];
-
- // unbind event after first callback as onceOnly is true.
- registrationData.me.unbindEventWithSelectorAndCallback.call(
- registrationData.target, registrationData.selector, registrationData.callback);
- }
-
- for (var i = 0, cb; (cb = callbacksToBeCalled[i]); i++) {
- if (cb && cb.callback) {
- cb.callback.call(cb.elem, cb.elem);
- }
- }
-
- if (registrationData && registrationData.callback && mutationEvents) {
- mutationEvents.addTimeoutHandler(registrationData.target, registrationData.selector, registrationData.callback, registrationData.options, registrationData.data);
- }
- },
- // traverse through all descendants of a node to check if event should be fired for any descendant
- checkChildNodesRecursively: function(nodes, registrationData, matchFunc, callbacksToBeCalled) {
- // check each new node if it matches the selector
- for (var i=0, node; (node = nodes[i]); i++) {
- if (matchFunc(node, registrationData, callbacksToBeCalled)) {
- callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
- }
-
- if (node.childNodes.length > 0) {
- utils.checkChildNodesRecursively(node.childNodes, registrationData, matchFunc, callbacksToBeCalled);
- }
- }
- },
- mergeArrays: function(firstArr, secondArr){
- // Overwrites default options with user-defined options.
- var options = {},
- attrName;
- for (attrName in firstArr) {
- if (firstArr.hasOwnProperty(attrName)) {
- options[attrName] = firstArr[attrName];
- }
- }
- for (attrName in secondArr) {
- if (secondArr.hasOwnProperty(attrName)) {
- options[attrName] = secondArr[attrName];
- }
- }
- return options;
- },
- toElementsArray: function (elements) {
- // check if object is an array (or array like object)
- // Note: window object has .length property but it's not array of elements so don't consider it an array
- if (typeof elements !== "undefined" && (typeof elements.length !== "number" || elements === window)) {
- elements = [elements];
- }
- return elements;
- }
- };
- })();
-
-
- // Class to maintain state of all registered events of a single type
- var EventsBucket = (function() {
- var EventsBucket = function() {
- // holds all the events
-
- this._eventsBucket = [];
- // function to be called while adding an event, the function should do the event initialization/registration
- this._beforeAdding = null;
- // function to be called while removing an event, the function should do the event destruction
- this._beforeRemoving = null;
- };
-
- EventsBucket.prototype.addEvent = function(target, selector, options, callback, data) {
- var newEvent = {
- target: target,
- selector: selector,
- options: options,
- callback: callback,
- data: data,
- firedElems: []
- };
-
- if (this._beforeAdding) {
- this._beforeAdding(newEvent);
- }
-
- this._eventsBucket.push(newEvent);
- return newEvent;
- };
-
- EventsBucket.prototype.removeEvent = function(compareFunction) {
- for (var i=this._eventsBucket.length - 1, registeredEvent; (registeredEvent = this._eventsBucket[i]); i--) {
- if (compareFunction(registeredEvent)) {
- if (this._beforeRemoving) {
- this._beforeRemoving(registeredEvent);
- }
-
- if (registeredEvent.data && registeredEvent.data.timeoutId) {
- clearTimeout(registeredEvent.data.timeoutId);
- }
-
- // mark callback as null so that even if an event mutation was already triggered it does not call callback
- var removedEvents = this._eventsBucket.splice(i, 1);
- if (removedEvents && removedEvents.length) {
- removedEvents[0].callback = null;
- }
- }
- }
- };
-
- EventsBucket.prototype.beforeAdding = function(beforeAdding) {
- this._beforeAdding = beforeAdding;
- };
-
- EventsBucket.prototype.beforeRemoving = function(beforeRemoving) {
- this._beforeRemoving = beforeRemoving;
- };
-
- return EventsBucket;
- })();
-
-
- /**
- * @constructor
- * General class for binding/unbinding arrive and leave events
- */
- var MutationEvents = function(getObserverConfig, onMutation) {
- var eventsBucket = new EventsBucket(),
- me = this;
-
- var defaultOptions = {
- fireOnAttributesModification: false
- };
-
- // actual event registration before adding it to bucket
- eventsBucket.beforeAdding(function(registrationData) {
- var
- target = registrationData.target,
- observer;
-
- // mutation observer does not work on window or document
- if (target === window.document || target === window) {
- target = document.getElementsByTagName("html")[0];
- }
-
- // Create an observer instance
- observer = new MutationObserver(function(e) {
- onMutation.call(this, e, registrationData);
- });
-
- var config = getObserverConfig(registrationData.options);
-
- observer.observe(target, config);
-
- registrationData.observer = observer;
- registrationData.me = me;
- });
-
- // cleanup/unregister before removing an event
- eventsBucket.beforeRemoving(function (eventData) {
- eventData.observer.disconnect();
- });
-
- this.bindEvent = function(selector, options, callback) {
- options = utils.mergeArrays(defaultOptions, options);
-
- var elements = utils.toElementsArray(this);
-
- for (var i = 0; i < elements.length; i++) {
- const data = {};
-
- // Add timeout handling
- me.addTimeoutHandler(elements[i], selector, callback, options, data);
-
- eventsBucket.addEvent(elements[i], selector, options, callback, data);
- }
- };
-
- this.unbindEvent = function() {
- var elements = utils.toElementsArray(this);
- eventsBucket.removeEvent(function(eventObj) {
- for (var i = 0; i < elements.length; i++) {
- if (this === undefined || eventObj.target === elements[i]) {
- return true;
- }
- }
- return false;
- });
- };
-
- this.unbindEventWithSelectorOrCallback = function(selector) {
- var elements = utils.toElementsArray(this),
- callback = selector,
- compareFunction;
-
- if (typeof selector === "function") {
- compareFunction = function(eventObj) {
- for (var i = 0; i < elements.length; i++) {
- if ((this === undefined || eventObj.target === elements[i]) && eventObj.callback === callback) {
- return true;
- }
- }
- return false;
- };
- }
- else {
- compareFunction = function(eventObj) {
- for (var i = 0; i < elements.length; i++) {
- if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector) {
- return true;
- }
- }
- return false;
- };
- }
- eventsBucket.removeEvent(compareFunction);
- };
-
- this.unbindEventWithSelectorAndCallback = function(selector, callback) {
- var elements = utils.toElementsArray(this);
- eventsBucket.removeEvent(function(eventObj) {
- for (var i = 0; i < elements.length; i++) {
- if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector && eventObj.callback === callback) {
- return true;
- }
- }
- return false;
- });
- };
-
- this.addTimeoutHandler = function(target, selector, callback, options, data) {
- if (!options.timeout || options.timeout <= 0) {
- return;
- }
-
- if (data.timeoutId) {
- clearTimeout(data.timeoutId);
- }
-
- data.timeoutId = setTimeout(() => {
- me.unbindEventWithSelectorAndCallback.call(target, selector, callback);
- callback.call(null, null);
- }, options.timeout);
- }
-
- return this;
- };
-
-
- /**
- * @constructor
- * Processes 'arrive' events
- */
- var ArriveEvents = function() {
- // Default options for 'arrive' event
- var arriveDefaultOptions = {
- fireOnAttributesModification: false,
- onceOnly: false,
- existing: false,
- timeout: 0 // default 0 (no timeout)
- };
-
- function getArriveObserverConfig(options) {
- var config = {
- attributes: false,
- childList: true,
- subtree: true
- };
-
- if (options.fireOnAttributesModification) {
- config.attributes = true;
- }
-
- return config;
- }
-
- function onArriveMutation(mutations, registrationData) {
- mutations.forEach(function( mutation ) {
- var newNodes = mutation.addedNodes,
- targetNode = mutation.target,
- callbacksToBeCalled = [],
- node;
-
- // If new nodes are added
- if( newNodes !== null && newNodes.length > 0 ) {
- utils.checkChildNodesRecursively(newNodes, registrationData, nodeMatchFunc, callbacksToBeCalled);
- }
- else if (mutation.type === "attributes") {
- if (nodeMatchFunc(targetNode, registrationData, callbacksToBeCalled)) {
- callbacksToBeCalled.push({ callback: registrationData.callback, elem: targetNode });
- }
- }
-
- utils.callCallbacks(callbacksToBeCalled, registrationData, arriveEvents);
- });
- }
-
- function nodeMatchFunc(node, registrationData, callbacksToBeCalled) {
- // check a single node to see if it matches the selector
- if (utils.matchesSelector(node, registrationData.selector)) {
- if(node._id === undefined) {
- node._id = arriveUniqueId++;
- }
- // make sure the arrive event is not already fired for the element
- if (registrationData.firedElems.indexOf(node._id) == -1) {
- registrationData.firedElems.push(node._id);
-
- return true;
- }
- }
-
- return false;
- }
-
- arriveEvents = new MutationEvents(getArriveObserverConfig, onArriveMutation);
-
- var mutationBindEvent = arriveEvents.bindEvent;
-
- // override bindEvent function
- arriveEvents.bindEvent = function(selector, arg2, arg3) {
-
- var options = (typeof arg2 === 'object') ? utils.mergeArrays(arriveDefaultOptions, arg2) : { ...arriveDefaultOptions };
- var callback = (typeof arg3 === 'function') ? arg3 : (typeof arg2 === 'function') ? arg2 : undefined;
- var elements = utils.toElementsArray(this);
-
- // For promise and async support, we can only do onceOnly=true
- if (!callback)
- options.onceOnly = true;
-
- if (options.existing) {
- var existing = [];
-
- for (var i = 0; i < elements.length; i++) {
- var nodes = elements[i].querySelectorAll(selector);
- for (var j = 0; j < nodes.length; j++) {
- existing.push({ callback: callback, elem: nodes[j] });
- }
- }
-
- // no need to bind event if the callback has to be fired only once and we have already found the element
- if (options.onceOnly && existing.length) {
- if (callback) {
- return callback.call(existing[0].elem, existing[0].elem);
- } else {
- return Promise.resolve(existing[0].elem);
- }
- }
-
- setTimeout(utils.callCallbacks, 1, existing);
- }
-
- if (callback) {
- mutationBindEvent.call(this, selector, options, callback);
- } else {
- var a = this;
- return new Promise(resolve => mutationBindEvent.call(a, selector, options, resolve));
- }
- };
-
- return arriveEvents;
- };
-
-
- /**
- * @constructor
- * Processes 'leave' events
- */
- var LeaveEvents = function() {
- // Default options for 'leave' event
- var leaveDefaultOptions = {
- onceOnly: false,
- timeout: 0, // default 0 (no timeout)
- };
-
- function getLeaveObserverConfig() {
- var config = {
- childList: true,
- subtree: true
- };
-
- return config;
- }
-
- function onLeaveMutation(mutations, registrationData) {
- mutations.forEach(function( mutation ) {
- var removedNodes = mutation.removedNodes,
- callbacksToBeCalled = [];
-
- if( removedNodes !== null && removedNodes.length > 0 ) {
- utils.checkChildNodesRecursively(removedNodes, registrationData, nodeMatchFunc, callbacksToBeCalled);
- }
-
- utils.callCallbacks(callbacksToBeCalled, registrationData, leaveEvents);
- });
- }
-
- function nodeMatchFunc(node, registrationData) {
- return utils.matchesSelector(node, registrationData.selector);
- }
-
- leaveEvents = new MutationEvents(getLeaveObserverConfig, onLeaveMutation);
-
- var mutationBindEvent = leaveEvents.bindEvent;
-
- // override bindEvent function
- leaveEvents.bindEvent = function(selector, arg2, arg3) {
-
- var options = (typeof arg2 === 'object') ? utils.mergeArrays(leaveDefaultOptions, arg2) : { ...leaveDefaultOptions };
- var callback = (typeof arg3 === 'function') ? arg3 : (typeof arg2 === 'function') ? arg2 : undefined;
-
- if (callback) {
- mutationBindEvent.call(this, selector, options, callback);
- } else {
- // For promise and async support, we can only do onceOnly=true
- options.onceOnly = true;
-
- var a = this;
- return new Promise(resolve => mutationBindEvent.call(a, selector, options, resolve));
- }
- };
-
- return leaveEvents;
- };
-
-
-
- var arriveEvents = new ArriveEvents(),
- leaveEvents = new LeaveEvents();
-
- function exposeUnbindApi(eventObj, exposeTo, funcName) {
- // expose unbind function with function overriding
- utils.addMethod(exposeTo, funcName, eventObj.unbindEvent);
- utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorOrCallback);
- utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorAndCallback);
- }
-
- /*** expose APIs ***/
- function exposeApi(exposeTo) {
- exposeTo.arrive = arriveEvents.bindEvent;
- exposeUnbindApi(arriveEvents, exposeTo, "unbindArrive");
-
- exposeTo.leave = leaveEvents.bindEvent;
- exposeUnbindApi(leaveEvents, exposeTo, "unbindLeave");
- }
-
- if ($) {
- exposeApi($.fn);
- }
- exposeApi(HTMLElement.prototype);
- exposeApi(NodeList.prototype);
- exposeApi(HTMLCollection.prototype);
- exposeApi(HTMLDocument.prototype);
- exposeApi(Window.prototype);
-
- var Arrive = {};
- // expose functions to unbind all arrive/leave events
- exposeUnbindApi(arriveEvents, Arrive, "unbindAllArrive");
- exposeUnbindApi(leaveEvents, Arrive, "unbindAllLeave");
-
- return Arrive;
-
- })(window, typeof jQuery === 'undefined' ? null : jQuery, undefined);
-