File: D:/HostingSpaces/ReturnIndustries/return-industries.nl/resources/js/kms/entity/initializer.js
/**
* Observes the contents of the element you reference to via the constructor selector variable
* for added nodes and triggers callbacks for added nodes when the match the given selector that
* you've specified with the bindSelectorToCallback method
*/
class Initializer {
/**
* @param wrapperSelector A selector of an element that contains all elements that are bound with the bindSelectorToCallback method. As close as possible to the things they match
*/
constructor(wrapperSelector) {
this._bindings = {};
this._onceBindings = {}; //Selectors that may only be initialized once if the binding in this object is true
this._booted = false;
this._observer = null;
this._isObserving = false;
this._showDebugInformation = false;
this._observerConfiguration = {
// attributeFilter: [], //An array of specific attribute names to be monitored. If this property isn't included, changes to all attributes cause mutation notifications. No default value.
// attributeOldValue: false, //Set to true to record the previous value of any attribute that changes when monitoring the node or nodes for attribute changes.
// attributes: true, //Set to true to watch for changes to the value of attributes on the node or nodes being monitored. The default value is false.
// characterDataOldValue: true, //Set to true to record the previous value of a node's text whenever the text changes on nodes being monitored.
// characterData: true, //Set to true to monitor the specified target node or subtree for changes to the character data contained within the node or nodes. No default value.
subtree: true, //Set to true to extend monitoring to the entire subtree of nodes rooted at target. All of the other MutationObserverInit properties are then extended to all of the nodes in the subtree instead of applying solely to the target node. The default value is false.,
childList: true, //Set to true to monitor the target node (and, if subtree is true, its descendants) for the addition or removal of new child nodes. The default is false.
};
this._wrapper = document.querySelector(wrapperSelector);
if (!this._wrapper) {
console.info('The selector "' + wrapperSelector + '" does not match any element. Could not monitor html elements and initialize them when needed.');
return;
}
this._boot();
}
/**
* Configure the Initializer to observe the element that wrapperSelector references
* for new HTMLElements, referenced by the bound selectors. So that when they are found, the callback is called.
* You need to call startObserving to start the observation.
*/
_boot() {
if (this._booted) return;
this._booted = true;
this._observer = new MutationObserver(this._mutationsObserved.bind(this));
}
/**
* Start observing for changes
*
* @return {Initializer}
*/
startObserving()
{
if (!this._booted) return this;
if(this._showDebugInformation) console.log('Starting to observe');
this._observer.takeRecords(); //Take the mutation records out of the observer so that it is cleared
this._observer.observe(this._wrapper, this._observerConfiguration);
this._isObserving = true;
return this;
}
/**
* Stop observing for changes
*
* @return {Initializer}
*/
stopObserving()
{
if (!this._booted) return this;
if(this._showDebugInformation) console.log('Stopped to observe');
this._observer.disconnect();
this._isObserving = false;
return this;
}
/**
* Triggered by the MutationObserver when a mutation had taken place
*
* @param {MutationRecord[]} mutationsList
*/
_mutationsObserved(mutationsList)
{
if (!this._booted) return this;
let mutationsCount = mutationsList.length;
if(this._showDebugInformation) console.info('Detected '+mutationsCount+' changes');
for(let mutationNumber = 0; mutationNumber < mutationsCount; mutationNumber++)
{
let mutationRecord = mutationsList[mutationNumber];
let addedNodes = mutationRecord.addedNodes;
let addedNodesCount = addedNodes.length;
for(let addedNodeNumber = 0; addedNodeNumber < addedNodesCount; addedNodeNumber++) {
let addedNode = addedNodes[addedNodeNumber];
this._triggerBoundCallbacksIfElementMatchesSelector(addedNode);
}
}
}
/**
* @param {HTMLElement} htmlElement
*/
_triggerBoundCallbacksIfElementMatchesSelector(htmlElement)
{
if(!htmlElement || !(htmlElement instanceof HTMLElement)) return;
for(let selector in this._bindings) {
// if(this._showDebugInformation) console.log('Checking if selector "'+selector+'" matches HTMLElement', htmlElement);
let addedElementMatchesSelector = htmlElement.matches(selector);
let elementsThatMatch = htmlElement.querySelectorAll(selector);
let matchingElementsCount = elementsThatMatch.length;
if(addedElementMatchesSelector || matchingElementsCount > 0) {
if(addedElementMatchesSelector) {
if(!htmlElement.hasAttribute('initialized') || this._onceBindings[selector] === false) {
if (this._showDebugInformation) console.log('Calling callback for selector "' + selector + '". HTMLElement', htmlElement);
this._bindings[selector](this, htmlElement);
htmlElement.setAttribute('initialized', '')
}
}
if(matchingElementsCount > 0) {
for(let currentMatchingElementIndex = 0; currentMatchingElementIndex < matchingElementsCount; currentMatchingElementIndex++)
{
let currentMatchingElement = elementsThatMatch[currentMatchingElementIndex];
if(!currentMatchingElement.hasAttribute('initialized') || this._onceBindings[selector] === false) {
if (this._showDebugInformation) console.log('Calling callback for selector "' + selector + '". HTMLElement', currentMatchingElement);
this._bindings[selector](this, currentMatchingElement);
currentMatchingElement.setAttribute('initialized', '');
}
}
}
}
}
}
/**
* @param {string} selector
* @param {function} callback
* @param once
* @return {Initializer}
*/
bindSelectorToCallback(selector, callback, once = true) {
if (!this._booted) return this;
//Register the selector and callback
if (typeof selector !== 'string') {
console.error('Initializer:bind The selector must be a string');
return this;
}
if (typeof callback !== 'function') {
console.error('Initializer:bind The callback must be a function');
return this;
}
if (typeof once !== 'boolean') {
console.error('Initializer:bind The once variable must be a boolean');
return this
}
this._bindings[selector] = callback;
this._onceBindings[selector] = once;
if(this._showDebugInformation) console.log('bound selector "'+selector+'" to a callback');
//Trigger existing matches. Temporary stops the observer to prevent double callback calling.
let wasObserving = this._isObserving;
if(this._isObserving) this.stopObserving();
let nodeList = this._wrapper.querySelectorAll(selector);
if (!nodeList) return this;
let length = nodeList.length;
for (let currentElementNumber = 0; currentElementNumber < length; currentElementNumber++) callback(this, nodeList[currentElementNumber]);
if(wasObserving) this.startObserving();
return this;
}
}