class JVB { constructor(config = {}) { this.config = config; this.content = null; this.resetFilters(); this.initLoading(); this.events = new Map(); //ICONS this.icons = new Map(); this.templates = new Map(); //DEBOUNCER this.timeouts = new Map(); window.addEventListener('beforeunload', () => this.cleanup()); this.loadTemplates(); } /******************************************** * FILTERS ********************************************/ resetFilters(elements = false) { this.filters = { content: this.content, status: 'all', taxonomies: {}, page: 1, order: 'DESC', orderby: 'date', ... this.config.filters } if (elements) { let checks = [this.filters.status, this.filters.order, this.filters.orderby]; checks.forEach(check => { let item = this.elements.filters.querySelector(`[data-filter][value="${check}"]`); if (item) { item.checked = true; } }); this.elements.filters.querySelectorAll('select').forEach(select => { select.value = ''; }); this.updateClearFiltersButton(); this.hasMore = true; this.loadContent(true); } } /******************************************** * EVENTS ********************************************/ on(elem, event, handler) { if (!this.events.has(elem)) { this.events.set(elem, new Map()); } this.events.get(elem).set(event, handler); elem.addEventListener(event, handler); } off(elem, event, handler) { this.events.get(elem).delete(event); elem.removeEventListener(event, handler); } /******************************************** * LOADING ********************************************/ initLoading() { this.isLoading = false; this.canLoad = true; let overlay = document.querySelector('.loading-overlay'); if (!overlay) { this.canLoad = false; return; } this.loading = { overlay: overlay, message: overlay.querySelector('.message'), title: overlay.querySelector('h3'), iconContainer: overlay.querySelector('div.icon'), icon: (this.content !== '') ? this.content : 'logo', quipInterval: null } this.quips = [ 'Loading', 'Hang in there', 'Getting things together' ]; } setLoading(isLoading) { if (!this.canLoad) { return; } this.isLoading = isLoading; isLoading ? this.showLoading() : this.hideLoading(); } showLoading(message = null, title = 'Loading') { this.isLoading = true; this.loading.title.textContent = title; if (message) { this.loading.message.textContent = message; } document.body.classList.add('loading'); document.body.style.overflow = 'hidden'; this.startQuips(); } hideLoading() { document.body.classList.remove('loading'); document.body.style.overflow = ''; this.stopQuips(); this.isLoading = false; } startQuips() { if (this.loading.quipInterval) { clearInterval(this.loading.quipInterval); } let quips = this.shuffleArray(this.quips); let index = 1; let content = quips[0]; let lastContent = quips[0]; this.loading.message.textContent = content; this.loading.message.classList.remove('changing'); this.loading.quipInterval = setInterval( () => { this.loading.message.classList.add('changing'); setTimeout(() => { index = (index + 1) % quips.length; content = quips[index]; this.removeChildren(this.loading.iconContainer); this.loading.iconContainer.append(this.getIcon(this.loading.icon)); this.typeLoop( this.loading.message, content ); this.loading.message.classList.remove('changing'); lastContent = content; }); }, 2000 ); } stopQuips() { if (this.loading.quipInterval) { clearInterval(this.loading.quipInterval); this.loading.quipInterval = null; } } /******************************************** * TEMPLATES ********************************************/ loadTemplates() { document.querySelectorAll('template').forEach(template => { const classes = Array.from(template.classList); if (classes.length > 0) { const item = template.content.cloneNode(true).firstElementChild; classes.forEach(key => { if (!this.templates.has(key)) { this.templates.set(key, item); } }); } }); } getTemplate(template) { if (this.templates.size === 0) { this.loadTemplates(); } if (window.templates.has(template)) { return window.templates.get(template).cloneNode(true); } return false; } /******************************************** * ICONS ********************************************/ getIcon(icon) { console.log('Getting Icon: '+icon); if (typeof icon === 'undefined') { return ''; } if (!this.icons.has(icon) && jvbSettings.icons[icon]) { let temp = document.createElement('div'); temp.innerHTML = jvbSettings.icons[icon]; this.icons.set(icon, temp.firstElementChild.cloneNode(true)); temp.remove(); } return this.icons.get(icon)?.cloneNode(true); } /******************************************** * UTILITY ********************************************/ shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } isEmptyObject(obj) { return Object.keys(obj).length === 0; } ucFirst(string) { return string.charAt(0).toUpperCase() + string.slice(1); } escapeHtml(text) { if (!text) return ''; // Convert to string if it's not already a string if (typeof text !== "string" && !(text instanceof String)) { text = String(text); } return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } sanitizeHtml(text) { let div = this.getIcon('back'); div.textContent = text; return div.innerHTML; } limitText(text, length = 100) { if (!text || text.length <= length) return text; return text.substring(0, length) + '...'; } removeChildren(node) { if (node.children.length === 0) { return; } while (node.firstChild) { node.removeChild(node.firstChild); } } typeText (container, text, speed = 50) { container.classList.add('typeText'); return new Promise((resolve) => { let index = 0; container.textContent = ''; const interval = setInterval(() => { if (index < text.length) { container.textContent += text.charAt(index); index++; } else { clearInterval(interval); resolve(); } }, speed); }); } eraseText (container, speed = 10) { return new Promise((resolve) => { let text = container.textContent; let index = text.length; const interval = setInterval(() => { if (index > 0) { index--; container.textContent = text.substring(0, index); } else { clearInterval(interval); resolve(); } }, speed); }); } typeLoop(container, text, typeSpeed = 50, eraseSpeed = 10) { let isRunning = true; async function loop() { while (isRunning) { // Type the text await window.typeText(container, text, typeSpeed); // Wait 1 second await new Promise(resolve => setTimeout(resolve, pauseAfterType)); // Erase the text await window.eraseText(container, eraseSpeed); // Wait 0.25 seconds before next iteration await new Promise(resolve => setTimeout(resolve, pauseAfterErase)); } } // Start the loop loop(); // Return a function to stop the loop return function stopLoop() { isRunning = false; }; } targetCheck(e, selector) { if (typeof selector !== 'string') { return false; } return e.target.closest(selector)??false; } /******************************************** * REQUESTS ********************************************/ async getRequest(endpoint, params, headers = {}, reset = false, force = false) { if (this.isLoading || !this.hasMore) return; try { this.setLoading(true); if (reset) { this.filters.page = 1; this.clearContent(); } const filters = this.buildFilters(); const data = await this.cache.fetchWithCache( `${jvbSettings.api}${endpoint}?${filters.toString()}`, { method: 'GET', headers: { 'X-WP-Nonce': jvbSettings.nonce, ... headers } }, { context: this.content, forceRefresh: force } ); } } //Overridden by child classes buildParams() { return ''; } setRequest() { } /******************************************** * DEBOUNCER ********************************************/ schedule(key, callback, delay = 1000) { this.cancel(key); this.timeouts.set(key, setTimeout( () => { callback(); this.timeouts.delete(key); }, delay )); } cancel(key) { if (this.timeouts.has(key)) { clearTimeout(this.timeouts.get(key)); this.timeouts.delete(key); } } /******************************************* * CLEANUP *******************************************/ cleanup() { for (let [elem, value] of this.events) { for (let [event, handler] of value) { elem.removeEventListener(event, handler); } } for (let timeout of this.timeouts.values()) { clearTimeout(timeout); } this.timeouts.clear(); } } window.JVB = new JVB();