Skip to content

Commit

Permalink
feat(export): add missing valueParserCallback dataContext & new demo (
Browse files Browse the repository at this point in the history
#1543)

* feat(export): add missing `valueParserCallback` dataContext

* fix(excel): the `excelExportOptions.style` should work on regular cells

* fix(export): Excel export of Group should work with Excel metadata
  • Loading branch information
ghiscoding committed May 25, 2024
1 parent 021738d commit 884b6e0
Show file tree
Hide file tree
Showing 12 changed files with 824 additions and 46 deletions.
93 changes: 79 additions & 14 deletions docs/grid-functionalities/Export-to-Excel.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,29 @@ Below is a preview of the previous customizations shown above
![image](https://user-images.githubusercontent.com/643976/208590003-b637dcda-5164-42cc-bfad-e921a22c1837.png)
### Cell Format Auto-Detect Disable
##### requires `v3.2.0` or higher
The system will auto-detect the Excel format to use for Date and Number field types, if for some reason you wish to disable it then you provide the excel export options below
```ts
// via column
this.columnDefinitions = [
{
id: 'cost', name: 'Cost', field: 'cost', type: FieldType.number
excelExportOptions: { autoDetectCellFormat: false }
}
];

// OR via grid options (column option always win)
this.gridOptions = {
// ...
excelExportOptions: { autoDetectCellFormat: false }
};
```
### Cell Value Parser
This is not recommended but if you have no other ways, you can also provide a cell value parser function callback to override what the system detected.
This is not recommended but if you have no other ways, you can also provide a `valueParserCallback` callback function to override what the system detected. This callback function is available for both `excelExportOptions` (regular cells) and `groupTotalsExcelExportOptions` (grouping total cells)
```ts
this.columnDefinitions = [
Expand All @@ -341,27 +362,71 @@ this.columnDefinitions = [
const groupType = 'sum';
const fieldName = columnDef.field;
return totals[groupType][fieldName];
},
},
}
}
];
```
### Cell Format Auto-Detect Disable
##### requires `v3.2.0` or higher
The system will auto-detect the Excel format to use for Date and Number field types, if for some reason you wish to disable it then you provide the excel export options below
By using `valueParserCallback`, there a lot of extra customizations that you can do with it. You could even use Excel Formula to do calculation even based on other fields on your item data context, the code below is calculating Sub-Total and Total. It's a lot of code but it shows the real power customization that exist. If you want to go with even more customization, the new [Example 23](https://ghiscoding.github.io/slickgrid-universal/#/example23) even shows you how to summarize Groups with Excel Formulas (but be warned, it does take a fair amount of code and logic to implement by yourself)
```ts
// via column
this.columnDefinitions = [
{
id: 'cost', name: 'Cost', field: 'cost', type: FieldType.number
excelExportOptions: { autoDetectCellFormat: false }
id: 'cost', name: 'Cost', field: 'cost', width: 80,
type: FieldType.number,

// use Formatters in the UI
formatter: Formatters.dollar,
groupTotalsFormatter: GroupTotalFormatters.sumTotalsDollar,

// but use the parser callback to customize our Excel export by using Excel Formulas
excelExportOptions: {
// you can also style the Excel cells (note again that HTML color "#" is escaped as "FF" prefix)
style: {
font: { bold: true, color: 'FF215073' },
format: '$0.00', // currency dollar format
},
width: 12,
valueParserCallback: (_data, columnDef: Column, excelFormatterId: number | undefined, _stylesheet, _gridOptions, dataRowIdx: number, dataContext: GroceryItem) => {
// assuming that we want to calculate: (Price * Qty) => Sub-Total
const colOffset = !this.isDataGrouped ? 1 : 0; // col offset of 1x because we skipped 1st column OR 0 offset if we use a Group because the Group column replaces the skip
const rowOffset = 3; // row offset of 3x because: 1x Title, 1x Headers and Excel row starts at 1 => 3
const priceIdx = this.sgb.slickGrid?.getColumnIndex('price') || 0;
const qtyIdx = this.sgb.slickGrid?.getColumnIndex('qty') || 0;
const taxesIdx = this.sgb.slickGrid?.getColumnIndex('taxes') || 0;

// the code below calculates Excel column position dynamically, technically Price is at "B" and Qty is "C"
// Note: if you know the Excel column (A, B, C, ...) then portion of the code below could be skipped (the code below is fully dynamic)
const excelPriceCol = `${String.fromCharCode('A'.charCodeAt(0) + priceIdx - colOffset)}${dataRowIdx + rowOffset}`;
const excelQtyCol = `${String.fromCharCode('A'.charCodeAt(0) + qtyIdx - colOffset)}${dataRowIdx + rowOffset}`;
const excelTaxesCol = `${String.fromCharCode('A'.charCodeAt(0) + taxesIdx - colOffset)}${dataRowIdx + rowOffset}`;

// `value` is our Excel cells to calculat (e.g.: "B4*C4")
// metadata `type` has to be set to "formula" and the `style` is what we defined in `excelExportOptions.style` which is `excelFormatterId` in the callback arg

let excelVal = '';
switch (columnDef.id) {
case 'subTotal':
excelVal = `${excelPriceCol}*${excelQtyCol}`; // like "C4*D4"
break;
case 'taxes':
excelVal = (dataContext.taxable)
? `${excelPriceCol}*${excelQtyCol}*${this.taxRate / 100}`
: '';
break;
case 'total':
excelVal = `(${excelPriceCol}*${excelQtyCol})+${excelTaxesCol}`;
break;
}

// use "formula" as "metadata", the "style" is a formatter id that comes from any custom "style" defined outside of our callback
return { value: excelVal, metadata: { type: 'formula', style: excelFormatterId } };
}
},
}
];
```
// OR via grid options (column option always win)
this.gridOptions = {
// ...
excelExportOptions: { autoDetectCellFormat: false }
};
```
#### use Excel Formulas to calculate Totals by using other dataContext props
![image](https://github.com/ghiscoding/slickgrid-universal/assets/643976/871c2d84-33b2-41af-ac55-1f7eadb79cb8)
2 changes: 2 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Example19 from './examples/example19';
import Example20 from './examples/example20';
import Example21 from './examples/example21';
import Example22 from './examples/example22';
import Example23 from './examples/example23';

export class AppRouting {
constructor(private config: RouterConfig) {
Expand Down Expand Up @@ -51,6 +52,7 @@ export class AppRouting {
{ route: 'example20', name: 'example20', view: './examples/example20.html', viewModel: Example20, title: 'Example20', },
{ route: 'example21', name: 'example21', view: './examples/example21.html', viewModel: Example21, title: 'Example21', },
{ route: 'example22', name: 'example22', view: './examples/example22.html', viewModel: Example22, title: 'Example22', },
{ route: 'example23', name: 'example23', view: './examples/example23.html', viewModel: Example23, title: 'Example23', },
{ route: '', redirect: 'example01' },
{ route: '**', redirect: 'example01' }
];
Expand Down
3 changes: 3 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ <h4 class="is-size-4 has-text-white">Slickgrid-Universal</h4>
<a class="navbar-item" onclick.delegate="loadRoute('example22')">
Example22 - Row Based Editing
</a>
<a class="navbar-item" onclick.delegate="loadRoute('example23')">
Example23 - Excel Export Formulas
</a>
</div>
</div>
</div>
Expand Down
42 changes: 42 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example23.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<h3 class="title is-3">
Example 23 - Excel Export Formulas
<div class="subtitle code-link">
<span class="is-size-6">see</span>
<a class="is-size-5" target="_blank"
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/vite-demo-vanilla-bundle/src/examples/example23.ts">
<span class="mdi mdi-link-variant"></span> code
</a>
</div>
</h3>

<h5 class="title is-5 mb-3">
Calculate Totals via Formatters in the UI, but use Excel Formula when exporting via <code>excelExportOptions.valueParserCallback</code>
</h5>
<h6 class="is-6 mb-4">
When Grouped we will also calculate the Group Totals in the UI via Group Formatter and we again use Excel Formula to calculate the Group Totals (sum) dynamically. For Grouping we need to use <code>groupTotalsExcelExportOptions.valueParserCallback</code> instead.
</h6>

<section class="row mb-2">
<div class="mb-1">
<button class="button is-small" data-test="export-excel-btn" onclick.delegate="exportToExcel()">
<span class="mdi mdi-file-excel-outline text-color-success"></span>
<span>Export to Excel</span>
</button>
<span>
<button class="button is-small" onclick.delegate="groupByTaxable()" data-test="group-by-btn">
<span>Group by Taxable</span>
</button>
</span>
<span class="ml-6 text-bold">
Tax Rate:
<input type="number" value.bind="taxRate" class="is-narrow input is-small" step="0.25" data-test="taxrate" />
<button class="button is-small" onclick.delegate="updateTaxRate()" data-test="update-btn">
Update
</button>
</span>

</div>
</section>

<div class="grid23">
</div>
19 changes: 19 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example23.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.grid23 {
.slick-row:not(.slick-group) >.cell-unselectable {
background: #ececec !important;
font-weight: bold;
}

.text-sub-total {
font-style: italic;
color: rgb(33, 80, 115);
}
.text-taxes {
font-style: italic;
color: rgb(198, 89, 17);
}
.text-total {
font-weight: bold;
color: rgb(0, 90, 158);
}
}
Loading

0 comments on commit 884b6e0

Please sign in to comment.