@@ -130,7 +130,40 @@ function refreshDatatablesOnFilterChange(url, tableId = 'crudTable')
130130 // replace the datatables ajax url with new_url and reload it
131131 table .ajax .url (url).load ();
132132 }
133- }
133+ }
134+
135+ // Each filter navbar stores its own filter state in a `data-filter-params` attribute.
136+ // This is the source of truth for consumers (e.g. report scripts) — they read from
137+ // the navbar DOM element, not the browser URL. This supports future scenarios with
138+ // multiple independent filter navbars on the same page.
139+ // When there is only one navbar, the browser URL is also kept in sync as a convenience
140+ // (bookmarkable URLs, shareable links).
141+ document .addEventListener (' backpack:filter:changed' , function (event ) {
142+ if (! event .detail || ! event .detail .shouldUpdateUrl ) return ;
143+
144+ // Find the navbar that owns this filter
145+ var componentId = event .detail .componentId || ' ' ;
146+ var navbar = componentId
147+ ? document .querySelector (' .navbar-filters[data-component-id="' + componentId + ' "]' )
148+ : document .querySelector (' .navbar-filters' );
149+
150+ if (! navbar) return ;
151+
152+ // Update the navbar's stored filter state
153+ var params = new URLSearchParams (navbar .getAttribute (' data-filter-params' ) || ' ' );
154+ if (event .detail .filterValue !== ' ' && event .detail .filterValue != null ) {
155+ params .set (event .detail .filterName , event .detail .filterValue );
156+ } else {
157+ params .delete (event .detail .filterName );
158+ }
159+ navbar .setAttribute (' data-filter-params' , params .toString ());
160+
161+ // Mirror to the browser URL only when there is a single filter navbar
162+ if (document .querySelectorAll (' .navbar-filters' ).length <= 1 ) {
163+ var newUrl = addOrUpdateUriParameter (window .location .href , event .detail .filterName , event .detail .filterValue );
164+ window .history .replaceState ({}, ' ' , newUrl);
165+ }
166+ });
134167
135168 // button to remove all filters
136169 document .addEventListener (' DOMContentLoaded' , function () {
@@ -151,6 +184,19 @@ function refreshDatatablesOnFilterChange(url, tableId = 'crudTable')
151184 return ;
152185 }
153186
187+ // Seed the navbar's data-filter-params from the URL, scoped to this navbar's own filters.
188+ // This lets consumers read filter state from the navbar DOM element from the start,
189+ // and ensures shared URLs with filter params are applied correctly on load.
190+ var urlParams = new URLSearchParams (window .location .search );
191+ var navbarParams = new URLSearchParams ();
192+ filters .forEach (function (filter ) {
193+ var filterName = filter .getAttribute (' filter-name' );
194+ if (urlParams .has (filterName)) {
195+ navbarParams .set (filterName, urlParams .get (filterName));
196+ }
197+ });
198+ navbar .setAttribute (' data-filter-params' , navbarParams .toString ());
199+
154200 // Add event listener only once per navbar to avoid duplication
155201 if (! navbar .hasAttribute (' data-filter-events-bound' )) {
156202 navbar .setAttribute (' data-filter-events-bound' , ' true' );
@@ -249,14 +295,7 @@ function refreshDatatablesOnFilterChange(url, tableId = 'crudTable')
249295 }
250296 }
251297
252- document .dispatchEvent (new CustomEvent (' backpack:filters:cleared' , {
253- detail: {
254- navbar: navbar,
255- filters: filters,
256- tableId: tableId
257- }
258- }));
259-
298+ // 1. Clear each filter's UI state
260299 filters .forEach (function (filter ) {
261300 filter .dispatchEvent (new CustomEvent (' backpack:filter:clear' , {
262301 detail: {
@@ -265,7 +304,27 @@ function refreshDatatablesOnFilterChange(url, tableId = 'crudTable')
265304 }));
266305 });
267306
268- // After clearing filters, re-initialize them to ensure proper state
307+ // 2. Clear the navbar's stored filter state and clean the browser URL
308+ navbar .setAttribute (' data-filter-params' , ' ' );
309+ if (document .querySelectorAll (' .navbar-filters' ).length <= 1 ) {
310+ let cleanUrl = URI (window .location .href ).search (' ' ).toString ();
311+ if (window .crud && typeof window .crud .updateUrl === ' function' ) {
312+ window .crud .updateUrl (cleanUrl);
313+ } else {
314+ window .history .replaceState ({}, ' ' , cleanUrl);
315+ }
316+ }
317+
318+ // 3. Notify consumers that all filters have been cleared
319+ document .dispatchEvent (new CustomEvent (' backpack:filters:cleared' , {
320+ detail: {
321+ navbar: navbar,
322+ filters: filters,
323+ tableId: tableId
324+ }
325+ }));
326+
327+ // 4. Re-initialize filters to ensure proper state
269328 setTimeout (function () {
270329 filters .forEach (function (filter ) {
271330 let initFunction = filter .getAttribute (' filter-init-function' );
@@ -274,16 +333,6 @@ function refreshDatatablesOnFilterChange(url, tableId = 'crudTable')
274333 }
275334 });
276335 }, 50 );
277-
278- // Force update the URL to remove all filter parameters after a short delay
279- // to ensure all filters have processed the clear event
280- setTimeout (function () {
281- let currentUrl = window .location .href ;
282- let cleanUrl = URI (currentUrl).search (' ' ).toString ();
283- if (window .crud && window .crud .updateUrl ) {
284- window .crud .updateUrl (cleanUrl);
285- }
286- }, 100 );
287336 });
288337 }
289338
0 commit comments