Hi all,
Me and my friend ChatGPT did some fiddling to create a hook - so we can save eg a product or edit an order without loosing the chosen tab when you click "Save". So on page reload the chosen tab you work on keeps open. It should work for all pages you have tabs (eg catalog.php en orders.php) - cross bwosers and even cross sessions - because it is stored locally on your PC/laptop. Also if you add more tabs (later) it should work.
You can safely put the contents of the code below in a new file named TabsHistoryStorage.php and upload to:
includes/hooks/admin/siteWide/TabsHistoryStorage.php
I can not uppload an attachment (I suppose because of forum safety).
Code: Select all
<?php
class hook_admin_siteWide_TabsHistoryStorage {
public function listen_injectBodyEnd() {
$js = <<<'JS'
<script>
(function () {
// --- Polyfill for CSS.escape (needed for some older browsers) ---
if (typeof window.CSS === 'undefined' || typeof window.CSS.escape !== 'function') {
window.CSS = window.CSS || {};
window.CSS.escape = function (sel) { return String(sel).replace(/[^a-zA-Z0-9_\-]/g, "\\$&"); };
}
const TAB_TRIGGER_SELECTOR = '[data-bs-toggle="tab"]';
const STORAGE_PREFIX = 'activeTab';
const PAGE_KEY = location.pathname; // make storage unique per page
// --- Find the tabset container ---
function getTabsetContainer(el) {
let node = el;
while (node && node !== document.documentElement) {
if (node.querySelector && node.querySelector('.tab-content')) return node;
node = node.parentElement;
}
return document.body;
}
// --- Generate a unique key per tabset (page + id or index) ---
function getStorageKey(container) {
const id = container.id ? container.id : getTabsetIndex(container);
return `${STORAGE_PREFIX}::${PAGE_KEY}::${id}`;
}
function getTabsetIndex(container) {
const allSets = Array.from(document.querySelectorAll('.nav.nav-tabs')).map(getTabsetContainer);
const idx = allSets.findIndex((c) => c === container);
return `idx-${Math.max(idx, 0)}`;
}
// --- Helper functions ---
const getTargetFromTrigger = (trigger) => trigger.getAttribute('data-bs-target') || trigger.getAttribute('href');
const findTrigger = (container, target) =>
container.querySelector(`${TAB_TRIGGER_SELECTOR}[href="${target}"], ${TAB_TRIGGER_SELECTOR}[data-bs-target="${target}"]`);
// --- Restore the saved tab for a given tabset ---
function restoreTabset(container) {
let target = null;
// 1) URL hash (if present and valid)
if (location.hash) {
const id = location.hash.substring(1);
if (container.querySelector(`[id="${CSS.escape(id)}"]`)) target = `#${id}`;
}
// 2) Saved tab in localStorage
if (!target) {
const saved = localStorage.getItem(getStorageKey(container));
if (saved && findTrigger(container, saved)) target = saved;
}
// 3) Fallback to first tab
if (!target) {
const first = container.querySelector(TAB_TRIGGER_SELECTOR);
if (first) target = getTargetFromTrigger(first);
}
// Activate via Bootstrap API
if (target) {
const trigger = findTrigger(container, target);
if (trigger) new bootstrap.Tab(trigger).show();
}
}
// --- Save active tab when user switches ---
document.addEventListener('show.bs.tab', function (e) {
const trigger = e.target;
const container = getTabsetContainer(trigger);
const target = getTargetFromTrigger(trigger);
if (target && container) {
localStorage.setItem(getStorageKey(container), target);
// intentionally NOT changing the URL hash
}
});
// --- Restore all tabsets on page ---
function restoreAllTabsets() {
const seen = new Set();
document.querySelectorAll('.nav.nav-tabs').forEach((nav) => {
const container = getTabsetContainer(nav);
if (container && !seen.has(container)) {
seen.add(container);
restoreTabset(container);
}
});
}
// --- Wait until DOM and other scripts are ready ---
function delayedRestore() { queueMicrotask(() => setTimeout(restoreAllTabsets, 0)); }
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', delayedRestore);
} else {
delayedRestore();
}
// --- Observe dynamically added tabs and restore automatically ---
const mo = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const n of m.addedNodes) {
if (n.nodeType === 1 && (n.matches?.(TAB_TRIGGER_SELECTOR) || n.querySelector?.(TAB_TRIGGER_SELECTOR))) {
const trigger = n.matches?.(TAB_TRIGGER_SELECTOR) ? n : n.querySelector(TAB_TRIGGER_SELECTOR);
const container = trigger ? getTabsetContainer(trigger) : null;
if (container) restoreTabset(container);
}
}
}
});
mo.observe(document.documentElement, { childList: true, subtree: true });
})();
</script>
JS;
return $js;
}
}
In the file you are using to update a page EG eg
admin/includes/actions/catalog/update_product.php
you should fix the return URL by copying and editing the last line to :
Code: Select all
//return $Admin->link('catalog.php', ['cPath' => $cPath, 'pID' => $products_id]);
Code: Select all
return $Admin->link('catalog.php', ['cPath' => $cPath, 'pID' => $products_id ,'action' => 'new_product']);
So now you stay on the same page. There must be a more clever way to change the return URL without touching core code. Maybe
@burt or
@ecartz could give directions.
I tested on V1.1.0.6 - but should work on all versions. As always - backup before testing.
Anyway - I hope this helps!
Arjan