Export All from DataTables with Server Side processing? - javascript

I have tables which use DataTables Server Side processing to show on my website. I want to be able to 'Export All' and have all rows be exported, not just those rows being displayed. There are 60000+ rows and 65+ columns, so it must be done with server side processing.
I have tried a few things, but so far nothing has worked.
I have tried this:
{ extend: 'excel',
text: 'Export Current Page',
exportOptions: {
modifier: {
page: 'current'
}
},
customize: function (xlsx)
{
var sheet = xlsx.xl.worksheets['sheet1.xml'];
$('row:first c', sheet).attr('s', '7');
}
}
Which only exported the rows that were showing on the page.
I've tried this:
{
text: 'Export All to Excel',
action: function (e, dt, button, config)
{
dt.one('preXhr', function (e, s, data)
{
data.length = -1;
}).one('draw', function (e, settings, json, xhr)
{
var excelButtonConfig = $.fn.DataTable.ext.buttons.excelHtml5;
var addOptions = { exportOptions: { 'columns': ':all'} };
$.extend(true, excelButtonConfig, addOptions);
excelButtonConfig.action(e, dt, button, excelButtonConfig);
}).draw();
}
}
This sends the whole table's data to the screen instead of using the pagination and sending the whole data set to an excel file.
I've searched around on Google and here in SO, but have not found a solution that works.
I should also mention that I want to Export All based on the current filters set on the table. So that the end user will get an Export of only those rows that they are searching for. They typically limit it to 30k - 40k rows, still with the 65+ columns. I don't (yet) allow to remove/hide columns.
EDIT/UPDATE
Here's a secondary consideration: If I can't Export All from a response from the server, can I build the Excel file on the server? My servers don't have Excel installed and I will still want my end user to get the file. I'm sure that I'd have to find a way to get Excel onto my servers, but how would I transfer any created files to the end user and would that even be any faster than just sending a response with the whole dataset and creating the Excel file on the user's computer?
EDIT
It was recommended that I try jquery's $.ajax() to get this to work. If someone could give me an idea of how to do that I'll try that for a third button.
I can already pull all the data, with the same filters and sorting that are added by the user, and do that with a button. The second attempt above does that but sends it to the screen. I have PHPExcel and a file that can create an Excel sheet. How would I take what I get in that second button and send it to the other file to create the Excel sheet? I thought that using jquery's $.ajax() might work, I just don't know how to get it to. I do know that I'll have to use $_POST since the data could be too big to use $_GET to send the data to the PHPExcel file.
I can already export to a CSV, but I need to export with some formatting, which CSV does not have. That's why I'm going to the trouble of use PHPExcel.
EDIT III
I am trying this, though it's not yet working:
{
text: 'Export all to Excel II',
action: function (e, dt, button, config)
{
dt.one('preXhr', function (e, s, data)
{
data.length = -1;
}).one('export', function (e, settings, json, xhr)
{
var excelButtonConfig = $.fn.DataTable.ext.buttons.excelHtml5;
var addOptions = { exportOptions: { 'columns': ':all'} };
$.extend(true, excelButtonConfig, addOptions);
excelButtonConfig.action(e, dt, button, excelButtonConfig);
})
}
}
EDIT 4
Hopefully the last edit.
I know that I have to do three things to make this work:
Get current Sorting and Filtering
Get dataset with length set to -1
Send this to PHPExcel file for processing and creation of Excel file
I can create a button like this:
{
text: 'Export all Data to Excel',
action:
}
I just don't know what the action needs to be.
My second attempt above pulls the whole dataset that I need, but sends it to the screen instead of to my PHPExcel file (ExportAllToExcel.php).
I have been trying to figure this out and haven't gotten very far. I've been told that I need to use $.ajax() to do this, I've been told that I don't need to use that. I have tried with and without and have not been able to get anywhere.
I have also tried using this to no effect:
$.fn.dataTable.ext.buttons.export =
{
className: 'buttons-alert',
"text": "Export All Test",
action: function (e, dt, node, config)
{
var SearchData = dt.search();
var OrderData = dt.order();
alert("Test Data for Searching: " + SearchData);
alert("Test Data for Ordering: " + OrderData);
}
};

First add the follwoing code in DataTable
"dom": 'Blfrtip',
"buttons": [
{
"extend": 'excel',
"text": '<button class="btn"><i class="fa fa-file-excel-o" style="color: green;"></i> Excel</button>',
"titleAttr": 'Excel',
"action": newexportaction
},
],
Then add this function inside $(document).ready() function
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();
}

I have this working, mostly. It is now timing out, but that's a separate issue due to data size not to this working. For small datasets, it works perfectly.
This is how I create the button (it's the export button that I'm using here):
"buttons": [{
extend: 'collection',
text: 'Selection',
buttons: ['selectAll', 'selectNone']
}, {
extend: 'collection',
text: 'Export',
buttons: ['export', 'excel', 'csv', 'pdf', { extend: 'excel',
text: 'Export Current Page',
exportOptions: {
modifier: {
page: 'current'
}
},
customize: function (xlsx)
{
var sheet = xlsx.xl.worksheets['sheet1.xml'];
$('row:first c', sheet).attr('s', '7');
}
}]
}
]
This is the initialization of the button created above:
$.fn.dataTable.ext.buttons.export =
{
className: 'buttons-alert',
id: 'ExportButton',
text: "Export All Test III",
action: function (e, dt, node, config)
{
var SearchData = dt.rows({ filter: 'applied' }).data();
var SearchData1 = dt.search();
console.log(SearchData);
var OrderData = dt.order();
console.log(SearchData1);
var NumCol = SearchData[0].length;
var NumRow = SearchData.length;
var SearchData2 = [];
for (j = 0; j < NumRow; j++)
{
var NewSearchData = SearchData[j];
for (i = 0; i < NewSearchData.length; i++)
{
NewSearchData[i] = NewSearchData[i].replace("<div class='Scrollable'>", "");
NewSearchData[i] = NewSearchData[i].replace("</div>", "");
}
SearchData2.push([NewSearchData]);
}
for (i = 0; i < SearchData2.length; i++)
{
for (j = 0; j < SearchData2[i].length; j++)
{
SearchData2[i][j] = SearchData2[i][j].join('::');
}
}
SearchData2 = SearchData2.join("%%");
window.location.href = './ServerSide.php?ExportToExcel=Yes';
}
};
And here is the part of the ServerSide.php file that gets the data and sends it to the server for processing:
require('FilterSort.class.php');
if (isset($_GET['ExportToExcel']) && $_GET['ExportToExcel'] == 'Yes')
{
$request = #unserialize($_COOKIE['KeepPost']);
$DataReturn = json_encode(FilterSort::complex($request,$sqlConnect,$table,$primaryKey,$ColumnHeader));
require './ExportAllToExcel.php';
}
else
{
echo json_encode(FilterSort::complex($request,$sqlConnect,$table,$primaryKey,$ColumnHeader));
}
This is how I set the cookie that I use to keep the search and sort criteria:
if(isset($_POST['draw']))
{
$KeepPost = $_POST;
$KeepPost['length'] = -1;
$PostKept = serialize($KeepPost);
setcookie("KeepPost",$PostKept,time() + (60*60*24*7));
}
All this combined sends the correct criteria to FilterSort.class.php which should process the criteria and return the dataset to ExportAllToExcell.php which then creates the Excel file. Right now I'm sending it huge reports and it times out, though.
UPDATE
I have slightly changed the way that I do this:
Here is the new set of buttons:
"buttons": [{
extend: 'collection',
text: 'Export',
buttons: ['export', { extend: 'csv',
text: 'Export All To CSV', //Export all to CSV file
action: function (e, dt, node, config)
{
window.location.href = './ServerSide.php?ExportToCSV=Yes';
}
}, 'csv', 'pdf', { extend: 'excel',
text: 'Export Current Page', //Export to Excel only the current page and highlight the first row as headers
exportOptions: {
modifier: {
page: 'current'
}
},
customize: function (xlsx)
{
var sheet = xlsx.xl.worksheets['sheet1.xml'];
$('row:first c', sheet).attr('s', '7');
}
}]
}
]
Here is how I create the Export All to Excel button:
$.fn.dataTable.ext.buttons.export =
{
className: 'buttons-alert', //Adds the "Export all to Excel" button
id: 'ExportButton',
text: "Export All To Excel",
action: function (e, dt, node, config)
{
window.location.href = './ServerSide.php?ExportToExcel=Yes';
}
};
These now send the data to the same ServerSide.php file that I was using before:
require('FilterSort.class.php');
if (isset($_GET['ExportToExcel']) && $_GET['ExportToExcel'] == 'Yes')
{
include 'Helper/LogReport.php';
$GetSQL = "Select Value from PostKept where UserName = '" .$_COOKIE['UserName']. "'";
$KeepResult = $conn->query($GetSQL);
$KeepResults = $KeepResult->fetchALL(PDO::FETCH_ASSOC);
$request = unserialize($KeepResults[0]['Value']);
$DataReturn = json_encode(FilterSort::complex($request,$sqlConnect,$table,$primaryKey,$ColumnHeader,1));
require './ExportAllToExcel.php';
I have also changed the way that I keep the query, I have it now also keeping the Table Name and UserName like this:
include 'DBConn.php';
$KeepPost = $_POST; //POST holds all the data for the search
$KeepPost['length'] = -1; //-1 means pulling the whole table
$PostKept = serialize($KeepPost); //This takes the array of data and turns it into a string for storage in SQL
$SQLCheck = "select distinct UserName from PostKept"; //Gets all the distinct Usernames of users that have used the Report Dashboard.
$sth = $conn->query($SQLCheck);
$CheckedUser = $sth->fetchALL(PDO::FETCH_ASSOC);
foreach($CheckedUser as $User)
{
foreach($User as $Index => $Who)
{
$FoundUsers[] = $Who; //Taking all the found users and placing them into a simpler array for searching later
}
}
if(isset($_COOKIE['UserName']) && in_array($_COOKIE['UserName'],$FoundUsers)) //If the user already has an entry update it with new information
{
$TSQL = "UPDATE PostKept set Value = '" .$PostKept. "', TableName = '" .$TableName. "' where UserName = '" .$_COOKIE['UserName']. "'";
}
else
{
if(isset($_COOKIE['UserName'])) //If this is a new user
{
$TSQL = "INSERT into PostKept(Value, TableName, UserName) select '" .$PostKept. "','" .$TableName. "','" .$_COOKIE['UserName']. "'";
}
else //If this is on the Prod site and the User info is not yet kept
{
$TSQL = "INSERT into PostKept(Value, TableName) select '" .$PostKept. "','" .$TableName. "'";
}
}
$sth = $conn->prepare($TSQL);
$sth->execute();
This is now what all combines to send the data to the ExportAllToExcel.php file that I have and then it in turn creates the file.

I just ran into this and came up with an alternate solution.
In the DataTable options, add this:
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
This will allow user to select all rows and will send -1 to server in 'length' query string parameter. At server side, you need to handle negative number and allow to return all rows when -1 is received.
This would display all rows in table and will export all of them.
I understand that this may not be suitable for 50-60K rows but for a smaller dataset, this can work without implementing any additional code at server and client side both.

in buttons:
action: function (e, dt, node, config) {
var formData = 'yourfilters';
formData.begin = '0';
formData.length = 'yourTotalSize';
$http({
url: 'yourURL',
method: 'POST',
data: JSON.stringify(formData)
}).then(function (ajaxReturnedData) {
dt.rows.add(ajaxReturnedData.data).draw();
$.fn.dataTable.ext.buttons.excelHtml5.action.call(this, e, dt, node, config);
});}

Related

A complete example using excel-builder.js

I need to use excel-builder.js to export excel file with stylings(header background color blue, freezing top row, column filtering), but the website with all documentation is not accessible anymore, I even contacted the author but without response.
If anyone can help create a small example with all these formattings, I will appreciate it a lot!
I know the following code will build an excel file:
var jsonData = [
['sample', 'data', 'for'],
['generating', 'excel', 'in'],
['java', 'script', ' ']
];
require(['excel-builder.js/excel-builder', 'download'], function (EB, downloader) {
var JSworkBook = EB.createWorkbook();
var JSworkSheet = JSworkBook.createWorksheet({name: 'Sheet'});
JSworkSheet.setData(jsonData);
JSworkBook.addWorksheet(JSworkSheet);
var data = EB.createFile(JSworkBook);
downloader('Artist WB.xlsx', data);
});
You can access a cached version of the documentation via the web archive . Keep in mind though that you'll need to navigate between the pages of the documentation using the cached-url-syntax, instead of just clicking on the links.
I'm only adding this because the project is now declared dead (https://github.com/stephenliberty/excel-builder.js), and the website documentation is offline(http://excelbuilderjs.com/). So best we can do is find someone willing to take control of it with a fork, or share our code.
The server-side script..
<cfscript>
path="/app/uploads/temp/";
full_path=ExpandPath(path) & FORM.filename;
if (!DirectoryExists(ExpandPath(path))) DirectoryCreate(ExpandPath(path));
FileWrite(full_path,(BinaryDecode(FORM.contents,"Base64")));
json=StructNew();
json.file=path & FORM.filename;
WriteJSON(json);
</cfscript>
you could use PHP..
<?php
header("Content-type: ".$_POST['contentType']);
header("Content-disposition: attachment; filename=\"{$_POST['fileName']}\"");
echo base64_decode($_POST['contents']);
?>
or asp..
[Authorize]
[System.Web.Mvc.SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class SystemController : ApiController {
private ILog log = LogManager.GetLogger(typeof(SystemController));
public class ExcelResponse {
public String FILE = "";
}
public class ExcelRequest {
public String filename;
public String contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
public String contents;
}
[HttpPost]
public ExcelResponse Excel(ExcelRequest request) {
ExcelResponse response = new ExcelResponse();
String the_path = "/_assets/temp/";
String path=System.Web.HttpContext.Current.Server.MapPath("~"+the_path);
log.Info("Generating excel file from data: "+path+request.filename);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
byte[] file_data=(Convert.FromBase64String(request.contents));
File.WriteAllBytes(path + request.filename, file_data);
response.FILE = the_path+request.filename;
return response;
}
}
And the include script...
require.config({
baseUrl: '/app/assets/',
paths: {
underscore: '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min',
JSZip: './excel-builder/jszip',
EB: './excel-builder.dist.min',
spin: '//cdnjs.cloudflare.com/ajax/libs/spin.js/1.2.7/spin.min',
image: '/3rdparty/requirejs/image',
text: '/3rdparty/requirejs/text',
util: '/excel-builder/Excel/util'
},
shim: {
'underscore': {
exports: '_'
},
'JSZip': {
exports: 'JSZip'
},
'swfobject': {
exports: 'swfObject'
}
}
});
goes into ./excel-builder/download.js
define(function () {
return function (fileName, content) {
var form = $("<form id='download'>").attr({
target: '_BLANK',
action: '/app/assets/excel-builder/excel.cfm',
method: 'post'
}).css({display: 'none'});
form.append($("<input>").attr({name: 'fileName', value: fileName}));
form.append($("<input>").attr({name: 'contentType', value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}));
var ta=$("<textarea>").attr({name: 'contents', value: content});
ta.val(content);
form.append(ta);
form.appendTo($("body"));
form.submit();
window.setTimeout(function () {form.remove();}, 30000);
}
});
The bootstrap (for tablesorter)...
$(document).ready(function() {
$("table.sort").on("filterEnd",ts_onTableFiltered);
});
The meat of it...
function ts_onTableFiltered(event,tblsorter) {
$(tblsorter.table).data('ts_sorter',tblsorter);/*legacy :: */ window.ts_currentSorter=tblsorter;
console.log(event.type);
$(tblsorter.table).data('ts_isFiltered',false); /*legacy. remove me after verification :: */ window.ts_isFiltered=false;
for (var i=0;i<tblsorter.lastSearch.length;i++) {
var currentItemFilter=((tblsorter.lastSearch[i]!="")?true:false);
if (currentItemFilter) {
$(tblsorter.table).data('ts_isFiltered',true);/* legacy:: */ts_isFiltered=true;
break;
}
}
var rows=[];
if ($.isEmptyObject( tblsorter.cache )) {
rows=tblsorter.$tbodies.eq( 0 ).children( 'tr' );
} else {
for(i=0;i<tblsorter.cache[0].normalized.length;i++) {
var row=tblsorter.cache[0].normalized[i][tblsorter.columns].$row[0];
if (!ts_isFiltered) {rows.push($(row));continue;}
var result=tblsorter.pager.regexRows.test(row.className);
if (!result) { // skip 'filtered' className
rows.push($(row));
}
}
}
$(tblsorter.table).data('ts_currentRowsInTable',rows); /*legacy :: */window.ts_currentRowsInTable=rows;
console.log(rows.length);
}
function downloadExcelFromTable(tableObj,filename,props) {
var button=$('a:contains("Excel")');
if (button.length > 1) {
var temp=0;
button.each(function() {
if ($(this).attr('onclick').indexOf(tableObj.attr('id'))>=0) {
temp=$(this);
}
});
//button=temp;
}
console.log("Generating document...");
var orig_data = [];
var headers=[];
var headers_text_len_max=0;
var excel_wait_html='<i class="fa fa-spinner fa-pulse"></i> Please wait...';
button.data('orig-html',button.html());button.html(excel_wait_html);
if (tableObj.data('ts_currentRowsInTable').length<1 || (tableObj.data('ts_currentRowsInTable').length==1 && tableObj.find('tr:contains("No known")').length>0)) {
return BootstrapMessage("Cannot download Excel document","Sorry but there aren't any result rows to populate an excel document. Please refine your report/resultant listing.",function() {
button.html(button.data('orig-html'));
});
}
var doSetupProperties=false;
if (typeof(props)==='undefined') {props={};}
if (typeof(props.column_widths)==='undefined') {
doSetupProperties=true;
props.column_widths=[];
props.column_widths_auto=[];
props.maxWidthPerRow=[];
}
var filterChosen=false;
if (tableObj.hasClass("selectable")) filterChosen=true;
var ignoreCols=[];
var i=0;
tableObj.find('th').each(function() {
if ($(this).text().length<1) ignoreCols.push(i);
else {
headers.push($(this).text());
props.maxWidthPerRow.push(0);
headers_text_len_max+=$(this).text().length;
}
i++;
});
orig_data.push(headers);
console.log("Headers done.");
var handleRow=function(row,ignoreCols) {
var col_i=0;
row.find('td').each(function() {
if ($.inArray(col_i,ignoreCols)<0) {
var text=$(this).text().split('\n').join('').trim();
if ($(this).hasClass('sorter-select')) {
text=$(this).find('select:first').val();
}
var contents=text;
if (tableObj.hasClass('attachTotalsRow')) {
if ((''+tableObj.data('totals_columns')).indexOf(col_i+1)!=-1) {
text=(text.replace('$',''));
contents=parseFloat(text);
}
//row_data.push({value: text,metadata: {style: }});
//row_data.push(parseFloat(text));
}
row_data.push(contents);
var href=$(this).find('a');
if (href.length>0) {
var url=href.attr('href');
row_data[row_data.length-1]={value: 'HYPERLINK("'+url+'","'+text+'")', metadata: { type: 'formula' }};
}
if (text.length > props.maxWidthPerRow[col_i]) props.maxWidthPerRow[col_i]=text.length;
}
col_i++;
});
return (row_data);
}
if (tableObj.hasClass('tablesorter')) {
/*its tablesorter, so we need to obtain all of the hidden filtered data too. */
var rows=tableObj.data('ts_currentRowsInTable');
for (i=0;i<rows.length;i++) {
var row_data=[];
var row = $(rows[i]);
/*if (filterChosen && !isNaN(tableObj.data('chosen'))) {
var target=row.find('td:nth-child('+tableObj.data('chosen')+' input[type=checkbox]');
if (target.length==1) {
if (!target.prop('checked')) continue;
}
}*/
orig_data.push(handleRow(row,ignoreCols));
}
} else {
tableObj.find('tbody tr').each(function() {
var row_data=[];
var row=$(this);
orig_data.push(handleRow(row,ignoreCols));
});
}
if (tableObj.hasClass('attachTotalsRow')) {
var list_cols=tableObj.data('totals_columns');
if (isNaN(list_cols)) {
var av_cols=list_cols.split(',');
} else {
var av_cols=[list_cols];
}
av_cols=av_cols.sort();
var max=orig_data.length;
var min=2; // skip header row
var new_totals_row=[];
for (var ii=0;ii<headers.length;ii++) {
new_totals_row[ii]="";
}
for (var i=0;i<av_cols.length;i++) {
var colIdent=String.fromCharCode(64+av_cols[i]);
new_totals_row[0]="Totals:"
new_totals_row[av_cols[i]-1]={value: 'SUM('+((colIdent+min)+':'+(colIdent+max))+')',metadata: {type: 'formula'}};
}
orig_data.push(new_totals_row);
}
// adjust column widths to fit their text.
if (doSetupProperties) {
var maxDigitWidth=8;
var padding=22;
var fn_truncate=function(num) {return Math.round(num*100)/100;};
var fn_calcWidth=function(numchars,maxdigit,pad) {return fn_truncate(((numchars * maxdigit + pad)/maxdigit*256)/256 );};
var fn_calcPixels=function(p_width,maxdigit){return fn_truncate(((256 * p_width + fn_truncate(128/maxdigit))/256)*maxdigit);};
for (var i=0;i<headers.length;i++) {
var perc_size_of_whole=(100/headers_text_len_max)*headers[i].length;
//props.column_widths.push({width: perc_size_of_whole*1.5});
//props.column_widths_auto.push({bestFit: true,width: maxWidthPerRow[i]});
props.column_widths.push({width: (fn_calcWidth(props.maxWidthPerRow[i],maxDigitWidth,padding)) });
}
}
console.log(props);
console.log("Row data done.");
require(['excel-builder/excel-builder', 'excel-builder/Excel/Table','excel-builder/download'], function (EB, Table, downloader) {
console.log("Beginning excel creation.");
var workbook = EB.createWorkbook();
var worksheet = workbook.createWorksheet({name: filename});
var stylesheet = workbook.getStyleSheet();
var currency = stylesheet.createFormat({
format: '$#,##0.00'
});
var boldDXF = stylesheet.createDifferentialStyle({
font: {
italic: true,
size: 12
}
});
var sheetStyle=stylesheet.createTableStyle({
name: 'SlightlyOffColorBlue',
wholeTable: boldDXF.id,
headerRow: stylesheet.createDifferentialStyle({
alignment: {horizontal: 'center'},
font: {
size: 13,
bold: true
}
}).id
});
console.log("Styles applied.");
var table = new Table();
table.styleInfo.themeStyle = "SlightlyOffColorBlue"; //"TableStyleDark2"; //This is a predefined table style
table.setReferenceRange([1, 1], [headers.length, orig_data.length]); //X/Y position where the table starts and stops.
//Table columns are required, even if headerRowCount is zero. The name of the column also must match the
//data in the column cell that is the header - keep this in mind for localization
table.setTableColumns(headers);
console.log("headers configured.");
worksheet.setData(orig_data);
worksheet.setColumns(props.column_widths)
//worksheet.setColumnFormats(props.column_widths_auto);
workbook.addWorksheet(worksheet);
worksheet.addTable(table);
workbook.addTable(table);
console.log("tables configured.");
var dt=new Date();
var file= replaceAll(filename," ","_") +"_"+ (dt.getFullYear()+"-"+dt.getMonth()+"-"+dt.getDate()+"_"+dt.getMilliseconds())+'.xlsx';
file=replaceAll(file,"/","_"); file=replaceAll(file,"\\","_");
var data = EB.createFile(workbook);
console.log("workbook generated. File generation commensing: ");
//downloader(file, data); // this uses the downloader.js file. Disabled as I am customizing the ajax call completely... as follows..
/*legacy attempt.. var contents=''+
'<form method="POST" action="/app/assets/excel-builder/excel.cfm" enctype="application/x-www-form-urlencoded">'+
'<input type="hidden" name="filename" value="'+file+'" />'+
'<input type="hidden" name="contentType" value="'+('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')+'" />'+
'<input type="hidden" name="contents" value="'+data+'" />'+
'<input type="submit" value="Download Now" class="button print" />'+
'</form>';*/
var dat={
filename: file,
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
contents: data
};
$.ajax({
url: '/app/assets/excel-builder/excel2.cfm',
type: "POST",
data: dat,
success: function(result) {
result=$.parseJSON(result);
console.log("Response received. File is located here: "+result.FILE);
if (typeof(onExcelDocGenerated)==='function') onExcelDocGenerated(); // optional callback (global)
contents='<a style="color: lightgreen;" href="'+result.FILE+'" class="button print" >Download Now</a>';
$.notice("Confirm Excel Download",contents,"Cancel",function() {
button.html(button.data('orig-html'));
if (typeof(excel_download_done)==='function') {
excel_download_done(); // optional callback (global)
}
},function() {
button.html(button.data('orig-html'));
if (typeof(excel_download_done)==='function') {
excel_download_done();
}
});
},
error: function(e) {
console.log("Fatal networking error: "+e);
}
});
});
}
And the implementation on the page..
<a href="#" id="exceldownload" onclick="try {downloadExcelFromTable($('table'),'My Report');}catch(e){alert('failed due to error: '+e);};return false;" class="button print table-excel">
<i class="fa fa-file-excel-o"></i> Excel
</a>
So that was a tablesorter full implementation supporting links inserted into the excel sheet and even a row for summation (totals) by simply adding the class(.attachTotalsRow) to the table class attr.
Even supports filtering, so it only grabs the rows that are filtered by tablesorter for the excel, and auto-column sizing based on text width.
There's several pieces that you can see were being developed, and lots of sparse comments and console logs thrown about, but it works.
The above is designed for the ajax call which provides a path to a file in JSON response. That way you can use the browser native click, which is supported on all devices. Opening a new window through javascript is unsafe, so that is the reason for that approach. Therefore the php script at the top would need updated to mimic the coldfusion one.
The part you wanted is at the bottom just before the ajax call.
Good luck!
var workbook = ExcelBuilder.Builder.createWorkbook();
var stylesheet = workbook.getStyleSheet();
var sheet1 = workbook.createWorksheet({
name: 'Data1'
});
var headings = stylesheet.createFormat({
"fill": {
"type": 'pattern',
"patternType": 'solid',
"fgColor": '4BACC6'
}
});
var jsonData = [
[{value:'sample', metadata: {style: headings.id}}, {value:'data', metadata: {style: headings.id}}, {value:'for', metadata: {style: headings.id}}],
['generating', 'excel', 'in'],
['java', 'script', ' ']
];
sheet1.setData(jsonData);
workbook.addWorksheet(sheet1);
ExcelBuilder.Builder.createFile(workbook, {
type: "blob"
}).then(function(data) {
saveAs(new Blob([data], {
type: "base64"
}), "Demo.xlsx");
});

getting php session variable in js file

I have a php file with a php if statement that generates a php session variable for a mySQL table name. I need to access this table name is a javascript file demo.js. Any ideas would be great.
PHP Code Snip-it:
$fcyclestartdate = $_POST['CycleStartDate'];
$fcycleenddate = $_POST['CycleEndDate'];
$_SESSION["paycycletable"] = "PCR_".str_replace("-", "", $fcyclestartdate)."_".str_replace("-", "", $fcycleenddate);
js file code Snip-it:
function DatabaseGrid()
{
this.editableGrid = new EditableGrid("demo", {//Variable table name must go where "demo" is
enableSort: true,
// define the number of row visible by page
pageSize: 50,
// Once the table is displayed, we update the paginator state
tableRendered: function() { updatePaginator(this); },
tableLoaded: function() { datagrid.initializeGrid(this); },
modelChanged: function(rowIndex, columnIndex, oldValue, newValue, row) {
updateCellValue(this, rowIndex, columnIndex, oldValue, newValue, row);
}
});
this.fetchGrid();
}
Save the table name in JS global space.
In your php code:
echo "<script> paycycletable='" . $_SESSION["paycycletable"] . "'</script>";
Now you have this variable anywhere in your JS
this.editableGrid = new EditableGrid(paycycletable, {...

How to get AngularJS and KendoUI working in harmony?

In stages, I setup my .Net MVC solution and ensured both Angular JS and KendoUI are working independently.
app.js:
var app = angular.module("app", ['kendo.directives']);
and in my controller, I have the following defined:
app.controller('contentTypesController', ['$scope', '$log', 'contentTypesRepository',
function ($scope, $log, contentTypesRepository) {
var a = {};
$scope.status;
$scope.contentTypes;
$scope.contentTypeOptions;
// for testing purposes, but not used - used for navigation properties
$scope.users;
getContentTypes();
function getContentTypes() {
// call the data repository
contentTypesRepository.getList()
.success(function (contentTypes) {
//console.log(contentTypes.value[0].Description);
//$log.log(contentTypes.value[0].Description)
$scope.contentTypes = contentTypes;
$scope.contentTypeOptions = {
dataSource: {
data: contentTypes
},
dataTextField: "Description",
dataValueField: "ContentTypeId",
optionLabel: "Select a Content Type"
};
})
.error(function (error) {
$scope.status = 'Unable to load data: ' + error.message;
});
}
$scope.updateContentTypes = function (id) {
var contentType;
for (var i = 0; i < $scope.contentTypes.length; i++) {
var currentType = $scope.contentTypes[i];
if (currentType.ID === id) {
contentType = currentType;
break;
}
}
};
$scope.insertContentType = function () {
// get contentType description from the client
var contentType = { 'Description': $scope.newContentType };
contentTypesRepository.insert(contentType)
.success(function () {
$scope.status = 'Added new content type. Refreshing list.';
// add the new content type to the client-side collection
$scope.contentTypes.value.push(
{ 'Description': $scope.newContentType }
);
$scope.newContentType = "";
})
.error(function (error) {
$scope.status = 'Unable to insert new content type: ' + error.message;
});
};
$scope.deleteContentType = function(id) {
contentTypesRepository.remove(id)
.success(function () {
$scope.status = 'Deleted content type. Refreshing list.';
for (var i = 0; i < $scope.contentTypes.length; i++) {
var contentType = $scope.contentTypes[i];
if (contentType.ID === id) {
// remove the content type from the client-side collection
$scope.contentTypes.splice(i, 1);
break;
}
}
// navigation properties = null
// $scope.xxxx = null;
})
.error(function (error) {
$scope.status = 'Unable to delete content type: ' + error.message;
});
};
// get some navigation property
//$scope.getCustomerOrders = function (id) {
// dataFactory.getOrders(id)
// .success(function (orders) {
// $scope.status = 'Retrieved orders!';
// $scope.orders = orders;
// })
// .error(function (error) {
// $scope.status = 'Error retrieving customers! ' + error.message;
// });
//};
$scope.addContentType = function () {
//return $scope.newContentType.$save();
$scope.contentTypes.value.push(
{ 'Description': $scope.newContentType }
);
$scope.newContentType = "";
}
In following the Angluar/Kendo examples here, I added code related to $scope.contentTypeOptions.
In my view:
<select kendo-drop-down-list k-options="contentTypeOptions"></select>
Which displays a dropdown, but no data.
I am able to view the data in a ng-repeater:
<ul>
<li ng-repeat="contentType in contentTypes.value">
{{ contentType.Description }}
</li>
</ul>
And the raw data by {{ contentTypeOptions }}.
Since the repeater uses contentTypes.value, I tried this as well in
$scope.contentTypeOptions = {
dataSource: {
data: contentTypes.value // tried both contentTypes and contentTypes.value
},
dataTextField: "Description",
dataValueField: "ContentTypeId",
optionLabel: "Select a Content Type"
};
... which is based on the JSON data:
Ultimately, I would like to get all the CRUD hooked up for a grid (which I have done in the past with OData, but now adding AngularJS to the mix) and thought simply displaying the data in an Angular/Kendo mix would be a good start. I'm hoping that after getting this pinned down the rest will be simple, and appreciate any suggestions.
Your code is a bit confusing since methods like $scope.updateContentTypes treat $scope.contentTypes as an array, but at the same time contentTypes appears to be an object with a property value which is an array.
One thing to be aware of is that Kendo UI widgets will convert your array to a Kendo DataSource internally. This means that changes you make to $scope.contentTypes won't affect the items in your data source in $scope.contentTypeOptions.
Another issue is that there is no full two-way binding between widgets and the data source in angular-kendo, and until recently, the data source wouldn't update at all unless you specifically declared it as a DataSource. There have been some improvements lately, although it's still not fully integrated, as far as I can see.
(you can try creating a deep watch on the data yourself, but that may create performance problems; see related post here).
Your dropdown doesn't show the data because you replace $scope.contentTypeOptions after creating the widget, and there is no $watch on that property that would update the widget with these options.
You can either create a DataSource explicitly and update that with:
$scope.contentTypeOptions.dataSource.data(contentType.value);
or you can use the attribute:
k-data-source="contentTypes"
which will create a $watch on $scope.contentTypes, so when you replace it, the widget will update as well.
Maybe this basic (although admittedly a bit messy) example will help you somewhat (I set up the 2nd dropdown in the same way you did; the "change" button updates the data source).
You will need to use the Angular Kendo bindings from Kendo labs.
Here is a an article with live demo and full source code:
http://blog.longle.io/2014/05/01/angularjs-kendo-ui-using-angular-kendo-with-asp-net-mvc-5-web-api-odata

JavaScript - ExtJs: export data with grid.getExcelXml when there are checkbox columns

I am using the framework JavaScript ExtJS version 3.2.1.
From my data grid, I want to export the data to Excel file.
Everything is working fine except when the grid has 1 or more checkbox columns: in this case, I have an error message:
p is null
If I hide the column that contains the checkboxes, I can export again.
Here is the code snapshot when I define the checkbox column (I built a framework because I use a lot of grid views):
this.column_model_sample_confirmed = new Ext.grid.CheckColumn({
id: cmnSampleConfirmed,
header: cmhSampleConfirmed,
dataIndex: cmnSampleConfirmed,
align: _KMX_EXT_CSS_LEFT_,
width: 30,
sortable: true,
hidden: false, // sample_confirmed
resizable: true,
filter: {},
editor: new Ext.form.TextField({}),
editable: false,
menuDisabled: false,
hideable: true,
fixe: true
});
Don't pay attention on variables/constantes I use because this code works fine.
Now, the code to implement my button "Export to Excel":
/**
* Export the grid in Excel
* FIXED: To get more compatibility with major browsers
* #param {Object} my_grid an editor grid panel instance
* #param {String} my_url_export the url to get the data exported (ajax call)
* #return NEANT
*/
var _kmx_fct_exportExcel = function(my_grid, my_url_export){
// we have to change the name of the worksheet to avoid some forbidden caracters such "(" or ":" to be able to open it with EXCEL
var tmp = my_grid.title;
try {
my_grid.setTitle('Sheet1');
var exportContent = my_grid.getExcelXml();
alert('THIS ALERT IS NOT DISPLAYED!');
if (!Ext.isIE6 && !Ext.isIE7 && !Ext.isSafari && !Ext.isSafari2 && !Ext.isSafari3) {
//alert('Ext.isIE6 = ' + Ext.isIE6 + "\n" + 'Ext.isIE7 = ' + Ext.isIE7 + "\n" + 'Ext.isSafari = ' + Ext.isSafari + "\n" + 'Ext.isSafari2 = ' + Ext.isSafari2 + "\n" + 'Ext.isSafari3 = ' + Ext.isSafari3);
document.location='data:application/vnd.ms-excel;base64,' + Base64.encode(exportContent);
}
else {
if (!Ext.fly('frmDummy')) {
var frm = document.createElement('form');
frm.id = 'frmDummy';
frm.name = id;
frm.className = 'x-hidden';
document.body.appendChild(frm);
}
Ext.Ajax.request({
url: my_url_export,
method : _self.kmx_form_method_post,
form:
Ext.fly('frmDummy'),
callback: function(o, s, r){
alert(r.responseText);
//alert('_kmx_fct_exportExcel callback function: OK');
},
isUpload: true,
params: { ex: exportContent }
});
}
}
catch (err) {
txt="There was an error on trying to export the data.\n\n";
txt+="Error message: " + err.message + "\n\n";
txt+="Click OK to continue.\n\n";
alert(txt);
}
my_grid.setTitle(tmp); // restore the grid panel name
};
This private function is defined inside a gridpanel "class" for people who used to use ExtJS 3. Again, don't pay on the variables/constantes I use.
The problem seems to be the line:
var exportContent = my_grid.getExcelXml();
Because the alert message right after this code:
alert('THIS ALERT IS NOT DISPLAYED!');
is not displayed.
So, my question is: how to export data into Excel format when you have a checkbox column?

Jira Gadget - Reload AJAX on Config Screen

Can't find the solution to this anywhere and I really hope it's possible.
I'm writing a Jira gadget and I have a configuration screen with 2 fields. One is the quickfind project picker; you type and it finds projects and you click the one you want.
The second field is Component. You can select the component of the project that you wish to filter by. However the components are different for each project so the Component field is populated with a AJAX call specified in the gadget in the "args" section of the "config" part.
Only problem is that this AJAX only gets called when the gadget is first loaded; i.e.: before a project is selected so the result is always "Select A Project".
I need a way to rerun this AJAX call on the event of the selected project being changed.
Is this possible? Or is there an alternative solution? I've tried timers to check for changes but there were a few problems there also; mainly that I couldn't access/alter the Component drop-box field. The gadget would just refuse to load.
Update: Below is the Javascript for the gadget. As you can see I added a refreshComponents() Javascript method which can retrieve the Components given a project ID however I have no way to attach this to the appropriate event. Also I can't seem to directly alter any components on the the page with the likes of jQuery or ordinary JS
<div id="chart_div" style="overflow: auto;"></div>
<script type="text/javascript">
var gadget = this;
google.load('visualization', '1.0', {'packages':['corechart']});
var oldProject = "initiated";
var globalGadget;
function timedComponentUpdate()
{
//alert(globalGadget.getPref("projectId"));
//setTimeout("timedComponentUpdate()",3000);
}
function refreshComponents(idString)
{
//refetch components
var url = "__ATLASSIAN_BASE_URL__/rest/severity-gadget/1.0/severity-issues/getComponents.json";
url += "?projectId=" + idString;
alert(url);
var xmlhttp;
if (window.XMLHttpRequest)
xmlhttp=new XMLHttpRequest();
else
{
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4)
{
//document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
alert(xmlhttp.responseText);
}
}
xmlhttp.open("GET",url,true);
xmlhttp.send();
}
function drawChart(args, bugtype, comp) {
//setup for whether were getting opened or closed
var axisTitle;
var compTitle;
var chart;
if(bugtype == "Bug")
axisTitle = "Bug";
else
axisTitle = "Issue";
if(comp == "All")
compTitle = "";
else
compTitle = " - Component: " + comp;
var wVar = gadgets.window.getViewportDimensions().width-20;
var hVar = wVar/3;
var hVar = hVar*2;
// Create the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Issues');
data.addColumn('number', 'Trivial');
data.addColumn('number', 'Minor');
data.addColumn('number', 'Major');
data.addColumn('number', 'Critical');
data.addColumn('number', 'Blocker');
AJS.$(args.weeks).each(function() {
data.addRow(["Week "+this.number,
parseInt(this.issues[0]),
parseInt(this.issues[1]),
parseInt(this.issues[2]),
parseInt(this.issues[3]),
parseInt(this.issues[4])
]);
});
var options = {'title':'Weekly '+axisTitle+' Backlog' + compTitle,
'width':wVar,
'height':hVar,
axisFontSize:4,
isStacked:true,
fontName: '"Arial"'
};
chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
var gadget = AJS.Gadget(
{
baseUrl: "__ATLASSIAN_BASE_URL__",
useOauth: "/rest/gadget/1.0/currentUser",
config: {
descriptor: function(args)
{
document.getElementById("chart_div").innerHTML = "";
var gadget = this;
var projectPicker = AJS.gadget.fields.projectOrFilterPicker(gadget, "projectId", args.projectOptions);
//bh
oldProject = this.getPref("projectId");
//refreshComponents(this.getPref("projectId"));
return {
theme : function()
{
if (gadgets.window.getViewportDimensions().width < 450)
{
return "gdt top-label";
}
else
{
return "gdt";
}
}(),
fields: [
AJS.gadget.fields.nowConfigured(),
projectPicker,
AJS.gadget.fields.days(gadget, "weeksPrevious"),
{
userpref: "issueType",
label: "Issue Type:",
description:"Choose which issue type you would like",
type: "select",
selected: this.getPref("issueType"),
options:[
{
label:"Any",
value:"Any"
},
{
label:"Bug",
value:"Bug"
}
]
},
{
userpref: "component",
label: "Component:",
description:"Choose which issue type you would like",
type: "select",
selected: this.getPref("component"),
options:args.components
}
]
};
},
args: function()
{
return [
{
key: "components",
ajaxOptions: function() {
var ajaxProject = this.getPref("projectId");
if(ajaxProject == "")
ajaxProject = "null";
return {
url: "/rest/severity-gadget/1.0/severity-issues/getComponents.json",
data:
{
projectId : ajaxProject
}
}
}
}
];
}()
},
view: {
onResizeReload: true,
onResizeAdjustHeight: true,
template: function(args) {
var gadget = this;
gadget.getView().empty();
drawChart(args.issueData, this.getPref("issueType"), this.getPref("component"));
gadget.resize();
},
args: [{
key: "issueData",
ajaxOptions: function() {
return {
url: "/rest/severity-gadget/1.0/severity-issues.json",
data: {
projectId : gadgets.util.unescapeString(this.getPref("projectId")),
weeksPrevious: this.getPref("weeksPrevious"),
issueType: this.getPref("issueType"),
component: this.getPref("component"),
backlog: true
}
};
}
}]
}
}
);
</script>
I think you'll need to turn your component field into a Callback Builder.
Inside the callback function you'll need to do a few things:
retrieve the options via an AJAX request
render the dropdown
attach an event handler to refresh the list when a particular event occurs
Your new component field might look something like this... I've assumed you've got jQuery available for brevity.
{
userpref: "component",
label: "Component",
id: "component_field_id"
description: "Choose which issue type you would like",
type: "callbackBuilder",
callback: function(parentDiv){
function renderOptions(options){
// Remove elements from the parentDiv and replace them
// with new elements based on the options param
// You can use gadget.getPref('component') to ensure you
// mark the right option as selected
}
function getOptions(){
$.ajax({
url: "__ATLASSIAN_BASE_URL__/rest/severity-gadget/1.0/severity-issues/getComponents.json",
data: {
// You might be able to get hold of the selected value
// from the gadget object instead of like this
projectId: $("#filter_projectOrFilterId_id").val()
}
}).done(renderOptions);
}
// Retrieve and render options on gadget load
getOptions();
// Retrieve and render options on an event
$(parentDiv).on("update-options", getOptions);
}
}
Additionally, you'll need to trigger an event when the project select field value changes. Somewhere else in your JS code (not inside the gadget definition) you'll need to put code like this, but you'll need to confirm the CSS selector for the project/filter selector:
$(document).on("change", "#filter_projectOrFilterId_id", function(){
$("#component_field_id").trigger("update-options");
});
I've not tested this but that's how I'd attempt to achieve what you're asking for.

Categories