From 7aa203150fc1a2ec79c681c13f0d486d531746eb Mon Sep 17 00:00:00 2001 From: George Halkiadakis Date: Sun, 12 Mar 2023 23:57:48 +0200 Subject: error pages; js system to support back/front-end --- html/assets/js/utils/apiready.js | 15 ++ html/assets/js/utils/confirm.js | 27 +++ html/assets/js/utils/debounce.js | 16 ++ html/assets/js/utils/defineApi.js | 9 + html/assets/js/utils/destroyevent.js | 11 + html/assets/js/utils/dom.js | 68 +++++++ html/assets/js/utils/domHelper.js | 375 +++++++++++++++++++++++++++++++++++ html/assets/js/utils/enabled.js | 6 + html/assets/js/utils/event.js | 13 ++ html/assets/js/utils/eventUtils.js | 75 +++++++ html/assets/js/utils/getApis.js | 17 ++ html/assets/js/utils/throttle.js | 15 ++ html/assets/js/utils/whenAll.js | 13 ++ 13 files changed, 660 insertions(+) create mode 100644 html/assets/js/utils/apiready.js create mode 100644 html/assets/js/utils/confirm.js create mode 100644 html/assets/js/utils/debounce.js create mode 100644 html/assets/js/utils/defineApi.js create mode 100644 html/assets/js/utils/destroyevent.js create mode 100644 html/assets/js/utils/dom.js create mode 100644 html/assets/js/utils/domHelper.js create mode 100644 html/assets/js/utils/enabled.js create mode 100644 html/assets/js/utils/event.js create mode 100644 html/assets/js/utils/eventUtils.js create mode 100644 html/assets/js/utils/getApis.js create mode 100644 html/assets/js/utils/throttle.js create mode 100644 html/assets/js/utils/whenAll.js (limited to 'html/assets/js/utils') diff --git a/html/assets/js/utils/apiready.js b/html/assets/js/utils/apiready.js new file mode 100644 index 0000000..17f1ad4 --- /dev/null +++ b/html/assets/js/utils/apiready.js @@ -0,0 +1,15 @@ +define(function () { + return function ($element, name) { + return new Promise(function (resolve, reject) { + var api = $element.data(name); + if (api) { + resolve(api); + } + else { + $element.one(name + '.ready', function () { + resolve($element.data(name)); + }); + } + }); + }; +}); \ No newline at end of file diff --git a/html/assets/js/utils/confirm.js b/html/assets/js/utils/confirm.js new file mode 100644 index 0000000..ef59cbb --- /dev/null +++ b/html/assets/js/utils/confirm.js @@ -0,0 +1,27 @@ +define(function () { + return function (options, callback) { + var $dialog = $(options.template instanceof jQuery ? options.template.html() : options.template); + $dialog.hide(); + $('body').append($dialog); + $dialog.parseAttributePlugins(); + + function close() { + $dialog.remove(); + } + + function open() { + $dialog.show(); + } + + open(); + $dialog.find('.button.yes').click(function (e) { + e.preventDefault(); + close(); + callback(); + }); + $dialog.find('.button.no').click(function (e) { + e.preventDefault(); + close(); + }); + }; +}); \ No newline at end of file diff --git a/html/assets/js/utils/debounce.js b/html/assets/js/utils/debounce.js new file mode 100644 index 0000000..f81d9a5 --- /dev/null +++ b/html/assets/js/utils/debounce.js @@ -0,0 +1,16 @@ +define('debounce', function () { + return function (func, wait, immediate) { + var timeout; + return function () { + var context = this, args = arguments; + var later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; + }; +}); diff --git a/html/assets/js/utils/defineApi.js b/html/assets/js/utils/defineApi.js new file mode 100644 index 0000000..3215714 --- /dev/null +++ b/html/assets/js/utils/defineApi.js @@ -0,0 +1,9 @@ +define(function () { + return function ($element, name, api) { + if (typeof (api) === 'undefined' || !api) { + throw 'undefined api'; + } + $element.data(name, api); + $element.trigger(name + '.ready'); + }; +}); \ No newline at end of file diff --git a/html/assets/js/utils/destroyevent.js b/html/assets/js/utils/destroyevent.js new file mode 100644 index 0000000..e6fe48a --- /dev/null +++ b/html/assets/js/utils/destroyevent.js @@ -0,0 +1,11 @@ +define(function () { + (function ($) { + $.event.special.destroyed = { + remove: function (o) { + if ((o.handler && o.type !== 'destroyed')) { + o.handler() + } + } + } + })(jQuery) +}); \ No newline at end of file diff --git a/html/assets/js/utils/dom.js b/html/assets/js/utils/dom.js new file mode 100644 index 0000000..78f3562 --- /dev/null +++ b/html/assets/js/utils/dom.js @@ -0,0 +1,68 @@ +define(function () { + const reduce = (elements, fn, initialValue) => { + return Array.prototype.reduce.call(elements, fn, initialValue); + } + + const forEach = (elements, fn) => { + Array.prototype.forEach.call(elements, fn); + } + const map = (elements, fn) => { + return Array.prototype.map.call(elements, fn); + } + + const replace = (toReplace, replacement) => { + var parentElement = toReplace.parentElement + if (parentElement) { + parentElement.replaceChild(replacement, toReplace); + } + } + + const matches = (element, selector) => { + var m = element.matches + || Element.prototype.matchesSelector + || Element.prototype.mozMatchesSelector + || Element.prototype.msMatchesSelector + || Element.prototype.oMatchesSelector + || Element.prototype.webkitMatchesSelector; + return m.call(element, selector); + } + + const closest = (element, selector) => { + while (element) { + if (matches(element, selector)) { + return element; + } else { + element = element.parentElement; + } + } + return null; + } + const isFunction = (input) => (typeof input === "function"); + const dispatchEvent = (eventName, data) => { + let event; + if (window.CustomEvent && isFunction(window.CustomEvent)) { + event = new CustomEvent(eventName, { detail: data, cancelable: true, bubbles: false }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventName, false, true, data); + } + return window.document.dispatchEvent(event); + } + + const parseHtml = (htmlString) => { + const c = document.createElement('div'); + c.innerHTML = htmlString.trim(); + return c.firstChild; + } + + return { + dispatchEvent, + closest, + matches, + reduce, + map, + forEach, + replace, + parseHtml + }; +}); \ No newline at end of file diff --git a/html/assets/js/utils/domHelper.js b/html/assets/js/utils/domHelper.js new file mode 100644 index 0000000..f36ab1a --- /dev/null +++ b/html/assets/js/utils/domHelper.js @@ -0,0 +1,375 @@ +define(function () { + + function isElement(obj) { + //instanceof fails for elements that have been created by another document (iframe case) + //in that case we check other properties existing in all element nodes + return obj instanceof HTMLElement || ((typeof obj === "object") && + (obj.nodeType === 1) && (typeof obj.style === "object") && + (typeof obj.ownerDocument === "object")); + } + + let slideUp = (target, duration = 500, callback = null) => { + target.style.transitionProperty = 'height, margin, padding'; + target.style.transitionDuration = duration + 'ms'; + target.style.boxSizing = 'border-box'; + target.style.height = target.offsetHeight + 'px'; + target.offsetHeight; + target.style.overflow = 'hidden'; + target.style.height = 0; + target.style.paddingTop = 0; + target.style.paddingBottom = 0; + target.style.marginTop = 0; + target.style.marginBottom = 0; + window.setTimeout(() => { + target.style.display = 'none'; + target.style.removeProperty('height'); + target.style.removeProperty('padding-top'); + target.style.removeProperty('padding-bottom'); + target.style.removeProperty('margin-top'); + target.style.removeProperty('margin-bottom'); + target.style.removeProperty('overflow'); + target.style.removeProperty('transition-duration'); + target.style.removeProperty('transition-property'); + if (callback) { + callback(); + } + }, duration); + } + + let slideDown = (target, duration = 500, callback = null) => { + target.style.removeProperty('display'); + let display = window.getComputedStyle(target).display; + + if (display === 'none') + display = 'block'; + + target.style.display = display; + let height = target.offsetHeight; + target.style.overflow = 'hidden'; + target.style.height = 0; + target.style.paddingTop = 0; + target.style.paddingBottom = 0; + target.style.marginTop = 0; + target.style.marginBottom = 0; + target.offsetHeight; + target.style.boxSizing = 'border-box'; + target.style.transitionProperty = "height, margin, padding"; + target.style.transitionDuration = duration + 'ms'; + target.style.height = height + 'px'; + target.style.removeProperty('padding-top'); + target.style.removeProperty('padding-bottom'); + target.style.removeProperty('margin-top'); + target.style.removeProperty('margin-bottom'); + window.setTimeout(() => { + target.style.removeProperty('height'); + target.style.removeProperty('overflow'); + target.style.removeProperty('transition-duration'); + target.style.removeProperty('transition-property'); + if (callback) { + callback(); + } + }, duration); + } + + var slideToggle = (target, duration = 500, callback = null) => { + if (window.getComputedStyle(target).display === 'none') { + return slideDown(target, duration, callback); + } else { + return slideUp(target, duration, callback); + } + } + + function resolveSelector(selector, context) { + if (typeof (selector) !== "string") { + if (context) { + throw 'Context is allowed only when selector is a string.'; + } + if (Array.isArray(selector)) { + return selector; + } + if (isElement(selector)) { + return [selector]; + } + + throw 'Selector can either be a string or an array'; + } + if (!context) { + return document.querySelectorAll(selector); + } + if (typeof (context) == "string") { + return resolveSelector(selector).filter(element => element.matches(selector)); + } + else if (Array.isArray(context)) { + return context.filter(element => element.matches(selector)); + } + else if (isElement(context)) { + return context.querySelectorAll(selector); + } + else { + throw 'Unknown context type: ' + typeof (context) + ". Not a string, array or HTMLElement."; + } + } + + var factory = (target, context) => new domHelper(target, context); + function domHelper(target, context) { + this.elements = Array.from(resolveSelector(target, context)); + this.on = function (event, callback) { + for (let i = 0; i < this.elements.length; i++) { + this.elements[i].addEventListener(event, function (e) { + callback.apply(e.target, arguments); + }); + } + return this; + } + this.parent = function (selector) { + if (!selector) { + return factory( + this.elements.map(e => e.parentElement).filter(e => e) + ); + } + return factory( + this.elements.map(element => { + do { + if (element.parentElement.matches(selector)) { + return element.parentElement; + } + element = element.parentElement; + + } while (element.parentElement); + return null; + }).filter(e => e) + ); + } + this.parents = function (selector) { + var parents = this.elements.map(e => { + var r = []; + while (e.parentElement) { + r.push(e.parentElement); + e = e.parentElement; + } + return r; + }).reduce((acc, curVal) => { + return acc.concat(curVal) + }).filter(e => e); + + if (!selector) { + return factory(parents); + } + return factory(selector, parents); + } + this.find = function (selector) { + return factory(this.elements.map(e => { + return factory(selector, e).elements; + }).reduce((acc, curVal) => { + return acc.concat(curVal) + })); + } + this.show = function () { + this.each(e => e.style.display = "block"); + } + this.hide = function () { + this.each(e => e.style.display = "none"); + } + this.innerWidth = function () { + var element = this.elements[0]; + return parseInt(window.getComputedStyle(element).width); + } + this.toggle = function () { + this.each(e => { + if ((e.offsetParent === null)) // hidden + { + e.style.display = "block"; + } + else { + e.style.display = "none"; + } + }); + } + this.each = function (callback) { + this.elements.forEach(callback); + return this; + } + this.scroll = function () { + this.each(element => element.scroll.apply(element, arguments)); + return this; + } + this.addClass = function (cssClass) { + if (!cssClass) { + return; + } + var classes = cssClass.split(' '); + this.elements.forEach(element => { + classes.forEach(cssClass => { + element.classList.add(cssClass); + }); + }); + return this; + } + this.removeClass = function (cssClass) { + if (!cssClass) { + return; + } + var classes = cssClass.split(' '); + this.elements.forEach(element => { + classes.forEach(cssClass => { + element.classList.remove(cssClass); + }); + }); + return this; + } + this.toggleClass = function (cssClass) { + if (!cssClass) { + return; + } + var classes = cssClass.split(' '); + this.elements.forEach(element => { + classes.forEach(cssClass => { + element.classList.toggle(cssClass); + }); + }); + return this; + } + this.is = function (selector) { + return factory(selector, this.elements).elements.length > 0; + } + this.not = function (exclude) { + if (typeof (exclude) == "string") { + return factory(this.elements.filter(e => !e.matches(exclude))); + } + else if (Array.isArray(exclude)) { + return factory(this.elements.filter(e => exclude.indexOf(e) == -1)); + } + else if (isElement(exclude)) { + return factory(this.elements.filter(e => e != exclude)); + } + else if (exclude instanceof domHelper) { + return factory(this.elements.filter(e => exclude.elements.indexOf(e) == -1)); + } + else { + throw 'Unknown exclude type: ' + typeof (exclude) + ". Not a string, array or HTMLElement or domHelper."; + } + } + this.get = (index) => this.elements[index]; + this.length = this.elements.length; + this.siblings = function (selector) { + var next = this.next(selector); + var prev = this.prev(selector); + return factory(prev.elements.concat(next.elements)); + } + this.next = function (selector) { + var siblings = this.elements.map(e => { + var r = []; + while (e.nextElementSibling) { + r.push(e.nextElementSibling); + e = e.nextElementSibling; + } + return r; + }).reduce((acc, curVal) => { + return acc.concat(curVal) + }).filter(e => e); + + if (!selector) { + return factory(siblings); + } + return factory(selector, siblings); + } + this.prev = function (selector) { + var siblings = this.elements.map(e => { + var r = []; + while (e.previousElementSibling) { + r.push(e.previousElementSibling); + e = e.previousElementSibling; + } + return r; + }).reduce((acc, curVal) => { + return acc.concat(curVal) + }).filter(e => e); + + if (!selector) { + return factory(siblings); + } + return factory(selector, siblings); + } + this.index = function () { + var items = []; + this.prev().each(function (e) { + items.push(e); + }); + items.push(this.get(0)); + this.next().each(function (e) { + items.push(e); + }); + return items.indexOf(this.get(0)); + } + this.slideDown = function (duration, callback) { + return this.each(function (e) { + slideDown(e, duration, callback); + }); + } + this.slideUp = function (duration, callback) { + return this.each(function (e) { + slideUp(e, duration, callback); + }); + } + this.slideToggle = function (duration, callback) { + return this.each(function (e) { + slideToggle(e, duration, callback); + }); + } + this.removeAttr = function (attribute) { + this.each(e => e.removeAttribute(attribute)); + } + this.hasClass = function (cssClass) { + if (!cssClass) { + return; + } + return new RegExp('(\\s|^)' + cssClass + '(\\s|$)').test(this.get(0).className); + } + this.val = function (inputValue) { + if (typeof (inputValue) === 'undefined') { + return this.elements[0].value; + } + return this.each(function (e) { + e.value = inputValue; + }); + } + this.data = function (attribute, inputValue) { + if (typeof (inputValue) === 'undefined') { + return this.elements[0].dataset[attribute]; + } + return this.each(function (e) { + e.dataset[attribute] = inputValue; + }); + } + this.text = function (inputValue) { + if (typeof (inputValue) === 'undefined') { + return this.elements[0].innerText; + } + return this.each(function (e) { + e.innerText = inputValue; + }); + } + this.contains = function (element) { + return this.elements.indexOf(element) >= 0; + } + + this.trigger = function (eventName) { + + if ("createEvent" in document) { + var evt = document.createEvent("HTMLEvents"); + evt.initEvent(eventName, false, true); + return this.each(function (e) { + e.dispatchEvent(evt); + }); + } + else { + return this.each(function (e) { + e.fireEvent("on" + eventName); + }); + + } + } + } + + return factory; +}); \ No newline at end of file diff --git a/html/assets/js/utils/enabled.js b/html/assets/js/utils/enabled.js new file mode 100644 index 0000000..658b2dc --- /dev/null +++ b/html/assets/js/utils/enabled.js @@ -0,0 +1,6 @@ +define(function () { + $.fn.enabled = function (e) { + $(this).prop('disabled', !e); + $(this).trigger('enabled:change', e); + }; +}); \ No newline at end of file diff --git a/html/assets/js/utils/event.js b/html/assets/js/utils/event.js new file mode 100644 index 0000000..7a5ada8 --- /dev/null +++ b/html/assets/js/utils/event.js @@ -0,0 +1,13 @@ +define(function () { + return function () { + var handlers = []; + this.bind = function (handler) { + handlers.push(handler); + } + this.trigger = function (_this, arguments) { + for (var i = 0; i < handlers.length; i++) { + handlers[i].apply(_this, arguments); + } + } + } +}); \ No newline at end of file diff --git a/html/assets/js/utils/eventUtils.js b/html/assets/js/utils/eventUtils.js new file mode 100644 index 0000000..21a1ce9 --- /dev/null +++ b/html/assets/js/utils/eventUtils.js @@ -0,0 +1,75 @@ +(function(){ + var _ = {}; + _.now = Date.now || function(){ + return new Date().getTime(); + }; + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function() { + previous = options.leading === false ? 0 : _.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + var now = _.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + var later = function() { + var last = _.now() - timestamp; + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + return function() { + context = this; + args = arguments; + timestamp = _.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + return result; + }; + }; + + window.EventUtils = _; +}()); \ No newline at end of file diff --git a/html/assets/js/utils/getApis.js b/html/assets/js/utils/getApis.js new file mode 100644 index 0000000..46788a6 --- /dev/null +++ b/html/assets/js/utils/getApis.js @@ -0,0 +1,17 @@ +define(['apiReady', 'whenAll'], function (apiReady, whenAll) { + return function ($elements, name) { + var result = []; + var d = $.Deferred(); + var p = $elements.map(function () { + var $element = $(this); + return apiReady($element, name).then(function (api) { + result.push({ api: api, $element: $element }); + }); + }); + whenAll(p).then(function () { + d.resolve(result); + }); + + return d; + }; +}); \ No newline at end of file diff --git a/html/assets/js/utils/throttle.js b/html/assets/js/utils/throttle.js new file mode 100644 index 0000000..ccfc9a7 --- /dev/null +++ b/html/assets/js/utils/throttle.js @@ -0,0 +1,15 @@ +define(function () { + return function (func, limit) { + var timeout; + var inThrottle; + return function () { + var context = this, args = arguments; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + } + setTimeout(() => inThrottle = false, limit); + + }; + }; +}); diff --git a/html/assets/js/utils/whenAll.js b/html/assets/js/utils/whenAll.js new file mode 100644 index 0000000..8d1c3e2 --- /dev/null +++ b/html/assets/js/utils/whenAll.js @@ -0,0 +1,13 @@ +define(function () { + return function (deferreds) { + return $.Deferred(function (def) { + $.when.apply(jQuery, deferreds).then( + function () { + def.resolveWith(this, [Array.prototype.slice.call(arguments)]); + }, + function () { + def.rejectWith(this, [Array.prototype.slice.call(arguments)]); + }); + }); + }; +}); -- cgit v1.2.3