Wappler 7.3.9
MacOS
NodeJS
Expected behavior
What do you think should happen?
When using SPA navigation with internal links, navigation elements that have dmx-class:active bindings should consistently apply the active class when their condition evaluates to true. The active state should be reliably reflected after each navigation event.
Actual behavior
What actually happens?
During SPA navigation, the active class is intermittently missing from navigation links even when the dmx-class:active condition is true. Sometimes the navigation link appears inactive despite being on the correct page. A hard page refresh always shows the correct active state.
How to reproduce
Step by step:
-
Create a page with navigation links using both the
internalattribute anddmx-class:activebinding with astartsWith()condition to match a parent route and its sub-routes:<ul class="nav nav-pills flex-column"> <li class="nav-item"> <a class="nav-link" href="/dashboard" internal>Dashboard</a> </li> <li class="nav-item"> <a class="nav-link" href="/blog" internal dmx-class:active="browser1.location.pathname.startsWith('/blog')"> Blog </a> </li> <li class="nav-item"> <a class="nav-link" href="/profile" internal>Profile</a> </li> </ul> -
Create two pages:
/blog- a list or index page showing all blog posts/blog/123- a detail page showing a single blog post (sub-route of/blog)
-
On the
/blogpage, add a link to the detail page:<a href="/blog/123" internal>View blog post</a> -
Navigate to
/blogusing the navigation link - observe theactiveclass is correctly applied -
From
/blog, click the link to navigate to/blog/123 -
Observe that the
activeclass is removed from the/blognavigation link, even though thedmx-class:active="browser1.location.pathname.startsWith('/blog')"condition should still be true (since/blog/123starts with/blog) -
Refresh the page and observe the
activeclass is correctly applied
Why this happens:
The issue occurs because when navigating to /blog/123, the dmxBootstrap5Navigation component's _stateHandler() checks if node.href == window.location.href. Since /blog !== /blog/123, it removes the active class. This conflicts with the custom dmx-class:active binding that uses startsWith() logic to keep parent routes active when viewing sub-routes.
Root cause:
The dmxBootstrap5Navigation component's _stateHandler() method (lines 26-46 in dmxBootstrap5Navigation.js) directly manages the active class:
_stateHandler () {
const node = this.$node;
const active = node.href == window.location.href ||
node.href == window.location.href.split("?")[0].split("#")[0];
node.classList.toggle('active', active);
// ...
}
This creates a race condition with AppConnect's dmx-class:active binding. Both systems try to control the same class during navigation events (popstate, pushstate, replacestate), leading to inconsistent timing and results.
Suggested fix:
Add a check to skip automatic active class management if the element has a dmx-class:active attribute:
_stateHandler () {
const node = this.$node;
const active = node.href == window.location.href ||
node.href == window.location.href.split("?")[0].split("#")[0];
if (!node.hasAttribute('dmx-class:active')) {
node.classList.toggle('active', active);
}
// ...
}
This would allow developers to use custom dmx-class:active logic (like startsWith() for matching sub-routes) without conflicts from the navigation component's automatic active state management, as this is the recommended approach by Patrick: