I'm working with an application that uses DataTables to generate a HTML table which is populated by data from an ajax request.
This is fairly simple:
var substancesTable = $('#substancesTable').DataTable({
"processing": true,
"serverSide": true,
"searching": false,
"ajax": {
"url": "/get-substances.json",
"method": "POST",
"cache": false,
"dataSrc": function (json) {
// Update non-Datatables UI elements and perform other functions based on the ajax response
$('#numSubstances').html(json.recordsTotal);
drawOptionsButton(json.isFiltering);
// Must return data for DataTables to work
return json.data;
}
},
// ...
});
There is a callback which DataTables provides, called rowCallback
(https://datatables.net/reference/option/rowCallback) which allows post processing of table rows after the table has been drawn. The key thing here is that it's after the ajax request to /get-substances.json
; the table must be populated with data because this callback is used to manipulate data within it at that point.
Within rowCallback
I'm providing an array of row ID's in my table - that is to say ID's which correspond to <tr>
elements inside #substancesTable
- and I go on to expand these rows. I can do this manually by hardcoding in an array of row ID's, e.g.
var substancesTable = $('#substancesTable').DataTable({
// ...
"rowCallback": function(row) {
var id = $(row).find('td:first').text();
var index = $.inArray(id, ['4', '7']); // hardcoded array
if (index !== -1) {
var tr = $(row).closest('tr');
var row = substancesTable.row( tr );
row.child.show();
tr.addClass('active');
}
});
The array I've hardcoded means rows 4 and 7 are expanded after the table has been populated, which is equivalent to the user clicking on them.
The problem I have is that I don't want to hardcode the array. The application stores the equivalent of var index
in Redis (cache) meaning that we can grab the data easily even if the user leaves the page. So I have added a second ajax request (outside the var substancesTable...
block) to obtain the Redis data. This makes an ajax request to populate an array, activeRows
:
var activeRows = [];
$.ajax({
url: '/view-substance/get-active-rows',
method: 'post',
cache: false,
}).done(function(data) {
activeRows = data;
console.log(activeRows);
});
I understand that the nature of ajax means my code is asynchronous. In some cases the ajax request shown above will complete before the DataTable is drawn, so I get the console.log(activeRows) appearing before the table is rendered, and in other cases it happens afterwards.
What is the correct way to make this second ajax request such that the values from it can be used in place of the hardcoded array? I appreciate I will need to convert the response to an array (since it's still JSON in the console.log
statement). But my question is focused on where to put this code such that it can be used reliably inside rowCallback
?
I have read How do I return the response from an asynchronous call? and understand about the async nature. I can't work out how to structure this to be used in a callback that's already part of an ajax request.
The application uses DataTables version 1.10.16 and jquery 3.2.1
You can actually use the ajax
option to:
Make the first AJAX request which retrieves the active rows.
Once the active rows are retrieved, make the second AJAX request which retrieves the table data.
Example: (see full code and demo here)
var activeRows = [];
function getActiveRows() {
return $.ajax({
url: '/view-substance/get-active-rows',
type: 'POST',
dataType: 'json'
...
}).done(function(data){
activeRows = data;
console.log(activeRows);
});
}
function getTableData(data, callback) {
return $.ajax({
url: '/get-substances.json',
type: 'POST',
dataType: 'json',
'data': data // must send the `data`, but can be extended using $.extend()
...
}).done(callback); // and call callback() once we've retrieved the table data
}
$('#example').dataTable({
ajax: function(data, callback){
getActiveRows().always(function(){
getTableData(data, callback);
});
},
rowCallback: function(row, data){
...
}
});
In the above example, I separated the AJAX calls into two different functions mainly to avoid long indentation in the ajax
option when you call the $('#example').dataTable()
. The code would otherwise look like:
var activeRows = [];
$('#example').dataTable({
ajax: function(data, callback){
// 1. Retrieve the active rows.
$.ajax({
url: '/view-substance/get-active-rows',
type: 'POST',
dataType: 'json'
...
}).done(function(res){
activeRows = res;
console.log(activeRows);
}).always(function(){
// 2. Retrieve the table data.
$.ajax({
url: '/get-substances.json',
type: 'POST',
dataType: 'json',
'data': data // must send the `data`, but can be extended using $.extend()
...
}).done(callback); // and call callback() once we've retrieved the table data
});
},
rowCallback: function(row, data){
...
}
});
I used the .always()
so that the table data would still be retrieved in case of failures in retrieving the active rows.
You could solve your problem with Promises. Promises are objects that help you manage and coordinate asynchronous tasks. Your case would look something like this:
var activeRows = [];
var substancesTable = $('#substancesTable').DataTable({
// ...
});
var pDataTable = new Promise(function(resolve, reject){
// you wan't to resolve just once,
// after the table has finished processing the received data.
// (You may want to change draw to an event that's more suitable)
substancesTable.one('draw', resolve);
});
var pOpenRows = new Promise(function( resolve, reject ){
$.ajax({
url: '/view-substance/get-active-rows',
method: 'post',
cache: false,
}).done(function(data) {
// you can either save your rows globaly or give them to the resolve function
// you don't have to do both
activeRows = data;
resolve( data );
});
});
// Here we basically create a third promise, which resolves (or rejects)
// automatically based on the promises in the array.
Promise.all([pDataTable, pOpenRows])
.then(function( values ){
// expand your table rows here
// Note: if you gave your rows to the resolve function you can access
// them here in values[1] (pDataTable's data would be in values[0])
});
In case you want to learn more about Promises:
For details on browser support you may look here:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With