Page 1 of 1

Admin catalog and orders Tabs - Update

Posted: Mon Nov 25, 2024 3:53 pm
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!

Re: Admin catalog

Posted: Tue Nov 26, 2024 10:39 am
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

Re: Admin catalog

Posted: Thu Oct 16, 2025 12:02 am
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

Re: Admin catalog

Posted: Thu Oct 16, 2025 12:06 am
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.

Re: Admin catalog

Posted: Thu Oct 16, 2025 10:30 am
by burt
You can probably amend the redirecting url with a Hook targetting

listen_updateProductAction

I think you need to manipulate

$GLOBALS['action_redirect']