|
|
@@ -0,0 +1,461 @@
|
|
|
+/*globals jQuery,Window,HTMLElement,HTMLDocument,HTMLCollection,NodeList,MutationObserver */
|
|
|
+/*exported Arrive*/
|
|
|
+/*jshint latedef:false */
|
|
|
+
|
|
|
+/*
|
|
|
+ * arrive.js
|
|
|
+ * v2.4.1
|
|
|
+ * https://github.com/uzairfarooq/arrive
|
|
|
+ * MIT licensed
|
|
|
+ *
|
|
|
+ * Copyright (c) 2014-2017 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) {
|
|
|
+ if (registrationData && registrationData.options.onceOnly && registrationData.firedElems.length == 1) {
|
|
|
+ // as onlyOnce param is true, make sure we fire the event for only one item
|
|
|
+ callbacksToBeCalled = [callbacksToBeCalled[0]];
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var i = 0, cb; (cb = callbacksToBeCalled[i]); i++) {
|
|
|
+ if (cb && cb.callback) {
|
|
|
+ cb.callback.call(cb.elem, cb.elem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (registrationData && registrationData.options.onceOnly && registrationData.firedElems.length == 1) {
|
|
|
+ // unbind event after first callback as onceOnly is true.
|
|
|
+ registrationData.me.unbindEventWithSelectorAndCallback.call(
|
|
|
+ registrationData.target, registrationData.selector, registrationData.callback);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 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) {
|
|
|
+ var newEvent = {
|
|
|
+ target: target,
|
|
|
+ selector: selector,
|
|
|
+ options: options,
|
|
|
+ callback: callback,
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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++) {
|
|
|
+ eventsBucket.addEvent(elements[i], selector, options, callback);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ 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;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @constructor
|
|
|
+ * Processes 'arrive' events
|
|
|
+ */
|
|
|
+ var ArriveEvents = function() {
|
|
|
+ // Default options for 'arrive' event
|
|
|
+ var arriveDefaultOptions = {
|
|
|
+ fireOnAttributesModification: false,
|
|
|
+ onceOnly: false,
|
|
|
+ existing: false
|
|
|
+ };
|
|
|
+
|
|
|
+ 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);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ 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, options, callback) {
|
|
|
+
|
|
|
+ if (typeof callback === "undefined") {
|
|
|
+ callback = options;
|
|
|
+ options = arriveDefaultOptions;
|
|
|
+ } else {
|
|
|
+ options = utils.mergeArrays(arriveDefaultOptions, options);
|
|
|
+ }
|
|
|
+
|
|
|
+ var elements = utils.toElementsArray(this);
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ return callback.call(existing[0].elem, existing[0].elem);
|
|
|
+ }
|
|
|
+
|
|
|
+ setTimeout(utils.callCallbacks, 1, existing);
|
|
|
+ }
|
|
|
+
|
|
|
+ mutationBindEvent.call(this, selector, options, callback);
|
|
|
+ };
|
|
|
+
|
|
|
+ return arriveEvents;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @constructor
|
|
|
+ * Processes 'leave' events
|
|
|
+ */
|
|
|
+ var LeaveEvents = function() {
|
|
|
+ // Default options for 'leave' event
|
|
|
+ var leaveDefaultOptions = {};
|
|
|
+
|
|
|
+ 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);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ 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, options, callback) {
|
|
|
+
|
|
|
+ if (typeof callback === "undefined") {
|
|
|
+ callback = options;
|
|
|
+ options = leaveDefaultOptions;
|
|
|
+ } else {
|
|
|
+ options = utils.mergeArrays(leaveDefaultOptions, options);
|
|
|
+ }
|
|
|
+
|
|
|
+ mutationBindEvent.call(this, selector, options, callback);
|
|
|
+ };
|
|
|
+
|
|
|
+ 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);
|