Admin catalog and orders Tabs - Update

Open to all! Ask other shopowners for help.
Post Reply
azpro
Contributor
Posts: 177
Joined: Fri Nov 06, 2020 8:25 am
Phoenix Version: v1.1.0.6
Has thanked: 30 times
Been thanked: 34 times

Admin catalog and orders Tabs - Update

Post by azpro »

Hi all,

Working and testing on my first Phoenix V 1.0.9.8 release / site something that borders me in admin/catalog.php is when you update a product - it goes back to the overview page. Instead of giving me feed-back message "product is updated" and reload the edit page. By changing the params on update - this can be accomplished - and this is not to hard.

But I want to stay / reload my opened Tab ... I tried some jQuery code to accccomplish opnening LastTab - but I have to little knowledge to succeed.

Does anyone know a solution?

Thanks!
Last edited by azpro on Thu Oct 16, 2025 4:24 pm, edited 1 time in total.


Join The Code Co-op to get access to your library in the Code Co-op Forum
User avatar
burt
Core Team
Posts: 4550
Joined: Tue Oct 29, 2019 9:37 am
Phoenix Version: v1.1.0.8
Has thanked: 252 times
Been thanked: 412 times

Re: Admin catalog

Post by burt »

Set the tab in storage, when you redirect to the page, read the storage and open the relevant tab?

That is, more or less, how the product_attributes.php page works - the relevant show/hide thing is opened and stays open for the session. Look at that hook for pointers;

https://github.com/CE-PhoenixCart/Phoen ... te.php#L19
I am not here to build for you.
I am here to build with you. Let's help each other.
azpro
Contributor
Posts: 177
Joined: Fri Nov 06, 2020 8:25 am
Phoenix Version: v1.1.0.6
Has thanked: 30 times
Been thanked: 34 times

Re: Admin catalog

Post by azpro »

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
Last edited by azpro on Thu Oct 16, 2025 6:05 am, edited 3 times in total.
azpro
Contributor
Posts: 177
Joined: Fri Nov 06, 2020 8:25 am
Phoenix Version: v1.1.0.6
Has thanked: 30 times
Been thanked: 34 times

Re: Admin catalog

Post by azpro »

Summary of what it does:
  • Remembers the last active tab across page reloads and browser sessions (localStorage).
  • Works on all admin pages with Bootstrap tabs (any container ID).
  • Automatically handles dynamically added tabs.
  • Written safely using NOWDOC, so PHP won’t interpret or break the JS.
  • Injected at the end of <body>, ensuring Bootstrap JS is already loaded.
You can now reuse this single file for every page with tabs — no configuration needed.
User avatar
burt
Core Team
Posts: 4550
Joined: Tue Oct 29, 2019 9:37 am
Phoenix Version: v1.1.0.8
Has thanked: 252 times
Been thanked: 412 times

Re: Admin catalog

Post by burt »

You can probably amend the redirecting url with a Hook targetting

listen_updateProductAction

I think you need to manipulate

$GLOBALS['action_redirect']
I am not here to build for you.
I am here to build with you. Let's help each other.


Join The Code Co-op to get access to your library in the Code Co-op Forum
Post Reply