Range Filter based on data-* attributes
Range Filter based on data-* attributes
I would like to add a range filter at the bottom of a column, except that instead of filtering on the column exact content, I would like to filter on data- attributes.
The reason is that the cell value is textual (eg 'New Kingdom', 'Old Kingdom', 'First Intermediate Period'), but it corresponds to a numerical range of dates.
I manage to add two search boxes, with the same model as the simple text search by column (https://datatables.net/examples/api/multi_filter.html), but I struggle to find how to access the attributes. Is this possible?
Bonus question, if it is possible: what is the best way to deal with unknown values? Empty data attributes?
Simplified example to illustrate the problem:
<table id="example" class="display">
<thead>
<tr>
<th>Identifier</th>
<th>Title</th>
<th>Material</th>
<th>Period</th>
<th>FindPlace</th>
</tr>
</thead>
<tbody>
<tr>
<td>item1</td>
<td>My first item</td>
<td>papyrus</td>
<td data-from="-1279" data-to="-1213">New Kingdom</td>
<td>Hermopolis</td>
</tr>
<tr>
<td>item2</td>
<td>My second item</td>
<td>pottery</td>
<td data-from="-332" data-to="565">Graeco-Roman Period</td>
<td>Diospolis Magna</td>
</tr>
<tr>
<td>item3</td>
<td>My third item</td>
<td>stone</td>
<td data-from="" data-to="">Unknown</td>
<td>Unknown</td>
</tr>
</tbody>
</table>
And the associated javascript:
let table = new DataTable('#example', {
searchHighlight: true,
columnDefs: [
{ className: 'select-filter', targets: [2] }, // add select filter material
{ className: 'range-filter', targets: [3]}, // add from/to range filter for dates
{ className: 'search-filter', targets: [4] } // add search filter for findplace
]
initComplete: function () {
// add filter boxes for filtering individual columns (eg material), this works well
this.api()
.columns('.select-filter')
.every(function () {
let column = this;
// name of the filter is the column header + 'filter'
let name = column.header().innerText+' filter:';
// identifier is 'select-' + the column index
let identifier = 'select-'+column.index();
// Create select element
let select = document.createElement('select');
select.setAttribute('id', identifier);
select.add(new Option(name,''));
column.footer().replaceChildren(select);
// Apply listener for user change in value
select.addEventListener('change', function () {
column
.search(select.value, {exact: true})
.draw();
});
// Add list of options
column
.data()
.unique()
.sort()
.each(function (d, j) {
select.add(new Option(d));
});
});
// add search box for individual column (e.g. findplace), this works well
this.api()
.columns('.search-filter')
.every(function () {
let column = this;
let title = column.footer().textContent;
// Create input element
let input = document.createElement('input');
input.placeholder = title;
column.footer().replaceChildren(input);
// Event listener for user input
input.addEventListener('keyup', () => {
if (column.search() !== this.value) {
column.search(input.value).draw();
}
});
});
// add range filter for datation
this.api()
.columns('.range-filter')
.every(function () {
let column = this;
// Create two input elements
let inputFrom = document.createElement('input');
let inputTo = document.createElement('input');
inputFrom.placeholder = 'from';
inputTo.placeholder = 'to';
column.footer().replaceChildren(inputFrom, inputTo);
// Event listener for user input
inputFrom.addEventListener('keyup', () => {
// This is where I block - how to do the search on the attributes?
if (column.search() < this.value) {
column.search(inputFrom.value).draw();
}
});
inputTo.addEventListener('keyup', () => {
// This is where I block - how to do the search on the attributes?
if (column.search() > this.value) {
column.search(inputTo.value).draw();
}
});
});
}
});
This question has an accepted answers - jump to answer
Answers
Interesting one! What you'll need to do for this is use a function for the search term when you pass it to
column().search(). That function will be passes the cell's row and column data index so you can do:I'd suggest doing
let inputFromValue = inputFrom.value;(and for theto) in the event listener, to cache those values.As you mention, you will need logic checks for what to do is one of the two inputs isn't present. In such a case I'd just treat it as a single filter.
It should be noted that this isn't going to be the most efficient since the data is being read from the DOM. Having the to/from values in the row's data object (i.e. a JSON object loaded from Ajax) would give better performance.
Allan
Thanks @allan for the tip! Unfortunately my grasp of javascript and DataTables API is still a bit shaky, so I do not really understand how to put your advice in practice... (I have been mainly copy-pasting from examples).
It is not clear to me how to use the function to pass the search term to
column.search(), or where the javascript code that you provided should go with respect to the event listeners.I'm going to look into Ajax/JSON after I manage to make this first version work!
I'm updating the relevant javascript with what I know is wrong code but should illustrate even better what I want to do
Something like this:
https://live.datatables.net/jamidedo/1/edit
I'm not certain my logic check is exactly right for this use case, and I've left the other ones for you to do
.
Allan
Thanks @allan, that's great
at least your example is working, even if we wtill need to convert strings to numbers!
I still need to check the logic for the full data, I do not really get everything (for instance some items do not come back when I delete the search input...)
I have one last question about this:
When I separate the search in two different functions (from/to), the first filters the table and the second starts over from the whole dataset instead of the already filtered data.
Is there a way to work from the already filtered data, instead of doing all the logic checks in a single function? I thought it would improve the speed since it can do at least one of the from/to search on a smaller dataset, but I did not manage to make it work.
The search is cumulative for each draw, but each draw will start the search from the full set. It used to be that I tried to have a cumulative filter from draw to draw, but it was getting harder and harder to know when to invalidate it and with the introduction of functions, it became basically impossible.
So what should happen on a draw is that the first filter will see the full data set, the second filter will see the result from the first, the third filter will see the result from the second, etc, etc.
Allan