Architecting Datatables usage in a Spring/Ajax system
Architecting Datatables usage in a Spring/Ajax system
Hi everyone. While I'm an experienced programmer, I'm a newcomer to web development and came across DataTables and almost got them working. I have no problem displaying them with static data and managed to find an example online with Spring Controllers returning static text, with some more complicated out of control solutions where strings were being hand built to conform with what DataTables is looking for.
Here's my ready function:
[code]
$(document).ready(function() {
initPopovers();
doUpdate();
setInterval(function(){doUpdate();},1000);
/* setInterval(function(){doDebug();},1); */
eventId = ${demandResponseData.header.eventId};
/* Table initialisation */
$('#device-table').dataTable( {
"bProcessing":true,
"bServerSide":true,
"sAjaxSource":"",
"fnServerData": function( sSource, aoData, fnCallback ) {
$.ajax( {
"dataType": 'json',
"type": "GET",
"url": sSource,
"data": aoData,
"success": fnCallback
});
},
/* "sAjaxSource":'http://www.sprymedia.co.uk/dataTables-1.4/media/examples_support/json_source.txt', */
/* "sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>", */
"sPaginationType": "bootstrap",
"oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
} );
$('#summary-stats').jScroll({top:100});
});
[/code]
And the table in the html:
[code]
Rendering engine
Browser
Platform(s)
Engine version
CSS grade
[/code]
And the relevant Spring Controller:
[code]
@RequestMapping(value = "/updateTable", method = RequestMethod.GET)
public @ResponseBody String doAjax() {
//Create the response, a well formed JSON including Datatables required vars.
//e.g.
return
"{ \"sEcho\": 2," +
" \"iTotalRecords\": 2," +
" \"iTotalDisplayRecords\": 2," +
" \"aaData\": [" +
" [" +
" \"Gecko\"," +
" \"Firefox 1.0\"," +
" \"Win 98+ / OSX.2+\"," +
" \"1.7\"," +
" \"A\"" +
" ]," +
" [" +
" \"Gecko\"," +
" \"Firefox 1.5\"," +
" \"Win 98+ / OSX.2+\"," +
" \"1.8\"," +
" \"A\"" +
" ]" +
" ]" +
"}";
}
[/code]
So far, this stuff mostly works. The problem is that filters & navigation no longer works and it's clear that I would have to do an enormous amount of parsing work and handling inputs and outputs so the server can calculate and return all the necessary information.
Here's what I really want to do.
I have other features in my webpage that already receive JSON information to render a graph, to render a progress bar, and to do various other things. In particular, I have this one response that I already have -- I would just use it to update the table:
[code]
{
"header":
{
"startDate":1338422400000,
"endDate":1338438600000,
"devicesExpected":8,
"eventId":482,
},
"status":
{
"dirty":false,
"numPending":0,
"numScheduling":0,
"numRetry":0,
"numScheduled":0,
"numStateCompleted":8,
"dirty":false,
},
"progress":
{
"state":"Completed",
"progressPercent":100.0,
"currTimeLabel":"Jun 04, 2012 10:53:36AM (PDT)",
"timeRemaining":"at May 30, 2012 09:30:00PM (PDT)"
},
"statusDto":
[{
"state":"COMPLETED",
"scheduledUtc":1338421705000,
"abortedUtc":null,
"completedUtc":1338438162000,
"overriddenUtc":null,
"eventStatus":"Completed"
},
{
"state":"COMPLETED",
"scheduledUtc":1338421706000,
"abortedUtc":null,
"completedUtc":1338437504000,
"overriddenUtc":null,
"eventStatus":"Completed"},
}]
}
[/code]
So the info in statusDto is ultimately what I would want to use to create the table with. It will typically range from a few dozen to a few hundred, but unlikely to be more than a few thousand. The status of each device can change and the server detects if anything is dirty triggering a new refresh.
So ideally... my ajax code already has a perfect hook:
[code]
if( data.status.dirty == true ) {
generateTable(data.statusDto);
}
function generateTable(tableData) {
???
}
[/code]
In generate table, I would nuke the table, generate a new one by iterating the tableData and somehow filling in the fields... but I'm not sure how to make the connection. I'm trying to understand this at a high level and looking to be pointed in the right direction and not go down a weird path.
Also, is there an clean way to convert a JSON 2D array (as above's statusDto) into something a DataTable could use -- while keeping all the features such as filter and navigation working?
Here's my ready function:
[code]
$(document).ready(function() {
initPopovers();
doUpdate();
setInterval(function(){doUpdate();},1000);
/* setInterval(function(){doDebug();},1); */
eventId = ${demandResponseData.header.eventId};
/* Table initialisation */
$('#device-table').dataTable( {
"bProcessing":true,
"bServerSide":true,
"sAjaxSource":"",
"fnServerData": function( sSource, aoData, fnCallback ) {
$.ajax( {
"dataType": 'json',
"type": "GET",
"url": sSource,
"data": aoData,
"success": fnCallback
});
},
/* "sAjaxSource":'http://www.sprymedia.co.uk/dataTables-1.4/media/examples_support/json_source.txt', */
/* "sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>", */
"sPaginationType": "bootstrap",
"oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
} );
$('#summary-stats').jScroll({top:100});
});
[/code]
And the table in the html:
[code]
Rendering engine
Browser
Platform(s)
Engine version
CSS grade
[/code]
And the relevant Spring Controller:
[code]
@RequestMapping(value = "/updateTable", method = RequestMethod.GET)
public @ResponseBody String doAjax() {
//Create the response, a well formed JSON including Datatables required vars.
//e.g.
return
"{ \"sEcho\": 2," +
" \"iTotalRecords\": 2," +
" \"iTotalDisplayRecords\": 2," +
" \"aaData\": [" +
" [" +
" \"Gecko\"," +
" \"Firefox 1.0\"," +
" \"Win 98+ / OSX.2+\"," +
" \"1.7\"," +
" \"A\"" +
" ]," +
" [" +
" \"Gecko\"," +
" \"Firefox 1.5\"," +
" \"Win 98+ / OSX.2+\"," +
" \"1.8\"," +
" \"A\"" +
" ]" +
" ]" +
"}";
}
[/code]
So far, this stuff mostly works. The problem is that filters & navigation no longer works and it's clear that I would have to do an enormous amount of parsing work and handling inputs and outputs so the server can calculate and return all the necessary information.
Here's what I really want to do.
I have other features in my webpage that already receive JSON information to render a graph, to render a progress bar, and to do various other things. In particular, I have this one response that I already have -- I would just use it to update the table:
[code]
{
"header":
{
"startDate":1338422400000,
"endDate":1338438600000,
"devicesExpected":8,
"eventId":482,
},
"status":
{
"dirty":false,
"numPending":0,
"numScheduling":0,
"numRetry":0,
"numScheduled":0,
"numStateCompleted":8,
"dirty":false,
},
"progress":
{
"state":"Completed",
"progressPercent":100.0,
"currTimeLabel":"Jun 04, 2012 10:53:36AM (PDT)",
"timeRemaining":"at May 30, 2012 09:30:00PM (PDT)"
},
"statusDto":
[{
"state":"COMPLETED",
"scheduledUtc":1338421705000,
"abortedUtc":null,
"completedUtc":1338438162000,
"overriddenUtc":null,
"eventStatus":"Completed"
},
{
"state":"COMPLETED",
"scheduledUtc":1338421706000,
"abortedUtc":null,
"completedUtc":1338437504000,
"overriddenUtc":null,
"eventStatus":"Completed"},
}]
}
[/code]
So the info in statusDto is ultimately what I would want to use to create the table with. It will typically range from a few dozen to a few hundred, but unlikely to be more than a few thousand. The status of each device can change and the server detects if anything is dirty triggering a new refresh.
So ideally... my ajax code already has a perfect hook:
[code]
if( data.status.dirty == true ) {
generateTable(data.statusDto);
}
function generateTable(tableData) {
???
}
[/code]
In generate table, I would nuke the table, generate a new one by iterating the tableData and somehow filling in the fields... but I'm not sure how to make the connection. I'm trying to understand this at a high level and looking to be pointed in the right direction and not go down a weird path.
Also, is there an clean way to convert a JSON 2D array (as above's statusDto) into something a DataTable could use -- while keeping all the features such as filter and navigation working?
This discussion has been closed.
Replies
DataTables can use that object as is: http://datatables.net/blog/Extended_data_source_options_with_DataTables . The 2D array is an old limitation that doesn't apply as of 1.8+ :-).
Do you really need server-side processing? You are echoing a static '2' for sEcho, which will never allow server-side processing to work, since that is different on each table draw. Server-side processing is only really needed for 5000+ rows (perhaps 10,000+ depending on how complex the table is).
Allowing DataTables to do the filtering, sorting etc means you just need to give it your data source, and that's it done :-)
Allan
[code]
var oTable; /*global scope*/
oTable = $('#device-table').dataTable( {
"sAjaxSource":"",
"aoColumns": [
{ "mDataProp": "state" },
{ "mDataProp": "scheduledUtc"},
{ "mDataProp": "abortedUtc"},
{ "mDataProp": "completedUtc"},
{ "mDataProp": "overriddenUtc"},
{ "mDataProp": "eventStatus"}
]
} );
[/code]
NOTE: I had a problem where my html for the table only had 5 columns, and it threw an exception on creation. Adding the correct number of columns fixed it.
Then I have my own code that updates and detects when the table is dirty -- and generates it.
[code]
generateTable(data.statusDto);
...
function generateTable(tableData) {
if( oTable == null ) {
return;
}
oTable.fnClearTable();
oTable.fnAddData(tableData);
}
[/code]
NOTE: At one point fnClearTable() was not defined, throwing exception, but it was because oTables was undefined because it was failing to initialize. Fixing that, fixed this problem.
where statusDto =
[code]
[{"state":"COMPLETED","scheduledUtc":1338421705000,"abortedUtc":null,"completedUtc":1338438162000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421706000,"abortedUtc":null,"completedUtc":1338437504000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421706000,"abortedUtc":null,"completedUtc":1338438547000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421714000,"abortedUtc":null,"completedUtc":1338437967000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421703000,"abortedUtc":null,"completedUtc":1338437969000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421708000,"abortedUtc":null,"completedUtc":1338437882000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421710000,"abortedUtc":null,"completedUtc":1338438134000,"overriddenUtc":null,"eventStatus":"Completed"},{"state":"COMPLETED","scheduledUtc":1338421706000,"abortedUtc":null,"completedUtc":1338438353000,"overriddenUtc":null,"eventStatus":"Completed"}]
[/code]
Spring
[code]
@RequestMapping(value = "/demand_response")
public class DemandResponseMonitoringController {
private DemandResponseMonitoringService demandResponseMonitoringService;
@RequestMapping(value = "/updateTable", method = RequestMethod.GET)
public @ResponseBody List doAjax(@RequestParam("eventId") int eventId) {
DemandResponseData data = demandResponseMonitoringService.getEventData(eventId);
if( data != null ) {
return data.getStatusDto();
}
return null;
}
[/code]
HTML
[code]
State
Scheduled
Aborted
Completed
Overridden
Status
[/code]
And the graph shows up correctly and filters/navigation work fine. So still need to test what happens on a larger dataset when filters and navigation on other pages are on.