I am using new feature in Datatables: \"HTML5 export buttons\". I am loading data with Ajax.
https://datatables.net/extensions/buttons/examples/html5/simple.html
Just wanted to post an actual answer for people struggling with this.
If you are exporting using the excel button, you can use the customizeData
button property to format the data going to excel a moment before it exports.
I used this to make a synchronous api call to my server to get the data, return it, massage it, and then let it continue on it's way. Code below.
{
extend: 'excel',
customizeData: function (p)
{
//get the params for the last datatables ajax call
var params = JSON.parse(options.dataTable.ajax.params());
//flag to tell the server to ignore paging info and get everything that matches the filter
params.export = true;
UC.Api(options.api.read.getHook(), params, function (data)
{
p.body = new Array();
$.each(data.data, function (i, d)
{
var item = [d.serial, UC.FormatDateToLocal(d.meta.Date), d.transmission.title, d.transmission.type, d.transmission.information];
p.body.push(item);
});
}, null, { async: false });
}
},
If you use Laravel framework, you can use this....
$.fn.DataTable.Api.register( 'buttons.exportData()', function( options ) {
if(this.context.length) {
var src_keyword = $('.dataTables_filter input').val();
// make columns for sorting
var columns = [];
$.each(this.context[0].aoColumns, function(key, value) {
columns.push({
'data' : value.data,
'name' : value.name,
'searchable' : value.bSearchable,
'orderable' : value.bSortable
});
});
// make option for sorting
var order = [];
$.each(this.context[0].aaSorting, function(key, value) {
order.push({
'column' : value[0],
'dir' : value[1]
});
});
// make value for search
var search = {
'value' : this.context[0].oPreviousSearch.sSearch,
'regex' : this.context[0].oPreviousSearch.bRegex
};
var items = [];
var status = $('#status').val();
$.ajax({
url: "server_side_url",
data: { columns: columns, order: order, search: search, status: status, page: 'all' }
success: function (result) {
$.each(result.data, function(key, value) {
var item = [];
item.push(key+1);
item.push(value.username);
item.push(value.email);
item.push(value.created_at);
item.push(value.status);
items.push(item);
});
},
async: false
});
return {
body: items,
// skip actions header
header: $("#user_table thead tr th").map(function() {
if(this.innerHTML!='Actions')
return this.innerHTML;
}).get()
};
}
});
var user_table = $('#user_table').DataTable({
dom: 'Bfrtip',
buttons: [
'copy', 'csv', 'excel', 'pdf', 'print'
],
"oSearch": {"bSmart": false},
processing: true,
serverSide: true,
ajax: {
url: "server_side_url",
type: 'GET',
data: function (d) {
d.status = ""; // when onload make status as empty to get all users
}
},
columns: [
{data: 'DT_RowIndex', name: 'DT_RowIndex'},
{data: 'username', name: 'username'},
{data: 'email', name: 'email'},
{data: 'created_at', name: 'created_at'},
{data: 'status', name: 'status'},
{data: 'actions', name: 'actions', orderable: false, searchable: false},
],
});
// filter users with status
$('#status').change(function() {
user_table.draw();
});
Yes, it's totally possible to make this work. Internally, DataTables has a function called buttons.exportData(). When you press a button, this function is called and returns the current page content. You can overwrite that function so it pulls all server side results based on current filters. And calling the same url used for ajax pagination.
You overwrite it before initializing your table. The code is as follows:
$(document).ready(function() {
jQuery.fn.DataTable.Api.register( 'buttons.exportData()', function ( options ) {
if ( this.context.length ) {
var jsonResult = $.ajax({
url: 'myServerSide.json?page=all',
data: {search: $(#search).val()},
success: function (result) {
//Do nothing
},
async: false
});
return {body: jsonResult.responseJSON.data, header: $("#myTable thead tr th").map(function() { return this.innerHTML; }).get()};
}
} );
$("#myTable ").DataTable(
{
"dom": 'lBrtip',
"pageLength": 5,
"buttons": ['csv','print', 'excel', 'pdf'],
"processing": true,
"serverSide": true,
"ajax": {
"url": "myServerSide.json",
"type": 'GET',
"data": {search: $(#search).val()}
}
}
});
I'm using Datatables Version: 1.10.15, and got @kevenpo's answer to work. I had to modify it a bit to handle our server-side parameters, but that was the only stumbling block. I changed his line: data.length = 2147483647;
to data.params[2]= -1;
because we stored our server-side parameters in a params sub-array. I have not tested it yet with a very large dataset to see what the performance is, but this is a very clever solution.
Thanks a lot to the user "kevinpo". He has given the way how all records from jquery datatable to be downloaded as excel when server side processing is On. Based on his answer, here i have complete export functionality implemented (copy, excel, csv, pdf, print) for server side processing.
inside $(document).ready()
define the below function & call this function on action
of each export button like below :
/* For Export Buttons available inside jquery-datatable "server side processing" - Start
- due to "server side processing" jquery datatble doesn't support all data to be exported
- below function makes the datatable to export all records when "server side processing" is on */
function newexportaction(e, dt, button, config) {
var self = this;
var oldStart = dt.settings()[0]._iDisplayStart;
dt.one('preXhr', function (e, s, data) {
// Just this once, load all data from the server...
data.start = 0;
data.length = 2147483647;
dt.one('preDraw', function (e, settings) {
// Call the original action function
if (button[0].className.indexOf('buttons-copy') >= 0) {
$.fn.dataTable.ext.buttons.copyHtml5.action.call(self, e, dt, button, config);
} else if (button[0].className.indexOf('buttons-excel') >= 0) {
$.fn.dataTable.ext.buttons.excelHtml5.available(dt, config) ?
$.fn.dataTable.ext.buttons.excelHtml5.action.call(self, e, dt, button, config) :
$.fn.dataTable.ext.buttons.excelFlash.action.call(self, e, dt, button, config);
} else if (button[0].className.indexOf('buttons-csv') >= 0) {
$.fn.dataTable.ext.buttons.csvHtml5.available(dt, config) ?
$.fn.dataTable.ext.buttons.csvHtml5.action.call(self, e, dt, button, config) :
$.fn.dataTable.ext.buttons.csvFlash.action.call(self, e, dt, button, config);
} else if (button[0].className.indexOf('buttons-pdf') >= 0) {
$.fn.dataTable.ext.buttons.pdfHtml5.available(dt, config) ?
$.fn.dataTable.ext.buttons.pdfHtml5.action.call(self, e, dt, button, config) :
$.fn.dataTable.ext.buttons.pdfFlash.action.call(self, e, dt, button, config);
} else if (button[0].className.indexOf('buttons-print') >= 0) {
$.fn.dataTable.ext.buttons.print.action(e, dt, button, config);
}
dt.one('preXhr', function (e, s, data) {
// DataTables thinks the first item displayed is index 0, but we're not drawing that.
// Set the property to what it was before exporting.
settings._iDisplayStart = oldStart;
data.start = oldStart;
});
// Reload the grid with the original page. Otherwise, API functions like table.cell(this) don't work properly.
setTimeout(dt.ajax.reload, 0);
// Prevent rendering of the full data to the DOM
return false;
});
});
// Requery the server with the new one-time export settings
dt.ajax.reload();
};
//For Export Buttons available inside jquery-datatable "server side processing" - End
And for buttons, define like below
"buttons": [
{
"extend": 'copy',
"text": '<i class="fa fa-files-o" style="color: green;"></i>',
"titleAttr": 'Copy',
"action": newexportaction
},
{
"extend": 'excel',
"text": '<i class="fa fa-file-excel-o" style="color: green;"></i>',
"titleAttr": 'Excel',
"action": newexportaction
},
{
"extend": 'csv',
"text": '<i class="fa fa-file-text-o" style="color: green;"></i>',
"titleAttr": 'CSV',
"action": newexportaction
},
{
"extend": 'pdf',
"text": '<i class="fa fa-file-pdf-o" style="color: green;"></i>',
"titleAttr": 'PDF',
"action": newexportaction
},
{
"extend": 'print',
"text": '<i class="fa fa-print" style="color: green;"></i>',
"titleAttr": 'Print',
"action": newexportaction
}
],
That's it. Now your download is ready.
I know this is an old question, however for anyone struggling with this, here's my solution.
Variables:
var downloading = false,
downloadTimestamp = null;
Download button definition:
buttons: [{
text: '<span class="glyphicon glyphicon-save-file" aria-hidden="true"></span>',
titleAttr: 'CSV',
className: 'downloadCSV',
action: function(e, dt, node, config) {
if (downloading === false) { //if download is in progress, do nothing, else
node.attr('disabled', 'disabled'); //disable download button to prevent multi-click, probably some sort of *busy* indicator is a good idea
downloading = true; //set downloading status to *true*
dt.ajax.reload(); //re-run *DataTables* AJAX query with current filter and sort applied
}
}
}]
Ajax definition:
ajax: {
url: ajaxURL,
type: 'POST',
data: function(data) {
data.timestamp = new Date().getTime(); //add timestamp to data to be sent, it's going to be useful when retrieving produced file server-side
downloadTimestamp = data.timestamp; //save timestamp in local variable for use with GET request when retrieving produced file client-side
if (downloading === true) { //if download button was clicked
data.download = true; //tell server to prepare data for download
downloading = data.draw; //set which *DataTable* draw is actually a request to produce file for download
}
return { data: JSON.stringify(data) }; //pass data to server for processing
}
}
'preDrawCallback' function:
preDrawCallback: function(settings) {
if (settings.iDraw === downloading) { //if returned *DataTable* draw matches file request draw value
downloading = false; //set downloading flag to false
$('.downloadCSV').removeAttr('disabled'); //enable download button
window.location.href = ajaxURL + '?' + $.param({ ts: downloadTimestamp }); //navigate to AJAX URL with timestamp as parameter to trigger file download. Or You can have hidden IFrame and set its *src* attribute to the address above.
return false; //as it is file request, table should not be re-drawn
}
}
Server-side:
if(download == false), then server executes SELECT columns FROM tables WHERE rowNumber BETWEEN firstRow AND lastRow and outputs result for normal display within DataTable.
if(download == true), then server executes SELECT columns FROM tables and stores all rows formatted as CSV file (or any other file format depending on what Your server environment is capable to produce) server-side for later retrieval by GET request.
Following is ASP JScript code that I've used server-side:
var timestamp = Number(Request.QueryString('ts')), //if it's a GET request, get timestamp
tableData = {
draw: data.draw,
recordsTotal: 100, //some number static or dynamic
recordsFiltered: 10, //some number static or dynamic
data: []
};
jsonData = String(Request.Form('data')), //if it's POST request, get data sent by *DataTable* AJAX
data = jsonData === 'undefined' || jsonData.length === 0 ? null : JSON.parse(jsonData); //do some error checking (optional)
if(!isNaN(timestamp)) { //check timestamp is valid
var csvTextKey = 'download-' + timestamp, //this is where timestamp value is used (can be any other unique value)
csvText = Session(csvTextKey); //obtain saved CSV text from local server-side storage
if(typeof csvText === 'undefined') { //if CSV text does not exist in local storage, return nothing (or throw error is You wish)
Response.End();
}
//if CSV exists:
Response.ContentType = 'text/csv'; //set response mime type
Response.AddHeader('Content-Disposition', 'attachment; filename=test.csv'); //add header to tell browser that content should be downloaded as file and not displayed
Response.Write(csvText); //send all content to browser
Response.End(); //stop further server-side code execution
}
//if timestamp is not valid then we assume this is POST request, hence data should be either prepared for display or stored for file creation
if(typeof data !== 'object' || data === null) { //do some more clever error checking
throw 'data is not an object or is null';
}
var recordset = data.download === true ? sqlConnection.Execute('SELECT * FROM #FinalTable') : Utilities.prepAndRunSQLQuery('SELECT * FROM #FinalTable WHERE rowId BETWEEN ? AND ?', [data.start, data.start + data.length], //execute SELECT either for display or for file creation
headerRow = [],
sqlHeaderRow = [],
exportData = [];;
if(data.download === true) { //create CSV file (or any other file)
if(!Array.isArray(data.columns)) {
throw 'data.columns is not an array';
}
for(var i = 0, dataColumnsCount = data.columns.length; i < dataColumnsCount; ++i) {
var dataColumn = data.columns[i], //get columns data object sent by client
title = dataColumn.title, //this is custom property set on client-side (not shown in code above)
sqlColumnName = typeof dataColumn.data === 'string' ? dataColumn.data : (typeof dataColumn.data.display === 'string' ? dataColumn.data.display : dataColumn.data['_']); //set SQL table column name variable
if(typeof title === 'string' && typeof sqlColumnName === 'string' && columnNames.indexOf(sqlColumnName) > -1) { //some more error checking
headerRow.push(title);
sqlHeaderRow.push(sqlColumnName);
}
}
exportData.push('"' + headerRow.join('","') + '"'); //add table header row to in CSV file format
}
while(recordset.EOF === false) { //iterate through recordset
if(data.download === true) { //if download flag is set build string containing CSV content
var row = [];
for(var i = 0, count = sqlHeaderRow.length; i < count; ++i) {
row.push(String(recordset.Fields(sqlHeaderRow[i]).Value).replace('"', '""'));
}
exportData.push('"' + row.join('","') + '"');
}
else { //else format data for display
var row = {};
for(var i = 1, fieldsCount = recordset.Fields.Count; i < fieldsCount; ++i) {
var field = recordset.Fields(i),
name = field.Name,
value = field.Value;
row[name] = value;
}
tableData.data.push(row);
}
recordset.MoveNext();
}
if(data.download === true) { //save CSV content in server-side storage
Session('download-' + data.timestamp) = exportData.join('\r\n'); //this is where timestamp value is used (can be any other unique value)
}
Response.Write(JSON.stringify(tableData)); //return data for display, if download flag is set, tableData.data = []