628 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			628 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @package         Regular.js
 | |
|  * @description     A light and simple JavaScript Library
 | |
|  *
 | |
|  * @author          Peter van Westen <info@regularlabs.com>
 | |
|  * @link            https://github.com/regularlabs/regularjs
 | |
|  * @copyright       Copyright © 2024 Regular Labs - All Rights Reserved
 | |
|  * @license         https://github.com/regularlabs/regularjs/blob/master/LICENCE MIT
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| if (typeof window.Regular === 'undefined'
 | |
|     || typeof Regular.version === 'undefined'
 | |
|     || Regular.version < 1.5) {
 | |
| 
 | |
|     window.Regular = new function() {
 | |
|         /**
 | |
|          *
 | |
|          * PUBLIC PROPERTIES
 | |
|          *
 | |
|          */
 | |
| 
 | |
|         this.version = 1.5;
 | |
| 
 | |
|         /**
 | |
|          *
 | |
|          * PUBLIC METHODS
 | |
|          *
 | |
|          */
 | |
| 
 | |
|         /**
 | |
|          * Sets a global alias for the Regular class.
 | |
|          *
 | |
|          * @param word  A string (character or word) representing the alias for the Regular class.
 | |
|          *
 | |
|          * @return boolean
 | |
|          */
 | |
|         this.alias = function(word) {
 | |
|             if (typeof window[word] !== 'undefined') {
 | |
|                 console.error(`Cannot set '${word}' as an alias of Regular, as it already exists.`);
 | |
| 
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             window[word] = $;
 | |
| 
 | |
|             return true;
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Returns a boolean based on whether the element contains one or more of the given class names.
 | |
|          *
 | |
|          * @param selector  A CSS selector string or a HTMLElement object.
 | |
|          * @param classes   A string or array of class names.
 | |
|          * @param matchAll  Optional boolean whether the element should have all given classes (true) or at least one (false).
 | |
|          *
 | |
|          * @return boolean
 | |
|          */
 | |
|         this.hasClasses = function(selector, classes, matchAll = true) {
 | |
|             if ( ! selector) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelector(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             if ( ! element) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             if (typeof classes === 'string') {
 | |
|                 classes = classes.split(' ');
 | |
|             }
 | |
| 
 | |
|             let hasClass = false;
 | |
| 
 | |
|             for (const clss of classes) {
 | |
|                 hasClass = element.classList.contains(clss);
 | |
| 
 | |
|                 if (matchAll && ! hasClass) {
 | |
|                     return false;
 | |
|                 }
 | |
| 
 | |
|                 if ( ! matchAll && hasClass) {
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return hasClass;
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Adds given class name(s) to the element(s).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param classes   A string or array of class names.
 | |
|          */
 | |
|         this.addClasses = function(selector, classes) {
 | |
|             doClasses('add', selector, classes);
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Removes given class name(s) from the element(s).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param classes   A string or array of class names.
 | |
|          */
 | |
|         this.removeClasses = function(selector, classes) {
 | |
|             doClasses('remove', selector, classes);
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Toggles given class name(s) of the element(s).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param classes   A string or array of class names.
 | |
|          * @param force     An optional boolean value that forces the class to be added or removed.
 | |
|          */
 | |
|         this.toggleClasses = function(selector, classes, force) {
 | |
|             switch (force) {
 | |
|                 case true:
 | |
|                     doClasses('add', selector, classes);
 | |
|                     break;
 | |
| 
 | |
|                 case false:
 | |
|                     doClasses('remove', selector, classes);
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     doClasses('toggle', selector, classes);
 | |
|                     break;
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Makes the given element(s) visible (changes visibility and display attributes).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          */
 | |
|         this.makeVisible = function(selector) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             if ('forEach' in element) {
 | |
|                 element.forEach(subElement => $.makeVisible(subElement));
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             let computedDisplay = getComputedStyle(element, 'display');
 | |
| 
 | |
|             if ( ! ('origDisplay' in element)) {
 | |
|                 element.origDisplay = computedDisplay === 'none'
 | |
|                     ? getDefaultComputedStyle(element, 'display')
 | |
|                     : computedDisplay;
 | |
|             }
 | |
| 
 | |
|             if (computedDisplay === 'none') {
 | |
|                 element.style.display = ('origDisplay' in element) ? element.origDisplay : '';
 | |
|             }
 | |
| 
 | |
|             let elementType = element.nodeName.toLowerCase();
 | |
| 
 | |
|             switch (elementType) {
 | |
|                 case 'tr':
 | |
|                     elementType = 'table-row';
 | |
|                     break;
 | |
|                 case 'td':
 | |
|                 case 'th':
 | |
|                     elementType = 'table-cell';
 | |
|                     break;
 | |
|                 case 'caption':
 | |
|                     elementType = 'table-caption';
 | |
|                     break;
 | |
|                 case 'col':
 | |
|                     elementType = 'table-column';
 | |
|                     break;
 | |
|                 case 'colgroup':
 | |
|                     elementType = 'table-column-group';
 | |
|                     break;
 | |
|                 case 'table':
 | |
|                     elementType = 'table';
 | |
|                     break;
 | |
|                 case 'thead':
 | |
|                     elementType = 'table-header-group';
 | |
|                     break;
 | |
|                 case 'tbody':
 | |
|                     elementType = 'table-row-group';
 | |
|                     break;
 | |
|                 case 'tfoot':
 | |
|                     elementType = 'table-footer-group';
 | |
|                     break;
 | |
|                 default:
 | |
|                     elementType = 'block';
 | |
|                     break;
 | |
|             }
 | |
| 
 | |
|             element.style.display    = elementType;
 | |
|             element.style.visibility = 'visible';
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Shows the given element(s) (makes visible and changes opacity attribute).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          */
 | |
|         this.show = function(selector) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             if ('forEach' in element) {
 | |
|                 element.forEach(subElement => $.show(subElement));
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             this.makeVisible(element);
 | |
| 
 | |
|             element.style.opacity = 1;
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Hides the given element(s) (changes opacity and display attributes).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          */
 | |
|         this.hide = function(selector) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             if ('forEach' in element) {
 | |
|                 element.forEach(subElement => $.hide(subElement));
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const computedDisplay = getComputedStyle(element, 'display');
 | |
| 
 | |
|             if (computedDisplay !== 'none' && ! ('origDisplay' in element)) {
 | |
|                 element.origDisplay = computedDisplay;
 | |
|             }
 | |
| 
 | |
|             element.style.display    = 'none';
 | |
|             element.style.visibility = 'hidden';
 | |
|             element.style.opacity    = 0;
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Shows or hides the given element(s).
 | |
|          *
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param force     An optional boolean value that forces the class to be added or removed.
 | |
|          */
 | |
|         this.toggle = function(selector, force) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             switch (force) {
 | |
|                 case true:
 | |
|                     $.show(selector);
 | |
|                     break;
 | |
| 
 | |
|                 case false:
 | |
|                     $.hide(selector);
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     const element = typeof selector === 'string'
 | |
|                         ? document.querySelectorAll(selector)
 | |
|                         : selector;
 | |
| 
 | |
|                     if ('forEach' in element) {
 | |
|                         element.forEach(subElement => $.toggle(subElement));
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                     element.style.display === 'none' ? $.show(selector) : $.hide(selector);
 | |
|                     break;
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Fades in the given element(s).
 | |
|          *
 | |
|          * @param selector    A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param duration    Optional duration of the effect in milliseconds.
 | |
|          * @param oncomplete  Optional callback function to execute when effect is completed.
 | |
|          */
 | |
|         this.fadeIn = function(selector, duration = 250, oncomplete) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             this.makeVisible(element);
 | |
| 
 | |
|             $.fadeTo(
 | |
|                 element,
 | |
|                 1,
 | |
|                 duration,
 | |
|                 () => {
 | |
|                     $.show(element);
 | |
|                     if (oncomplete) {
 | |
|                         oncomplete.call(element);
 | |
|                     }
 | |
|                 }
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Fades out the given element(s).
 | |
|          *
 | |
|          * @param selector    A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param duration    Optional duration of the effect in milliseconds.
 | |
|          * @param oncomplete  Optional callback function to execute when effect is completed.
 | |
|          */
 | |
|         this.fadeOut = function(selector, duration = 250, oncomplete) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             $.fadeTo(
 | |
|                 element,
 | |
|                 0,
 | |
|                 duration,
 | |
|                 () => {
 | |
|                     $.hide(element);
 | |
|                     if (oncomplete) {
 | |
|                         oncomplete.call(element);
 | |
|                     }
 | |
|                 }
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Fades out the given element(s).
 | |
|          *
 | |
|          * @param selector    A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param opacity     Opacity Value to fade to
 | |
|          * @param duration    Optional duration of the effect in milliseconds.
 | |
|          * @param oncomplete  Optional callback function to execute when effect is completed.
 | |
|          */
 | |
|         this.fadeTo = function(selector, opacity, duration = 250, oncomplete) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             if ('forEach' in element) {
 | |
|                 element.forEach(subElement => $.fadeTo(subElement, opacity, duration));
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const wait        = 50; // amount of time between steps
 | |
|             const nr_of_steps = duration / wait;
 | |
|             const change      = 1 / nr_of_steps; // time to wait before next step
 | |
| 
 | |
|             element.style.opacity = getComputedStyle(element, 'opacity');
 | |
| 
 | |
|             if (opacity === element.style.opacity) {
 | |
|                 element.setAttribute('data-fading', '');
 | |
| 
 | |
|                 if (oncomplete) {
 | |
|                     oncomplete.call(element);
 | |
|                 }
 | |
| 
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             this.makeVisible(element);
 | |
| 
 | |
|             const direction = opacity > element.style.opacity ? 'in' : 'out';
 | |
| 
 | |
|             element.setAttribute('data-fading', direction);
 | |
| 
 | |
|             (function fade() {
 | |
|                 if (element.getAttribute('data-fading')
 | |
|                     && element.getAttribute('data-fading') !== direction
 | |
|                 ) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 const new_opacity = direction === 'out'
 | |
|                     ? parseFloat(element.style.opacity) - change
 | |
|                     : parseFloat(element.style.opacity) + change;
 | |
| 
 | |
|                 if ((direction === 'in' && new_opacity >= opacity)
 | |
|                     || (direction === 'out' && new_opacity <= opacity)
 | |
|                 ) {
 | |
|                     element.style.opacity = opacity;
 | |
| 
 | |
|                     element.setAttribute('data-fading', '');
 | |
| 
 | |
|                     if (oncomplete) {
 | |
|                         oncomplete.call(element);
 | |
|                     }
 | |
| 
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 element.style.opacity = new_opacity;
 | |
| 
 | |
|                 setTimeout(() => {
 | |
|                     fade.call();
 | |
|                 }, wait);
 | |
|             })();
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Runs a function when the document is loaded (on ready state).
 | |
|          *
 | |
|          * @param func  Callback function to execute when document is ready.
 | |
|          */
 | |
|         this.onReady = function(func) {
 | |
|             document.addEventListener('DOMContentLoaded', func);
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Converts a string with HTML code to 'DOM' elements.
 | |
|          *
 | |
|          * @param html  String with HTML code.
 | |
|          *
 | |
|          * @return element
 | |
|          */
 | |
|         this.createElementFromHTML = function(html) {
 | |
|             return document.createRange().createContextualFragment(html);
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Loads a url with optional POST data and optionally calls a function on success or fail.
 | |
|          *
 | |
|          * @param url      String containing the url to load.
 | |
|          * @param data     Optional string representing the POST data to send along.
 | |
|          * @param success  Optional callback function to execute when the url loads successfully (status 200).
 | |
|          * @param fail     Optional callback function to execute when the url fails to load.
 | |
|          */
 | |
|         this.loadUrl = function(url, data, success, fail) {
 | |
|             const request = new XMLHttpRequest();
 | |
| 
 | |
|             request.open('POST', url, true);
 | |
| 
 | |
|             request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
 | |
| 
 | |
|             request.onreadystatechange = function() {
 | |
|                 if (this.readyState !== 4) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (this.status === 200) {
 | |
|                     success && success.call(null, this.responseText, this.status, this);
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 fail && fail.call(null, this.responseText, this.status, this);
 | |
|             };
 | |
| 
 | |
| 
 | |
|             request.send(this.toUrlQueryString(data));
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Converts a data object (key, value) to a serialized query string.
 | |
|          *
 | |
|          * @param data    The object with the data to serialize.
 | |
|          * @param prefix  An Optional prefix.
 | |
|          */
 | |
|         this.toUrlQueryString = function(data, prefix) {
 | |
|             if (typeof data !== 'object') {
 | |
|                 return data;
 | |
|             }
 | |
| 
 | |
|             const parts = [];
 | |
| 
 | |
|             if ( ! (Symbol.iterator in Object(data))) {
 | |
|                 data = Object.entries(data);
 | |
|             }
 | |
| 
 | |
|             for (let i in data) {
 | |
|                 let value = data[i];
 | |
|                 let name  = '';
 | |
| 
 | |
|                 if (value instanceof Array) {
 | |
|                     [name, value] = value;
 | |
|                 }
 | |
| 
 | |
|                 let key = name ? (prefix ? `${prefix}[${name}]` : name) : prefix;
 | |
| 
 | |
|                 if ( ! key) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 if (value !== null && typeof value === 'object') {
 | |
|                     if (value instanceof Array) {
 | |
|                         key += '[]';
 | |
|                     }
 | |
| 
 | |
|                     parts.push(this.toUrlQueryString(value, key));
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 parts.push(`${key}=${value}`);
 | |
|             }
 | |
| 
 | |
|             return parts.join('&');
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          *
 | |
|          * ALIASES
 | |
|          *
 | |
|          */
 | |
| 
 | |
|         this.as          = this.alias;
 | |
|         this.hasClass    = this.hasClasses;
 | |
|         this.addClass    = this.addClasses;
 | |
|         this.removeClass = this.removeClasses;
 | |
|         this.toggleClass = this.toggleClasses;
 | |
| 
 | |
|         /**
 | |
|          *
 | |
|          * PRIVATE FUNCTIONS
 | |
|          *
 | |
|          */
 | |
| 
 | |
|         /**
 | |
|          * Executes an action on the element(s) to add/remove/toggle classes.
 | |
|          *
 | |
|          * @param action    A string that identifies the action: add|remove|toggle.
 | |
|          * @param selector  A CSS selector string, a HTMLElement object or a collection of HTMLElement objects.
 | |
|          * @param classes   A string or array of class names.
 | |
|          */
 | |
|         const doClasses = function(action, selector, classes) {
 | |
|             if ( ! selector) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const element = typeof selector === 'string'
 | |
|                 ? document.querySelectorAll(selector)
 | |
|                 : selector;
 | |
| 
 | |
|             if ('forEach' in element) {
 | |
|                 element.forEach(subElement => doClasses(action, subElement, classes));
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (typeof classes === 'string') {
 | |
|                 classes = classes.split(' ');
 | |
|             }
 | |
| 
 | |
|             element.classList[action](...classes);
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Finds the computed style of an element.
 | |
|          *
 | |
|          * @param element   A HTMLElement object.
 | |
|          * @param property  The style property that needs to be returned.
 | |
|          *
 | |
|          * @returns mixed
 | |
|          */
 | |
|         const getComputedStyle = function(element, property) {
 | |
|             if ( ! element) {
 | |
|                 return null;
 | |
|             }
 | |
| 
 | |
|             return window.getComputedStyle(element).getPropertyValue(property);
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          * Finds the default computed style of an element by its type.
 | |
|          *
 | |
|          * @param element   A HTMLElement object.
 | |
|          * @param property  The style property that needs to be returned.
 | |
|          *
 | |
|          * @returns mixed
 | |
|          */
 | |
|         const getDefaultComputedStyle = function(element, property) {
 | |
|             if ( ! element) {
 | |
|                 return null;
 | |
|             }
 | |
| 
 | |
|             const defaultElement = document.createElement(element.nodeName);
 | |
| 
 | |
|             document.body.append(defaultElement);
 | |
|             let propertyValue = window.getComputedStyle(defaultElement).getPropertyValue(property);
 | |
|             defaultElement.remove();
 | |
| 
 | |
|             return propertyValue;
 | |
|         };
 | |
| 
 | |
|         /**
 | |
|          *
 | |
|          * PRIVATE VARIABLES
 | |
|          *
 | |
|          */
 | |
| 
 | |
|         /**
 | |
|          * @param  $  internal shorthand for the 'this' keyword.
 | |
|          */
 | |
|         const $ = this;
 | |
|     };
 | |
| }
 |