Datatables - How to load only a certain amount rows on page-load? - javascript

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' }
],
});
});

Related

Multiple tables with individual search inputs in serverSide mode using DataTables

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?

DataTables and columnDefs Rendering Data

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

set selected page number - kendo grid

I am creating a grid using kendo-telrik. In this suppose I am having 100 data. From this in json I have only 10 data(suppose first page contains 10 data in pagination). And I am showing all pages(10 pages for 100 data) all the time.
When I click on another page then get next 10 data and show it in ui. For this I am using page change function of kendo grid and get next 10 data.Now I want to list that data in grid with page which I selected.
For this I create sample which is as - http://jsfiddle.net/pporwal26/y6KdK/76/
var jsonData = JSON.parse("{\"Report\":\"type1\",\"FileList\":[{\"owner\":\"machine-174\\\\admin\",\"path\":\"C:\\\\workarea\\\\WinTest1lakhfileinKB\\\\WinTest\\\\nGYh\\\\SMv\\\\U1P8FLx\\\\vMbhdo\\\\TgFSW\\\\42Ioulj0w.txt\"},{\"owner\":\"machine-174admin\",\"path\":\"C:\\\\workarea\\\\bada_data\\\\Employee Database - Copy (7) - Copy.mdb\"}],\"Count\":100,\"total\":100,\"page\":1}");
function nextData(page){
jsonData = JSON.parse("{\"Report\":\"type1\",\"FileList\":[{\"owner\":\"machine-170\\\\admin\",\"path\":\"C:\\\\workarea\\\\WinTest1lakhfileinKB\\\\WinTest\\\\nGYh\\\\SMv\\\\U1P8FLx\"},{\"owner\":\"machine-170admin\",\"path\":\"C:\\\\workarea\"}],\"Count\":100,\"total\":100,\"page\":"+page+"}");
createGrid(jsonData);
}
createGrid(jsonData);
function createGrid(jsonData){
$("#grid").kendoGrid({
pageable: true,
scrollable: true,
page: jsonData.page,
pageable: {
pageSize: 2,
refresh: true,
change:function(e){
nextData(e.index);
}
},
dataSource: {
serverPaging: true,
schema: {
data: "FileList",
total: "total"
},
data: jsonData
}
});
}
In this sample when I click on change event than it always show 1 page not the page which I selected. and also want that on each click grid is always create new not add in it. What can I do for that?
Well I know that calling createGrid(jsonData); inside of the nextData function, you're going to create a table for every time you increment, that alone will cause bugs. Have you tried commenting that out?
I've personally never used kendoGrid, so if my answer's no good, I do apologies, but from having had a look online, this seems to work?
Worst case, you do need that change, in which case, can't you target the data inside the table and just over ride it in some other function? A pretty morbid way of doing it would be to delete/destroy the current one in order to make a new one. But it would work I guess?

OnDemandGrid with dstore Tackable and Memory not showing complete data when data is set into it

I have OnDemandGrid with dstore Trackable and Memory store associated with it. Initially, a memory store with empty array is set. When actual data is fetched and set into the grid, grid does not display full data. It always displays 100 rows.
Moreover, if any column is sorted, then it displays entire set of data.
Grid is created in following manner
this.grid = new (declare([Grid, Selection, ColumnHider,ColumnResizer,Keyboard, Selector]))({
// use Infinity so that all data is available in the grid
//className: "dgrid-autoheight",
showHeader: true,
collection: self.memStore,
minRowsPerPage : 100,
bufferRows: 10,
pagingDelay : 15,
farOffRemoval : 2000,
columns: modifiedColumnList,
selectionMode: "extended",
//pagingLinks: true,
//pagingTextBox: true,
//firstLastArrows: true,
//rowsPerPage: 100,
//pageSizeOptions: [100, 150, 250],
adjustLastColumn : true,
loadingMessage: 'Loading data...',
noDataMessage: 'No data.',
allowSelect: function(row){
return true;
},
keepScrollPosition: true
},this.domNode);
Memory store is created in following manner
var TrackableMemoryStore = (declare([Memory, Trackable]));
Data is set in the following manner
self.memStore = new TrackableMemoryStore({ data: gridData, idProperty: "SRVC_ORDER_ID",defaultNewToStart : true });
self.grid.set("collection", self.memStore);
This sounds like an issue identified in dgrid 0.4.1 and 1.0.0, which happens if you call startup on your grid before it is actually displayed.
In most cases, this is something that should be fixed in your own code, since startup should only be called after the grid's element is in document flow. However, Dijit's StackContainer also happens to call startup on all of its children before they are really displayed, but typically gets away with it because it then calls resize when they're actually displayed.
There is a fix for this issue on the master, dev-1.0, and dev-0.4 branches, so if you use the latest commits from one of them, this should be resolved.
(Incidentally, in earlier versions, the grid would simply end up requesting all data from the store at once, which is definitely not a good thing for large or server-based stores.)

Unable to assign a pre-filter on jqGrid during its load time for its state saving

I am using jqGrid and I am required to save its specific state if user has performed some search and the grid shows the records as per search criteria. When user performs search and clicks on edit button to edit the record, I am required to show the grid in last left status of the records shown as per search criteria. Just went around several answers of Oleg, but still unable to get my thing working. My jqGrid code is as under:
$('#grid').jqGrid({
url: GetPath,
datatype: 'json',
mtype: 'POST',
search: true,
postdata: {filters: postfilt, sord: postsord, sidx: postsort },
colNames: // ColumnNamesArray
colModel: //The ColumnModel.
page: postpage,
pager: jQuery('#pager'),
rowNum: 10,
rowList: [10, 20, 30, 40],
viewrecords: true,
sortname: 'DefaultSortColName',
sortorder: 'DESC',
loadonce: true,
gridview: true,
ignoreCase: true,
jsonReader: {
root: 'rows',
page: 'page',
total: 'total',
records: 'records',
repeatitems: false,
userdata: 'userdata',
Id: 'PrimaryKeyId'
},
autowidth: true,
multiselect: false
)};
I am having one common js file inside which the jqGrids code exists. Every time I need to populate a jqGrid, I simply need to call the js file before which I need to set few javascript variables say for example: for specifying url of jqgrid, etc. Notice the variable names postfilt, postsord and postsort. I am simply assigning values to these variables as per my requirement: when my grid is loading for the first time, the filters are blank so as to load the grid with all of the data, and assigning these variables with postdata filter values which I have saved earlier when some filter is being applied by user. The issue I am facing is, my this dynamic filter kind of functionality works fine for the first grid on which I implemented it. But it doesn't works for my other grids on which I am trying to implement it.(Of course the different grid search filters are saved for different grids). I have checked while debugging in Chrome's F12 that my filter variables does shows the proper values which are to be provided for a jqGrids postData filter, but still it loads the jqGrid with all of the data every time and not with the filter when required. I tried several times for calling a function in gridComplete or loadComplete for providing postdata filters after getting loaded with my grid, but still being unsuccessfull on filling my jqGrid with last provided search criteria on its first load. Where am I being wrong in providing the criterias for jqgrid so as to load it in its last left state when it is loaded for the first time ? Any help is appreciated.
Edit: One of my saved postdata search filter for jqGrid is as under:
{"groupOp":"AND","rules":[{"field":"ClientName","op":"cn","data":"client"}]}
The main reason of your problem is the wrong parameter which you use. JavaScript is case sensitive. There are no postdata parameter. So it will be just ignored by jqGrid. There are exist postData parameter instead. So to include the filter and to set the initial sorting in the request to url: GetPath you need do the following:
$('#grid').jqGrid({
...
search: true,
postData: {filters: postfilt },
sortname: postsort,
sortorder: postsord,
...
});
In the same way Id property of jsonReader will be ignored. You should use id instead. Moreover jqGrid merge default properties of jsonReader (see the documentation) with the values included in the grid options. So you don't need to include the default properties like root: 'rows', page: 'page' and other. jsonReader should looks like
jsonReader: {
repeatitems: false,
id: 'PrimaryKeyId'
}

Categories

Resources