document.addEventListener('DOMContentLoaded', () => {
// Listen for back/forward browser buttons
window.addEventListener('popstate', (e) => {
if (e.state && e.state.url) {
fetchPage(e.state.url, false);
} else {
fetchPage(window.location.href, false);
}
});
// Intercept clicks globally
document.body.addEventListener('click', (e) => {
// Find closest anchor tag
const a = e.target.closest('a');
if (!a) return;
// Ignore external links, new tabs, and non-http links
if (a.target === '_blank' || a.host !== window.location.host || a.protocol !== window.location.protocol) return;
// Ignore links meant for modals or javascript functions
const href = a.getAttribute('href');
if (!href || href === '#' || href.startsWith('javascript:') || a.hasAttribute('data-toggle')) return;
// Only apply SPA to internal .php pages or root
if (a.pathname.endsWith('.php') || a.pathname === '/' || a.pathname === '') {
e.preventDefault();
const url = a.href;
if (url !== window.location.href) {
fetchPage(url, true);
}
}
});
});
function getSkeletonHtml() {
return `
`;
}
function fetchPage(url, push = true) {
const mainWrapper = document.querySelector('.main-panel');
if (!mainWrapper) {
window.location.href = url; // Hard fallback if structure is weird
return;
}
let contentWrapper = mainWrapper.querySelector('.content-wrapper');
if(contentWrapper) {
// Show skeleton immediately
contentWrapper.innerHTML = getSkeletonHtml();
}
// Update active state in sidebar immediately to provide UX feedback
updateSidebarActiveState(url);
fetch(url)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.text();
})
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Extract and update title
const newTitle = doc.querySelector('title');
if (newTitle) {
document.title = newTitle.textContent;
}
// Extract new main-panel
const newMain = doc.querySelector('.main-panel');
if (newMain && mainWrapper) {
mainWrapper.innerHTML = newMain.innerHTML;
// Push history state to URL
if (push) {
history.pushState({url: url}, '', url);
}
// Execute inline scripts that come natively with the injected HTML
executeScripts(mainWrapper);
// Dispatch event so global listeners (like DataTables) can reinitialize
document.dispatchEvent(new CustomEvent('spaContentReplaced', { detail: { url: url } }));
} else {
// If the target page doesn't have the expected structure, fallback to full load
window.location.href = url;
}
})
.catch(err => {
console.error('SPA Fetch Error:', err);
window.location.href = url; // Fallback to normal navigation on error
});
}
function executeScripts(container) {
// When elements are injected via innerHTML, their