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) {
|
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, """)
|
.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();
|