From 7a9054bb3f033c98067b3196378311dae54c5fbf Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 20 Jan 2026 01:31:53 +0000
Subject: [PATCH] =OperationQueue refactor to the JVBase/managers/queue namespace
---
assets/js/concise/UtilityFunctions.js | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 156 insertions(+), 4 deletions(-)
diff --git a/assets/js/concise/UtilityFunctions.js b/assets/js/concise/UtilityFunctions.js
index d80a661..2381056 100644
--- a/assets/js/concise/UtilityFunctions.js
+++ b/assets/js/concise/UtilityFunctions.js
@@ -152,6 +152,118 @@
return false;
}
+
+
+
+/**
+ * Load and instantiate HTML templates as lightweight components
+ */
+class TemplateRegistry {
+ constructor() {
+ this.templates = new Map(); // name -> <template>
+ this.definitions = new Map(); // name -> component definition
+ }
+
+ /**
+ * Collect all <template class="name"> elements
+ */
+ registerAll(root = document) {
+ root.querySelectorAll('template').forEach(tpl => {
+ tpl.classList.forEach(name => {
+ if (!this.templates.has(name)) {
+ this.templates.set(name, tpl);
+ }
+ });
+ });
+ }
+
+ /**
+ * Define component behavior
+ */
+ define(name, definition = {}, context = null) {
+ this.definitions.set(name, {
+ refs: definition.refs || null,
+ manyRefs: definition.manyRefs || null,
+ setup: definition.setup || null,
+ context: context
+ });
+ }
+
+ /**
+ * Create a component instance
+ */
+ create(name, data = {}) {
+ const tpl = this.templates.get(name);
+ if (!tpl) {
+ console.warn(`[TemplateRegistry] Template "${name}" not found`);
+ return null;
+ }
+
+ const element = tpl.content.cloneNode(true).firstElementChild;
+ if (!element) return null;
+
+ const def = this.definitions.get(name);
+ const refs = def?.refs
+ ? this.#collectRefs(element, def.refs)
+ : {};
+ const manyRefs = def?.manyRefs
+ ? this.#collectRefs(element, def.manyRefs, false)
+ : {};
+
+ def?.setup?.({
+ el: element,
+ refs,
+ manyRefs,
+ data
+ });
+
+ return element;
+ }
+
+ /**
+ * Resolve refs declared in component definition
+ */
+ #collectRefs(root, refMap, single = true) {
+ const refs = {};
+
+ for (const [key, value] of Object.entries(refMap)) {
+ let selector;
+ let required = false;
+
+ if (typeof value === 'string') {
+ selector = value;
+ } else {
+ selector = value.selector;
+ required = !!value.required;
+ }
+
+ const found = (single) ? root.querySelector(selector) : root.querySelectorAll(selector);
+
+ if (required) {
+ if (single && !found) {
+ console.warn(`[TemplateRegistry] Required ref "${key}" not found: ${selector}`);
+ }
+
+ if (!single && found.length === 0) {
+ console.warn(`[TemplateRegistry] Required manyRef "${key}" not found: ${selector}`);
+ }
+ }
+
+ refs[key] = single ? found : Array.from(found);
+ }
+
+ return refs;
+ }
+
+}
+
+
+window.jvbTemplates = new TemplateRegistry();
+
+document.addEventListener('DOMContentLoaded', () => {
+ window.jvbTemplates.registerAll();
+});
+
/**
* Gets a clone of an icon element if it exists for efficient DOM manipulation
* @param icon
@@ -283,6 +395,46 @@
};
}
+window.chunkIt = async function(items, renderCallback, placementCallback, size = 10) {
+ const chunks = [];
+ for (let i = 0; i <items.length; i += size) {
+ chunks.push(items.slice(i, i + size));
+ }
+
+ for (const chunk of chunks) {
+ const fragment = document.createDocumentFragment();
+ chunk.forEach(item => {
+ const element = renderCallback(item);
+ if (element) fragment.append(element);
+ });
+
+ placementCallback(fragment);
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+}
+
+window.prefixInput = function(input, prefix, replace = false) {
+ let newId = replace ? prefix : `${prefix}${input.name}`;
+ if (input.labels.length > 0) {
+ input.labels?.forEach(label => {
+ label.htmlFor = newId;
+ });
+ } else {
+ if (input.nextElementSibling?.tagName === 'LABEL') {
+ input.nextElementSibling.htmlFor = newId;
+ }else if (input.previousElementSibling?.tagName === 'LABEL') {
+ input.previousElementSibling.htmlFor = newId;
+ } else {
+ let label = input.parentElement.querySelector(`label[for="${input.id}"]`);
+ if (label) {
+ label.htmlFor = newId;
+ }
+ }
+ }
+
+ input.id = newId;
+}
+
/**
* Makes first letter uppercase
* @param string
@@ -713,23 +865,23 @@
* @param {HTMLElement|null} parent
* @returns {object}
*/
-window.uiFromSelectors = function(selectors, parent = null) {
+window.uiFromSelectors = function(selectors, parent = null, all = false) {
let ui = {};
for (let [key, selector] of Object.entries(selectors)) {
if (typeof selector === 'object') {
ui[key] = window.uiFromSelectors(selector, parent);
}else {
if (!parent) {
- ui[key] = document.querySelector(selector);
+ ui[key] = (all) ? document.querySelectorAll(selector) : document.querySelector(selector);
} else {
- ui[key] = parent.querySelector(selector);
+ ui[key] = (all) ? parent.querySelectorAll(selector) : parent.querySelector(selector);
}
-
}
}
return ui;
}
+
class DebouncedActions {
constructor() {
this.timeouts = new Map();
--
Gitblit v1.10.0