ColumnControl: search input value restored from stateSave is not sent with the first server-side req
ColumnControl: search input value restored from stateSave is not sent with the first server-side req
Versions: DataTables 3.1.2, ColumnControl 2.1.2 (also 3.0.4 / 2.0.2). No jQuery.
Description
With serverSide: true and stateSave: true, a ColumnControl search input restored from saved state shows its value in the header, but the first Ajax request doesn't include it. columns[i].columnControl is missing from that request, so the table loads unfiltered while the header shows a filter. Nothing redraws afterwards, so the table stays unfiltered until the user edits a search.
searchList is not affected: its restored selection is in the first request.
Reproduction (an ajax function stands in for the server; it logs what each request carries):
const blank = { search: '', smart: true, regex: false, caseInsensitive: true };
new DataTable('#example', {
serverSide: true,
stateSave: true,
stateLoadCallback: () => ({
time: Date.now(), start: 0, length: 10, order: [], search: blank,
columns: [{ visible: true, search: blank }],
columnControl: { name: { searchInput: { logic: 'contains', type: 'text', value: 'abc' } } },
}),
stateSaveCallback: () => {},
columns: [{ name: 'name', data: 'name' }],
columnControl: [
{ target: 0, content: ['order'] },
{ target: 1, content: ['search'] },
],
ajax: (data, callback) => {
console.log('columnControl sent:', JSON.stringify(data.columns[0].columnControl));
callback({ draw: data.draw, recordsTotal: 0, recordsFiltered: 0, data: [] });
},
});
Expected: the first request has columns[0].columnControl.search = { value: 'abc', logic: 'contains', type: 'text' }.
Actual: the first request has no columnControl for the column (undefined), and the header input shows abc. Typing in the input afterwards sends columnControl.search as expected.
What I think is happening
The search content's constructor registers its preXhr listener (e.page.info().serverSide && e.on('preXhr.DT', …)), and it also calls _stateLoad(state.loaded()) to restore the input. In my testing these contents are built after initComplete, which is after the first request has gone out. The restore happens with _loadingState set, so no draw follows either. The value is therefore restored into the UI, but it never reaches a request until the user changes it.
A plain api.draw() inside initComplete doesn't help: at that point the listener isn't registered yet, so the redraw is unfiltered too. Deferring the redraw by a tick works.
Workaround we're using
initComplete(settings) {
const api = new DataTable.Api(settings);
const restored = Object.values(api.state.loaded()?.columnControl ?? {})
.some((c) => c?.searchInput && (c.searchInput.value !== ''
|| c.searchInput.logic === 'empty' || c.searchInput.logic === 'notEmpty'));
if (restored) {
setTimeout(() => api.draw(false));
}
}
It works, but it costs a second request on load. It would be better if the first request carried the restored search. Either way, I'd expect a restored search to trigger a draw once the contents are ready.
Answers
Thank you for the detailed bug report! I'll look into it in detail tomorrow and get back to you then.
Allan
This sounds like a similar issue to one I reported here
Hi,
It looks me a little while to realise what the trigger for this was, as I couldn't replicate it locally initially. The issue is in the
-content searchtype whencolumn.nameis specified. If-content searchTextwere used, it would work, as would-content searchwithoutcolumn.name!The combination of the two results in the error you are seeing - apologies for the bug.
Your test case (very neat - nice one!) shows it working with the nightly build now in this little example.
Thank you for the detailed bug report.
Allan