More flexibel Excel export
More flexibel Excel export
This is to share my latest work on exporting data to Excel including formatting and dynamic column ordering and selection. While I already had column formatting (numbers, percentages, custom column width) this was static and didn't work with column reordering and user induced column visibility changes. In addition: When making changes to the underlying Data Tables I always forgot to adjust the hard-coded export formatting which led to errors etc..
My first change was to have a split button for the Excel export:
- Full export
- Export only selected columns (set with colVis - Button)
- Export only main view (based on responsive-extension results)
I replaced the hard-coded column numbers for formatting with HTML-classes and data-* attributes. This makes sure that nothing breaks when new columns are inserted or columns are moved around.
I use the following HTML classes to indicate that special formatting needs to be done for the Excel export:
- xlsTwoDecPlaces (numbers with two decimal places, e.g. $ amounts)
- xlsFourDecPlaces (numbers with four decimal places, e.g. percentages; these also require conversion from percentage to fraction: 50% needs to be passed to Excel as 0.5, in case you want a formatted field and not a "dumb" string)
- xlsRightAligned (fields that should be right aligned in Excel not the usual left alignment)
- noExport (columns that should never be exported because they are simply irrelevant, e.g. columns with document links)
- noExportFull (columns that should not be exported only when a full export is triggered)
- data-length attribute (Excel column width in case a special column width is required)
Since I use colReorder and stateSaving (both are optional and can be set by the user) I need to capture the "initial" columns based on the HTML BEFORE state loading and colReorder make any changes.
This is how I do this. The arrays are global variables to have them available later on.
table
.on( 'preInit', function (e, settings) {
//save the original formatting values BEFORE applying saved states and
//before initialization on "preInit"
xlsTwoDecPlacesColsNumbers = [];
xlsFourDecPlacesColsNumbers = [];
xlsRightAlignedColsNumbers = [];
xlsLengthSpecifiedColsNumbers = [];
xlsLengthIfSpecifiedColsNumbers = [];
//all columns including class never etc.
table.columns().every( function() {
var header = this.header();
if ( $(header).hasClass('xlsTwoDecPlaces') ) {
xlsTwoDecPlacesColsNumbers.push ( true );
} else {
xlsTwoDecPlacesColsNumbers.push ( false );
}
if ( $(header).hasClass('xlsFourDecPlaces') ) {
xlsFourDecPlacesColsNumbers.push ( true );
} else {
xlsFourDecPlacesColsNumbers.push ( false );
}
if ( $(header).hasClass('xlsRightAligned') ) {
xlsRightAlignedColsNumbers.push ( true );
} else {
xlsRightAlignedColsNumbers.push ( false );
}
var length = $(header).attr('data-length');
if ( typeof length !== "undefined" && length !== false ) {
xlsLengthSpecifiedColsNumbers.push ( true );
xlsLengthIfSpecifiedColsNumbers.push ( length );
} else {
xlsLengthSpecifiedColsNumbers.push ( false );
xlsLengthIfSpecifiedColsNumbers.push ( "0" );
}
});
})
I also need to capture which columns are in the main view currently. I capture that in a global variable as well.
table
.on( 'responsive-resize', function (e, dt, columns) {
if ( ! $.isArray(columns) ) {
return;
}
if ( columns.length <= 0 ) {
return;
}
//this is the column selection for the excel main view export
columnsVisibleMain = columns;
});
The split button:
buttons: [
{extend: "excelCashFlow", split: ["excelCashFlowVisible", "excelCashFlowNoChildRows"] },
{extend: "colvis", columns: ':not(.never)' }
],
Replies
The underlying buttons for the split button (deleted quite a bit of code, but kept the most relevant parts. and yes, I know this could be coded in a more concise way ... but I don't really care if it works ):
I do the conversion from numbers to Excel letters in a very simple way using the array below avoiding any kind of calculation. I am not very good at math ...
The "exportOptions" method is executed prior to the "customize" method. Depending on the button one of these lines is being executed:
This function creates arrays (globals) that are used in "exportOptions" and "customize" as well. The challenge is to apply the right formatting options even for partly or reordered views. Hence it is important to know the original position of a column. A transposition needs to be done. Another challenge is that "exportOptions" is called for each table column, while "customize" is only called once. Confusing.
So this is the function:
This is what the split button looks like:
Hope that helps a little in case you have similar challenges! In case you export everything as a string this is not required of course! But my users want to be able to use the Excel for immediate calculation and don't want to reformat everything manually. I wish my bank was able to do that too!
Roland
How good is that! Amazing - thank you for sharing this with us.
The Excel export, and export in general, is something I'm planning on working on intensively, probably in the second part of the year. It is way to hard to customise it at the moment.
I'm sure others will find this useful!
Allan