I am using datatables 1.10.19 and I would like to filter data based on the contents of a table cell.
I am using the columnDefs option to alter the contents of the returned data.
I am using this php script to retrieve data.
My code is;
$('#example').DataTable({
processing : true,
serverSide : true,
ajax: url": '/server_processing.php',
columnDefs: [{
targets: 5, // row 6 in the html table
"render": function(data, type, row) {
if (row[5] == 0) {
data = 'rejected';
}
return data;
},
}]
});
This successfully displays a table, and rejected in column 6 when 0 is returned from the database. However datatables won't allows me to filter on the word rejected. I get No matching records found, however I can filter on the integer 0.
I thought datatables was supposed to filter what was displayed in the table?
Any advice is appreciated.
You have server-side processing mode enabled (serverSide: true) which means that you have to perform search yourself on the server-side. This could be done manually or by using helper classes/libraries.
For example, for PHP use SSP helper class (ssp.class.php) available in DataTables distribution. For Laravel framework, there is Laravel DataTables.
DataTables plug-in performs the search for you only in client-side processing mode.
Simple, all I had to do was set this;
serverSide: false
Related
Application is using:
DataTables 1.10.18
jquery 3.2.1
PHP back-end
lodash 4.17.4
The application contains a web page which consists of multiple DataTables. Each of these uses serverSide: true (server-side mode) to obtain the data via an ajax endpoint which returns JSON data.
The tables are initialised as follows:
On page load several <table>'s are rendered. I'm using a jquery .each() to initialise the DataTable for each one:
$.each($('table'), function () {
$(this).DataTable({
processing: true,
serverSide: true,
searching: false,
ajax: {
data: {
table_id: $(this).attr('id')
},
url: '/get-data.json',
},
...
});
Each <table> has an ID. This is passed via ajax in the data: attribute. The endpoint /get-data.json returns data based on the table ID. In other words it knows "which table" the data should be obtained for based on this ID.
I want to be able to do searching on tables, but it must be done server-side. For this reason my initialisation code in (1) sets searching: false because this effectively disables the client-side search facility that DataTables provides (which we can't use in this instance as searching must be done server-side).
The problem I'm facing is how to create search inputs for each table, make an ajax call and update the appropriate table. I want the search to work in realtime after >=3 characters have been entered. Critical to this question is that 1 search input is responsible for searching 1 DataTable - it's not a search feature where the input can update "any/every table on the page" which is a commonly described pattern in other questions. 1 input : searching 1 table in this case.
My plan has been as follows - each table referenced in point (2) has an ID. I need to create unique inputs. So if I have tables with ID's #table1, #table2, #table3 I can easily create:
<input type="text" name="table1_search" id="table1_search">
<input type="text" name="table2_search" id="table2_search">
<input type="text" name="table3_search" id="table3_search">
I then detect if any changes have occurred on inputs:
$('input[type="text"]').bind("keyup change input",
function (e) {
// Ignore tab key for keyup event otherwise it'll fire an ajax request that does nothing useful.
if (e.which !== 9) {
processSearch.call(this);
} else {
e.preventDefault();
}
});
var prev_value = {};
function processSearch() {
var obj = $(this),
search_id = obj.attr('id'), // ID of input
search_value = obj.val(); // Value of input
// There's been no change to the field, ignore.
if (prev_value[search_id] === search_value) {
return;
}
prev_value[search_id] = search_value;
/* Wait until at least 3 characters have been entered, or user has cleared the input */
if (search_value.length >= 3 || (!search_value)) {
debouncedDraw({search_id: search_id, search_value: search_value});
}
}
The above code does what I need in terms of waiting for >=3 characters to be entered. I'm then executing a function called debouncedDraw which passes an object containing search_id and search_value. These refer to the input ID and value respectively, e.g. if I type "foo" into #table1_search then the object is:
{search_id: 'table1_search', search_value: 'foo'}
The debouncedDraw function looks like this. This is using lodash to limit the rate at which the function can fire. The point here is to stop it making needless ajax requests based on a question I asked a few years ago here: DataTables - kill ajax requests when a new one has started:
var debouncedDraw = _.debounce(function (opts) {
console.log(opts);
}, 500);
At the moment this will just console.log the object given above.
I'm unsure of the best way to proceed at this point. I need to re-run /get-data.json via ajax and then update the appropriate table.
I could access the request data and split the search_id based on the underscore to work out which table ID the data is for (e.g. table1_search targets #table1). I then need to write this data back to the appropriate table (#table1 in this case).
I can't help but think I'm going about this in a convoluted way and wondered if DataTables itself has any better ways of supporting this? It seems quite a basic requirement (multiple searchable tables in serverSide mode). But I can't find any posts which refer how to do this specifically.
All the "gotchas" I've experienced over the years is encapsulated in the snippet below. This is the basic template I always use when creating a new datatable. You can create as many datatables on a page as you need using this pattern.
Personally I would use a different ajax url path/route for each table so that table logic is in separate files in the backend... but it is possible to have all the data logic in a single backend file. I modified my usual template to suit that.
<script> //I usually put the script section in the head tag
var table_1; //declare your table var here and initialize as a datatable inside document ready below.
$(document).ready(function() {
table_1 = $('#table_1').DataTable( {
dom: "Bfrtip",
ajax: {
url: "/get-data.json?table=table_1", //add query string var for backend routing
type: "POST" //use POST to not have to deal with url encoding various characters
},
serverSide: true,
searchDelay: 2000, // use this instead of custom debounce
processing: true, // optional visual indicator that a search has been sent to backend
lengthMenu: [ 10, 25, 50, 75, 100 ], // define per page limits. first value will be the default
buttons: [
"pageLength" // per page drop down button. i usually override/extend the default button
],
columns: [ // column definitions of json data fields
{ data: "col_1", title: "ID", width: "1%" }, // width: 1% makes col width as small as possible
{ data: "col_2", title: "Label 2", visible:false }, //visible: false allows you access to field data without displaying to user
{ data: "col_3", title: "Label 3", render: function ( data, type, row ) { //render allows combining of fields into single column
return data + ' <small>('+row.col_2+')</small>'; // data will be col_3 value. row.col_2 is how you reference col_2 value
} },
{ data: "col_4", title: "Label 4", searchable:false }, //searchable: false set this field to not be used in search
],
rowId: 'col_1' //sets the tr row id to the value in this column. useful for DOM and other manipulation later
} );
}
</script>
<table id="table_1" class="table table-striped table-bordered table-sm" style="width:100%"></table>
<!-- If you define title attributes in col definitions above you don't need to create html table headers/footers. Just an empty table tag will do. -->
With this pattern you can utilize the built-in search input that comes with datatables for your use case with server-side processing on all tables.
There's a method behind my madness which I tried to document in the script comments on each line. Let me know if you have a question on something. I'm thinking this is bounty worthy.
For reference, when developing a new app using datatables I basically live on this page https://datatables.net/reference/option/
Edit 1
Inside your existing debounced drawTable function you could do something like this:
function drawTable(id) {
$('#'+id).DataTable().ajax.url( 'get-data.json?table_id='+id+'&foo=bar' ); //update ajax url of existing dt - if necessary
$('#'+id).DataTable().search(search_input_val).draw(); // fire ajax request with value from your custom search input
}
I'm fairly certain you will need to set "searching" to true though for this method to work.
Edit 2
Another way I just thought of, without using dt search. Pass all your data through modified url and load/reload.
$('#'+id).DataTable().ajax.url( 'get-data.json?table_id='+id+'&search=foo' ).load();
You could then get rid of all the debounce stuff if you use a button click listener or an onblur listener on the input field and fire the same command above.
Have you seen this? https://datatables.net/reference/api/%24.fn.dataTable.util.throttle()
I've never used it before, but it looks like a debounce. The example on the page shows it being used for .search()
I've implemented the following but would prefer a better solution as I don't think this is efficient and it definitely isn't elegant!
Taking the code from the question I modified the debounce function as follows:
var debouncedDraw = _.debounce(function (opts) {
// Destroy the existing DataTable.
$('#' + opts.search_region).DataTable().destroy();
// Re-run the drawTable method to get the new DataTable with the search results
drawTable(opts.search_region);
}, 500);
I introduced a function called drawTable which takes the ID of a <table> and runs the DataTables initialisation code. The ajax object was also modified to take into account anything entered into the search keywords input for the given table ID:
function drawTable(id) {
$id = $('#'+id); // Convert string ID to jquery identifier
$id.DataTable({
// DataTable initialisation code as per question
ajax: {
data: {
table_id: id,
keywords: $('input[name="keywords_' + id + '"]').val() // search keywords for table_id
},
url: '/get-data.json',
},
// ... code as per question
});
}
The $.each() was modified so that it detects the ID of each <table> on page load and calls drawTable:
$.each($('table'), function () {
drawTable($(this).attr('id'));
});
This "works" in that it creates each of the required DataTable's on page load, and also handles the search. The search input names were modified to the format: keywords_ plus the ID of the table, e.g. keywords_table1.
I don't think this is efficient because I'm having to call destroy on my DataTable. As per the docs:
This has a very significant performance hit on the page, since a lot of calculations and DOM manipulation is involved, so if you can avoid this, and use the API, that is very strongly encouraged!
However the reason I'm doing this is also as given in the same docs:
DataTables does not allow initialisation options to be altered at any time other than at initialisation time. Any manipulation of the table after initialisation must be done through the API
Well, I'm not using the client-side search feature as I'm having to do searching server-side. So I'm unsure whether manipulating the table via the API would actually help in this instance anyway.
Are there better ways of achieving this?
I'm using jQuery and tabulator to create a fairly simple webpage showing the progress of various live events. I have three table elements on the page showing events in different states. Broadly speaking I have a table for events that are 'Live', 'Finished' and 'Upcoming'.
The tabulator code for each table looks a bit like this:
var eventTable = {
layout:"fitColumns",
columns:[
{title:"Event ID", field:"eventID"},
{title:"Event name", field:"eventName"},
{title:"Event status", field:"eventStatus"}
]
};
I can then generate and populate the tables using data retrieved from the server using an AJAX call:
$('#live-events-table').tabulator(eventTable);
$('#live-events-table').tabulator("setData", "/cgi-bin/getevents.py");
$('#live-events-table').tabulator("setFilter", "status", "in", ["intermission", "mid-event"]);
Then similar code for the finished and upcoming tables.
This creates three requests for getevents.py every time the page is updated. This is undesirable because the data is pulled from another API and I want to avoid sending multiple identical requests. Also, there's a (small) chance the data could change between requests.
Is there a way of using the same data to populate all three tables?
Learnt something today. Never heard of Tabulator. I've been using DataTables for this sort of advanced tabular layout in the form of a jQuery plugin. Tabulator looks promising. Will have to explore more in the future.
In the docs I see Tabulator - Set Table Data, lots of alternative options available to set the data.
Hope I am understanding the problem correctly.
My thoughts/flow for tacking this problem would be:
Assuming your doing this already: Prep server side (getevents.py) needs to return all 'Live', 'Finished' and 'Upcoming' rows in JSON together in 1 response. How do I make a JSON object with multiple arrays?.
Client side on doc ready(), makes a jQuery ajax call to fetch this data before 3 x tabulators are built/called.
You can use a loading indicator. So the client sees something is happening in the 3 x before replaced by jQuery tabulator once the data is returned and you built with tabulator.
In the ajax success callback function you can iterate over the returned json object (containing the 3 x eventTypes). You might have to JSON.parse() if you store it in a JavaScript variable to iterate over.
Now get your specific array of event types nested objects arrays/json.
Then call your tabulator creation methods using the setData pointing to each specific JavaScript array containing the relevant data.
Not sure if this falls under "premature optimization", but I would proceed as the problem itself is rather fun to solve, but you know the requirements/needs better than I.
Good luck.
I fixed this fairly simply, using suggestions from #RoryGS.
Define the table options in a variable, as before:
var eventTable = {
layout:"fitColumns",
columns:[
{title:"Event ID", field:"eventID"},
{title:"Event name", field:"eventName"},
{title:"Event status", field:"eventStatus"}
]
};
Then make a jQuery ajax call to fetch the data, and build the table(s) in the success option of the function:
$(function() {
$.ajax({
dataType: "json",
url: "/cgi-bin/getevents.py",
success: function(data, status){
$('#live-events-table').tabulator(eventTable);
$('#finished-events-table').tabulator(eventTable);
$('#live-events-table').tabulator("setData", data);
$('#live-events-table').tabulator("setFilter", "status", "in", ["intermission", "mid-event"]);
$('#finished-events-table').tabulator("setData", data);
$('#finished-events-table').tabulator("setFilter", "status", "in", ["post-event"]);
}
})})
It seems to be necessary to set the data individually for each table. I would like to specify the data in the constructor, but this doesn't seem to work. I will continue to try to refine this.
If you are getting the data from a URL you can pass it into the ajaxURL property in the table constructor and tabulator will make the request for you:
$("#example-table").tabulator({
ajaxURL:"/cgi-bin/getevents.pyw", //ajax URL
columns:[...] //define columns etc
});
I am using datatable with server side processing.
Here is my data
name type cost
abc a $5
bac d $10
I want first field clickable to mydomain.com/?abc
second field to mydomain.com?bac
but data is populating with mysql so don't know how to do that
Use columns.render option to generate content for a cell.
For example:
var table = $('#example').DataTable({
columnDefs: [
{
targets: 0,
render: function(data, type, full, meta){
if(type === 'display'){
data = '' + data + '';
}
return data;
}
}
]
});
See this example for code and demonstration.
You could try building a calculated column and adding anchor to it. Below example from this reference page and this thread on calculated columns might give you an idea how to do it.
$('#example').dataTable( {
"columnDefs": [ {
"targets": 0,
"data": "download_link",
"render": function ( data, type, row, meta ) {
return 'Download';
}
} ]
} );
If you want to capture a click event and handle it in a custom way it's a little more complicated. When I needed to hack something like this together a while back I ended up having to use calculated columns to add HTML classes to cells I wanted to make interactive, wait for DataTable to be filled (listen to on load complete or on init complete, not sure now), query DOM for elements with HTML classes I used and attaching my event handlers in this manner. Main reason for this being when document initializes and attaches handlers to DOM elements, table rows are not there yet, so event's wouldn't get automagically attached.
I have a fiddle that make an ajax to URL and rendering a table, but I want to defer and load only 10 rows during page load.
HTML
<table id="example" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Account ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
</table>
JS
$(document).ready(function() {
$('#example').DataTable( {
"bPaginate": true,
"processing": true,
"bServerSide": true,
ajax: {
url: 'https://api.myjson.com/bins/1egsx',
dataSrc: 'data'
},
columns: [
{ data: 'account_id' },
{ data: 'name' },
{ data: 'email' }
],
"deferRender": true,
"deferLoading": 10,
} );
});
I kept getting
No matching records found
TL;DR: You should probably be either using deferRender with client-side processing OR server-side processing without deferRender (by fixing your JSON data). This answer assumes you want server-side processing.
deferLoading
When correctly using server-side processing, the default behavior is to only send the number of rows on one page per ajax request. You shouldn't need to be using deferLoading - here's what that does (from documentation here):
When using server-side processing, the default mode of operation for DataTables is to simply throw away any data that currently exists in the table and make a request to the server to get the first page of data to display. This is fine for an empty table, but if you already have the first page of data displayed in the plain HTML, it is a waste of resources. As such, this option exists to allow you to instruct DataTables to not make that initial request, rather it will use the data already on the page (no sorting etc will be applied to it).
Since all your data comes from ajax, that option shouldn't be selected.
deferRender
You really shouldn't need to use deferRender either, if you're correctly using serverside processing. deferRender will (from it's documentation here):
As an example to help illustrate this, if you load a data set with 10,000 rows, but a paging display length of only 10 records, rather than create all 10,000 rows, when deferred rendering is enabled, DataTables will create only 10.
Note the important phrase here:
if you load a data set with 10,000 rows
If you're using serverside processing correctly, you should only be loading the number of rows per page in a single load. deferRender is really an option to speed up datatables when using clientside processing. Serverside processing already handles what deferRender does. See this picture from the DataTables FAQ for how to speed up datatables:
Note that it emphasizes deferRender for clientside only. A note to make here is that if you don't have a LOT of rows (tens of thousands +) you probably don't need to use serverside processing.
Using Serverside Processing Correctly:
Your problem probably comes from the fact that your API isn't returning the proper form of JSON for server-side processing; you need to send more information than just the data to be displayed. Here is the documentation page with the full description (you should definitely read it), but I'll try to outline the basics below.
Request
The request sent to your API is going to contain some data that you need to address.
draw is a unique identifier that tracks sets of request-response pairs; the value in the response needs to match the value in the request. This is how datatables matches requests to responses.
start represents the record that should be the first in the response; if we are showing 10 records per page and we're on page 2, start will be equal to 10, so that in the response we send only records numbered 10-19.
length represents the expected number of rows for this draw, so in the above example of 10 records per page on page 2, length will be equal to 10. This is how many records you should return. This value will be based on the lengthMenu or pageLength options to the Datatables initialization. (Documented here and here, respectively)
An important thing to remember that is often forgotten is to ONLY send as many rows as length; don't send all your rows in the first request.
Response
Your response will need to be altered as well.
Basically, instead of just returning data, you need to return an object that looks like the below example (from that documentation page):
{
"draw": 1,
"recordsTotal": 57,
"recordsFiltered": 57,
"data": [
[
"Angelica",
"Ramos",
"System Architect",
"London",
"9th Oct 09",
"$2,875"
],
[
"Ashton",
"Cox",
"Technical Author",
"San Francisco",
"12th Jan 09",
"$4,800"
],
...
]
}
Note the additional data: draw, recordsTotal, and recordsFiltered in addition to the usual data. These are required options when using serverside processing.
draw represents the number corresponding to the request made to your API; the GET request will also have a draw value, and the value in the GET must match the value in the response.
recordsTotal represents the total number of records in the table, across all pages, where recordsFiltered represents the number of records that meet the filtering requirements set by the GET request (if there is no filtering, it should be equal to recordsTotal.
These are the minimum required elements to your reply, but you should look into the linked documentation for more information on optional values.
Side Note
As a side note; bServerSide should be serverSide and bPaginate should be paging if you're using DataTables version 1.10 or later.
You can also do it with iDisplayLength:
$('#example').DataTable( {
"aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],
"iDisplayLength": 10,
"bPaginate": true,
"processing": true,
"bServerSide": true,
ajax: {
url: 'https://api.myjson.com/bins/1egsx',
dataSrc: 'data'
},
columns: [
{ data: 'account_id' },
{ data: 'name' },
{ data: 'email' }
],
} );
I assume your intention here is to display the DataTable as soon as the page loads. Described below are two ways to load partial data depending on your requirements. The entire data can be loaded at a later point in time by triggering an event like click of a button. In both cases, the entire data will be downloaded as well and stored locally which can then be loaded based on the triggered event so as to avoid making another call to fetch the entire data.
Case 1: Only want to download partial data from the source
The entire data will be fetched from 'https://api.myjson.com/bins/1egsx' before the DataTable will process the data. If you want to only fetch partial data from the URL, you can create your custom function that will parse the data in AJAX interactive mode (readyState=3), stop after receiving the first 10 entries and then massage the data and pass that as the input data for the DataTable. Here's a summary of what you need to do
var inputData = '';
function loadDataTablePreview() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 3 && this.status == 200) {
inputData += this.responseText;
// Count the number of closing '}'. If its 10, lets start processing our data
// Get the index of 10 closing '}'
// Create a substring of inputData
// append ']}'
// Now JSON Decode
var decodedData = JSON.parse(inputData);
// Now create an instance of the DataTable passing this data as the input data
$('#example').DataTable( {
"bPaginate": true,
"processing": true,
"bServerSide": true,
data: decodedData,
columns: [
{ data: 'account_id' },
{ data: 'name' },
{ data: 'email' }
],
});
}
else if (this.readyState == 4 && this.status == 200) {
// JSON Decode the data and store it to load later
}
};
xhttp.open("GET", "https://api.myjson.com/bins/1egsx", true);
xhttp.send();
}
$(document).ready(function() {
loadDataTablePreview();
}
Case 2: Only load the first 10 entries after downloading entire data
Assuming you are ok with having the entire data downloaded, before displaying the DataTable, you can create a function for your dataSrc as seen below. This will display return only the 10 entries in the table. You can optionally store the entire JSON in this function in the browser data store (like sessionStorage) and then trigger a JS function to load the entire JSON at a later point in time.
$(document).ready(function() {
$('#example').DataTable( {
"bPaginate": true,
"processing": true,
"bServerSide": true,
ajax: {
url: 'https://api.myjson.com/bins/1egsx',
dataSrc: function ( json ) {
// Store entire data in sessionStorage
// return the first 10 entries only for now
return json.data.slice(0,10);
},
},
columns: [
{ data: 'account_id' },
{ data: 'name' },
{ data: 'email' }
],
});
});
I'm using Datatables grid to show data from different datasources dynamically at server-side through AJAX.
These datasources have different column names and quantities.
Whats the better way to change datatables configuration on the fly.
Actually I am doing what you've suggested now to populate the table header dynamically which are returned from server response prior to initialise the data table using datatables grid.
The possible pseudo-code can be:
function initDataTable() {
var theadSetup = loadColumnSetup(); // load thead setup from server
// construct thead which are required to initialise a datatable
constructTableHead(theadSetup); // manipulate TH elements here
$('.dataTable').dataTable( {
aaData: dataSrc, // may also be retrieved from server
// other settings...
} );
}