Skip to content

Commit 41584fb

Browse files
pxpmStyleCIBot
andcommitted
"Agnostic" filters - decouple filters from datatables (#5714)
* wip * wip * Apply fixes from StyleCI [ci skip] [skip ci] * wip * Apply fixes from StyleCI [ci skip] [skip ci] * remove filters navbar component --------- Co-authored-by: StyleCI Bot <[email protected]>
1 parent b9c31bb commit 41584fb

File tree

2 files changed

+183
-109
lines changed

2 files changed

+183
-109
lines changed

src/resources/views/crud/inc/datatables_logic.blade.php

+23-2
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,35 @@ functionsToRunOnDataTablesDrawEvent: [],
305305
"<'table-footer row mt-2 d-print-none align-items-center '<'col-sm-12 col-md-4'l><'col-sm-0 col-md-4 text-center'B><'col-sm-12 col-md-4 'p>>",
306306
}
307307
}
308-
</script>
308+
</script>
309309
@include('crud::inc.export_buttons')
310310

311311
<script type="text/javascript">
312+
// TODO: this needs to be agnostic per filter navbar as in the future hopefully we can have more than one
313+
// table in the same page and setup filters for each one.
314+
document.addEventListener('backpack:filters:cleared', function (event) {
315+
// behaviour for ajax table
316+
var new_url = '{{ url($crud->getOperationSetting("datatablesUrl").'/search') }}';
317+
var ajax_table = new DataTable('#crudTable');
318+
319+
// replace the datatables ajax url with new_url and reload it
320+
ajax_table.ajax.url(new_url).load();
321+
322+
// remove filters from URL
323+
crud.updateUrl(new_url);
324+
});
325+
326+
document.addEventListener('backpack:filter:changed', function (event) {
327+
let filterName = event.detail.filterName;
328+
let filterValue = event.detail.filterValue;
329+
let shouldUpdateUrl = event.detail.shouldUpdateUrl;
330+
let debounce = event.detail.debounce;
331+
updateDatatablesOnFilterChange(filterName, filterValue, filterValue || shouldUpdateUrl, debounce);
332+
});
333+
312334
jQuery(document).ready(function($) {
313335
314336
window.crud.table = $("#crudTable").DataTable(window.crud.dataTableConfiguration);
315-
316337
window.crud.updateUrl(location.href);
317338
318339
// move search bar
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
11
<nav class="navbar navbar-expand-lg navbar-filters mb-0 py-0 shadow-none">
2-
{{-- Brand and toggle get grouped for better mobile display --}}
3-
<a class="nav-item d-none d-lg-block my-auto"><span class="la la-filter"></span></a>
4-
<button class="navbar-toggler ms-3"
5-
type="button"
6-
data-toggle="collapse" {{-- for Bootstrap v4 --}}
7-
data-target="#bp-filters-navbar" {{-- for Bootstrap v4 --}}
8-
data-bs-toggle="collapse" {{-- for Bootstrap v5 --}}
9-
data-bs-target="#bp-filters-navbar" {{-- for Bootstrap v5 --}}
10-
aria-controls="bp-filters-navbar"
11-
aria-expanded="false"
12-
aria-label="{{ trans('backpack::crud.toggle_filters') }}">
2+
{{-- Brand and toggle get grouped for better mobile display --}}
3+
<a class="nav-item d-none d-lg-block my-auto"><span class="la la-filter"></span></a>
4+
<button class="navbar-toggler ms-3"
5+
type="button"
6+
data-toggle="collapse" {{-- for Bootstrap v4 --}}
7+
data-target="#bp-filters-navbar" {{-- for Bootstrap v4 --}}
8+
data-bs-toggle="collapse" {{-- for Bootstrap v5 --}}
9+
data-bs-target="#bp-filters-navbar" {{-- for Bootstrap v5 --}}
10+
aria-controls="bp-filters-navbar"
11+
aria-expanded="false"
12+
aria-label="{{ trans('backpack::crud.toggle_filters') }}">
1313
<span class="la la-filter"></span> {{ trans('backpack::crud.filters') }}
14-
</button>
14+
</button>
1515

16-
{{-- Collect the nav links, forms, and other content for toggling --}}
17-
<div class="collapse navbar-collapse" id="bp-filters-navbar">
16+
{{-- Collect the nav links, forms, and other content for toggling --}}
17+
<div class="collapse navbar-collapse" id="bp-filters-navbar">
1818
<ul class="nav navbar-nav">
19-
{{-- THE ACTUAL FILTERS --}}
20-
@foreach ($crud->filters() as $filter)
21-
@includeFirst($filter->getNamespacedViewWithFallbacks())
22-
@endforeach
23-
<li class="nav-item"><a href="#" id="remove_filters_button" class="nav-link {{ count(Request::input()) != 0 ? '' : 'invisible' }}"><i class="la la-eraser"></i> {{ trans('backpack::crud.remove_filters') }}</a></li>
19+
{{-- THE ACTUAL FILTERS --}}
20+
@foreach ($crud->filters() as $filter)
21+
@includeFirst($filter->getNamespacedViewWithFallbacks())
22+
@endforeach
23+
<li class="nav-item"><a href="#" class="nav-link remove_filters_button {{ count(Request::input()) != 0 ? '' : 'invisible' }}"><i class="la la-eraser"></i> {{ trans('backpack::crud.remove_filters') }}</a></li>
2424
</ul>
25-
</div>{{-- /.navbar-collapse --}}
26-
</nav>
27-
28-
@push('crud_list_scripts')
25+
</div>{{-- /.navbar-collapse --}}
26+
</nav>
27+
28+
@push('after_scripts')
2929
@basset('https://unpkg.com/[email protected]/src/URI.min.js')
3030
<script>
31-
function addOrUpdateUriParameter(uri, parameter, value) {
32-
var new_url = normalizeAmpersand(uri);
33-
34-
new_url = URI(new_url).normalizeQuery();
31+
if(typeof addOrUpdateUriParameter !== 'function') {
32+
function addOrUpdateUriParameter(uri, parameter, value) {
33+
let new_url = URI(uri).normalizeQuery();
3534
3635
// this param is only needed in datatables persistent url redirector
3736
// not when applying filters so we remove it.
@@ -40,106 +39,160 @@ function addOrUpdateUriParameter(uri, parameter, value) {
4039
}
4140
4241
if (new_url.hasQuery(parameter)) {
43-
new_url.removeQuery(parameter);
42+
new_url.removeQuery(parameter);
4443
}
4544
4645
if (value !== '' && value != null) {
47-
new_url = new_url.addQuery(parameter, value);
46+
new_url = new_url.addQuery(parameter, value);
4847
}
4948
5049
$('#remove_filters_button').toggleClass('invisible', !new_url.query());
5150
52-
return new_url.toString();
53-
54-
}
55-
56-
function updateDatatablesOnFilterChange(filterName, filterValue, update_url = false, debounce = 500) {
57-
// behaviour for ajax table
58-
var current_url = crud.table.ajax.url();
59-
var new_url = addOrUpdateUriParameter(current_url, filterName, filterValue);
60-
61-
new_url = normalizeAmpersand(new_url);
62-
63-
// add filter to URL
64-
crud.updateUrl(new_url);
65-
crud.table.ajax.url(new_url);
66-
67-
// when we are clearing ALL filters, we would not update the table url here, because this is done PER filter
68-
// and we have a function that will do this update for us after all filters had been cleared.
69-
if(update_url) {
70-
// replace the datatables ajax url with new_url and reload it
71-
callFunctionOnce(function() { refreshDatatablesOnFilterChange(new_url) }, debounce, 'refreshDatatablesOnFilterChange');
51+
return new_url.normalizeQuery().toString();
52+
}
53+
}
54+
55+
if(typeof updatePageUrl !== 'function') {
56+
function updatePageUrl(filterName, filterValue, currentUrl = null) {
57+
currentUrl = currentUrl || window.location.href;
58+
let newUrl = addOrUpdateUriParameter(currentUrl, filterName, filterValue);
59+
crud.updateUrl(newUrl);
60+
return newUrl;
7261
}
62+
}
63+
64+
if(typeof updateDatatablesOnFilterChange !== 'function') {
65+
function updateDatatablesOnFilterChange(filterName, filterValue, update_url = false, debounce = 500) {
66+
// behaviour for ajax tables
67+
let new_url = updatePageUrl(filterName, filterValue, crud.table.ajax.url());
68+
crud.table.ajax.url(new_url);
69+
70+
// when we are clearing ALL filters, we would not update the table url here, because this is done PER filter
71+
// and we have a function that will do this update for us after all filters had been cleared.
72+
if(update_url) {
73+
// replace the datatables ajax url with new_url and reload it
74+
callFunctionOnce(function() { refreshDatatablesOnFilterChange(new_url) }, debounce, 'refreshDatatablesOnFilterChange');
75+
}
7376
74-
return new_url;
75-
}
76-
77-
/**
78-
* calls the function func once within the within time window.
79-
* this is a debounce function which actually calls the func as
80-
* opposed to returning a function that would call func.
81-
*
82-
* @param func the function to call
83-
* @param within the time window in milliseconds, defaults to 300
84-
* @param timerId an optional key, defaults to func
85-
*
86-
* FROM: https://stackoverflow.com/questions/27787768/debounce-function-in-jquery
87-
*/
88-
if(typeof callFunctionOnce !== 'function') {
77+
return new_url;
78+
}
79+
}
80+
81+
/**
82+
* calls the function func once within the within time window.
83+
* this is a debounce function which actually calls the func as
84+
* opposed to returning a function that would call func.
85+
*
86+
* @param func the function to call
87+
* @param within the time window in milliseconds, defaults to 300
88+
* @param timerId an optional key, defaults to func
89+
*
90+
* FROM: https://stackoverflow.com/questions/27787768/debounce-function-in-jquery
91+
*/
92+
if(typeof callFunctionOnce !== 'function') {
8993
function callFunctionOnce(func, within = 300, timerId = null) {
90-
window.callOnceTimers = window.callOnceTimers || {};
91-
timerId = timerId || func;
92-
if (window.callOnceTimers[timerId]) {
93-
clearTimeout(window.callOnceTimers[timerId]);
94-
}
95-
window.callOnceTimers[timerId] = setTimeout(func, within);
94+
window.callOnceTimers = window.callOnceTimers || {};
95+
timerId = timerId || func;
96+
if (window.callOnceTimers[timerId]) {
97+
clearTimeout(window.callOnceTimers[timerId]);
98+
}
99+
window.callOnceTimers[timerId] = setTimeout(func, within);
96100
}
97-
}
98-
99-
function refreshDatatablesOnFilterChange(url)
100-
{
101-
// replace the datatables ajax url with new_url and reload it
102-
crud.table.ajax.url(url).load();
103-
}
101+
}
104102
103+
if(typeof refreshDatatablesOnFilterChange !== 'function') {
104+
function refreshDatatablesOnFilterChange(url)
105+
{
106+
// replace the datatables ajax url with new_url and reload it
107+
crud.table.ajax.url(url).load();
108+
}
109+
}
105110
106-
function normalizeAmpersand(string) {
107-
return string.replace(/&amp;/g, "&").replace(/amp%3B/g, "");
108-
}
111+
// button to remove all filters
112+
document.addEventListener('DOMContentLoaded', function () {
109113
110-
// button to remove all filters
111-
jQuery(document).ready(function($) {
112-
$("#remove_filters_button").click(function(e) {
113-
e.preventDefault();
114+
// find all nav.navbar-filters
115+
let filtersNavbar = document.querySelectorAll('.navbar-filters');
114116
115-
// behaviour for ajax table
116-
var new_url = '{{ url($crud->getOperationSetting("datatablesUrl").'/search') }}';
117-
var ajax_table = $("#crudTable").DataTable();
117+
// if there are no navbars, return
118+
if (!filtersNavbar.length) {
119+
return;
120+
}
118121
119-
// replace the datatables ajax url with new_url and reload it
120-
ajax_table.ajax.url(new_url).load();
122+
// run the init function for each filter
123+
filtersNavbar.forEach(function(navbar) {
124+
let filters = navbar.querySelectorAll('li[filter-init-function]');
121125
122-
// clear all filters
123-
$(".navbar-filters li[filter-name]").trigger('filter:clear');
126+
if(filters.length === 0) {
127+
return;
128+
}
124129
125-
// remove filters from URL
126-
crud.updateUrl(new_url);
127-
});
130+
document.addEventListener('backpack:filter:changed', function(event) {
131+
132+
// check if any of the filters are active
133+
let anyActiveFilters = false;
134+
135+
filters.forEach(function(filter) {
136+
if (filter.classList.contains('active')) {
137+
anyActiveFilters = true;
138+
}
139+
});
140+
141+
if(anyActiveFilters === true) {
142+
navbar.querySelector('.remove_filters_button').classList.remove('invisible');
143+
}else{
144+
navbar.querySelector('.remove_filters_button').classList.add('invisible');
145+
}
146+
});
147+
148+
filters.forEach(function(filter) {
149+
let initFunction = filter.getAttribute('filter-init-function');
150+
if (window[initFunction]) {
151+
window[initFunction](filter, navbar);
152+
}
153+
});
154+
155+
if(filtersNavbar.length === 0) {
156+
return;
157+
}
128158
129-
// hide the Remove filters button when no filter is active
130-
$(".navbar-filters li[filter-name]").on('filter:clear', function() {
131-
var anyActiveFilters = false;
132-
$(".navbar-filters li[filter-name]").each(function () {
133-
if ($(this).hasClass('active')) {
134-
anyActiveFilters = true;
135-
// console.log('ACTIVE FILTER');
159+
let removeFiltersButton = navbar.querySelector('.remove_filters_button');
160+
if (removeFiltersButton) {
161+
removeFiltersButton.addEventListener('click', function(e) {
162+
e.preventDefault();
163+
164+
document.dispatchEvent(new Event('backpack:filters:cleared', {
165+
detail: {
166+
navbar: navbar,
167+
filters: filters,
168+
}
169+
}));
170+
171+
filters.forEach(function(filter) {
172+
filter.dispatchEvent(new CustomEvent('backpack:filter:clear', {
173+
detail: {
174+
clearAllFilters: true,
175+
}
176+
}));
177+
});
178+
});
136179
}
137-
});
138180
139-
if (anyActiveFilters == false) {
140-
$('#remove_filters_button').addClass('invisible');
141-
}
181+
filters.forEach(function(filter) {
182+
filter.addEventListener('backpack:filter:clear', function() {
183+
let anyActiveFilters = false;
184+
filters.forEach(function (filterInstance) {
185+
if (filterInstance.classList.contains('active')) {
186+
anyActiveFilters = true;
187+
}
188+
});
189+
190+
if (anyActiveFilters === false) {
191+
removeFiltersButton?.classList.add('invisible');
192+
}
193+
});
194+
});
142195
});
143-
});
196+
});
144197
</script>
145-
@endpush
198+
@endpush

0 commit comments

Comments
 (0)