dmxBootstrap5Navigation.js changes are lost after fragment load

Wappler Version : v4.6.4
Operating System : MacOS
Server Model: NodeJS
Database Type: N/A
Hosting Type: Docker

Expected behavior

dmxBootstrap5Navigation.js correctly appends the class “active”

Actual behavior

dmxBootstrap5Navigation.js correctly appends the class “active”, but the changes are lost as soon as the HTML fragment is loaded on links with Internal routing enabled (SPA-style). This only happens on projects that have a navigation bar (e.g.: sidebar) loaded through a fragment - the top navigation bar works as intended because it’s not loaded again through the fragment

_update() is called before the fragment is loaded (as soon as pushstate), so the addition of class “active” might be lost (overwritten) by the HTML coming from the fragment

How to reproduce

Use your browser’s network tools to bandwidth limit the network connection, so HTTP queries are executed slower. Add console.log("CALLED!") to dmxBootstrap5Navigation.js:

dmxBootstrap5Navigation.js
// Bootstrap 5 Navigation
document.addEventListener('DOMContentLoaded', function(event) {

  window.addEventListener('popstate', _update);
  window.addEventListener('pushstate', _update);

  _update();

  function _update() {
    console.log("CALLED!")
  	var url = window.location.href;

  	document.querySelectorAll('a.nav-link:not([data-bs-toggle]), a.dropdown-item').forEach(function(elem) {
   		elem.classList.toggle('active', elem.href == url || elem.href == url.split("?")[0].split("#")[0]);
  	});

    document.querySelectorAll('a.dropdown-item.active').forEach(function(elem) {
      var theItem = elem.closest('.nav-item.dropdown');
      if (theItem) {
        var theToggle = theItem.querySelector('.dropdown-toggle');
        if (theToggle) {
          theToggle.classList.toggle('active');
        }
      }
  	});
  }
});

Use a NodeJS project with Internal routing links, so page changes are loaded through fragments. Notice the browser’s console and network tab. Once you click the link _update() is called and class “active” is appended, but once the fragment loads those changes are lost.

Suggestion:
_update() needs to be called after fragment load in addition to the current events

1 Like

Would appreciate if we could get this fixed, an event needs to be fired after the HTML fragment is loaded. I'm not familiar with dmxRouting.js to do it myself

@George @patrick I fixed this, push it to production

dmxRouting.js:

    onload: function (t) {
        this.set("loading", !1), 200 == this.xhr.status || 0 == this.xhr.status ? (this.children.splice(0).forEach(function (t) {
            t.$destroy()
        }), this.bindings = [], this.$node.innerHTML = this.xhr.responseText, this.$parse(this.$node), this.evalScripts(this.$node), window.grecaptcha && dmx.array(this.$node.querySelectorAll(".g-recaptcha")).forEach(function (t) {
            grecaptcha.render(t)
        }), this.dispatchEvent("load")) : 222 == this.xhr.status ? location.assign(this.xhr.responseText) : 401 == this.xhr.status ? this.dispatchEvent("unauthorized") : 403 == this.xhr.status ? this.dispatchEvent("forbidden") : 404 == this.xhr.status ? this.dispatchEvent("notfound") : this.dispatchEvent("error")
        window.dispatchEvent(new Event('wappler.afterFragmentLoad'));
    },

dmxBootstrap5Navigation.js:

  window.addEventListener('popstate', _update);
  window.addEventListener('pushstate', _update);
  window.addEventListener('wappler.afterFragmentLoad', _update);

I’ve made several changes to the dmxBootstrap5Navigation.js. The dmx-view component is not the only component that loads html content from the server, also the dmx-route component does this. Also it could be possible that the nav is being generated dynamically (data from serverconnect to build the nav).

So instead of triggering an event for all those different cases I’ve added the Mutation Observer to watch the document for changes in the DOM, when new nodes are added to the document I call the _update. Also moved it outside the DOMContentLoaded listener, when for example the include was in the fragment it would not execute since the DOMContentLoaded event will never trigger then.

Here the updated file: dmxBootstrap5Navigation.zip (717 Bytes)

Would be fine if you could test this solution. Your solution also works fine for the fragment issue you encountered but doesn’t solve all the issues I described.

Uncaught TypeError: records.addedNodes is undefined
console.log(records)

Edit: Fixed:

new MutationObserver(function(records) {
      if (records.length) _update();
    }).observe(document.body, { subtree: true, childList: true });

Seems to work :+1:

I checked the addedNodes because I only wanted to do the _update only when new nodes where added, forgot that the records is also an array. But having it on any change should not have that much impact on performance, so will do the _update on all changes. The records.length is also not needed, it only triggers the callback when there was an actual DOM change and we only listen to childList changes (adding and removing nodes).

Simplified version:

new MutationObserver(_update).observe(document.body, { subtree: true, childList: true });

Seems to work :+1:

By the way, while you’re at it, is there a way we can specify an additional attribute to match URLs in the beginning?

e.g.:

<a href="/dashboard" dmx-match-href="/dashboard" internal>Dashboard</a>

This is to allow having class active while like on the following URL:

/dashboard/change-password

Because in my website I have a sidebar for the user dashboard, but I want the navigation bar link “Dashboard” to be active on any page within the dashboard

As a reminder, you haven’t pushed this fix yet. Your fix is working

Patrick, your fix is working, are you not interested in publishing the fixed file?

For your convenience, this is the fixed file:

// Bootstrap 5 Navigation
document.addEventListener('DOMContentLoaded', function(event) {

  window.addEventListener('popstate', _update);
  window.addEventListener('pushstate', _update);

  // Listen to DOM changes and call update when nodes are added
  new MutationObserver(_update).observe(document.body, { subtree: true, childList: true });

  _update();

  function _update() {
  	var url = window.location.href;

  	document.querySelectorAll('a.nav-link:not([data-bs-toggle]), a.dropdown-item').forEach(function(elem) {
   		elem.classList.toggle('active', elem.href == url || elem.href == url.split("?")[0].split("#")[0]);
  	});

    document.querySelectorAll('a.dropdown-item.active').forEach(function(elem) {
      var theItem = elem.closest('.nav-item.dropdown');
      if (theItem) {
        var theToggle = theItem.querySelector('.dropdown-toggle');
        if (theToggle) {
          theToggle.classList.toggle('active');
        }
      }
  	});
  }
});

Fixed in Wappler 5.0.4

This topic was automatically closed after 32 hours. New replies are no longer allowed.