Skip to content

Commit 8b95b46

Browse files
[WC-2826]: Export to excel types and formats (#1941)
2 parents d216baf + 7c2e38e commit 8b95b46

File tree

7 files changed

+155
-26
lines changed

7 files changed

+155
-26
lines changed

packages/pluggableWidgets/datagrid-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- We added a new property for export to excel. The new property allows to set the cell export type and also the format for type number and date.
12+
913
## [3.7.0] - 2025-11-11
1014

1115
### Added

packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ export function getProperties(values: DatagridPreviewProps, defaultProperties: P
6060
if (column.minWidth !== "manual") {
6161
hidePropertyIn(defaultProperties, values, "columns", index, "minWidthLimit");
6262
}
63+
// Hide exportNumberFormat if exportType is not 'number'
64+
if (column.exportType !== "number") {
65+
hidePropertyIn(defaultProperties, values, "columns", index, "exportNumberFormat" as any);
66+
}
67+
// Hide exportDateFormat if exportType is not 'date'
68+
if (column.exportType !== "date") {
69+
hidePropertyIn(defaultProperties, values, "columns", index, "exportDateFormat" as any);
70+
}
6371
});
6472

6573
if (values.pagination === "buttons") {
@@ -179,7 +187,10 @@ export const getPreview = (
179187
minWidth: "auto",
180188
minWidthLimit: 100,
181189
allowEventPropagation: true,
182-
exportValue: ""
190+
exportValue: "",
191+
exportType: "default",
192+
exportDateFormat: "",
193+
exportNumberFormat: ""
183194
}
184195
];
185196
const columns = rowLayout({

packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ const defaultColumn: ColumnsPreviewType = {
3838
minWidth: "auto",
3939
minWidthLimit: 100,
4040
allowEventPropagation: true,
41-
exportValue: ""
41+
exportValue: "",
42+
exportDateFormat: "",
43+
exportNumberFormat: "",
44+
exportType: "default"
4245
};
4346

4447
const initColumns: ColumnsPreviewType[] = [defaultColumn];

packages/pluggableWidgets/datagrid-web/src/Datagrid.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@
6262
<caption>Export value</caption>
6363
<description />
6464
</property>
65+
<property key="exportType" type="enumeration" defaultValue="default">
66+
<caption>Export type</caption>
67+
<description />
68+
<enumerationValues>
69+
<enumerationValue key="default">Default</enumerationValue>
70+
<enumerationValue key="number">Number</enumerationValue>
71+
<enumerationValue key="date">Date</enumerationValue>
72+
<enumerationValue key="boolean">Boolean</enumerationValue>
73+
</enumerationValues>
74+
</property>
75+
<property key="exportNumberFormat" type="expression" required="false">
76+
<caption>Export number format</caption>
77+
<description>Optional Excel number format for exported numeric values (e.g. "#,##0.00", "$0.00", "0.00%"). See all formats https://docs.sheetjs.com/docs/csf/features/nf/</description>
78+
<returnType type="String" />
79+
</property>
80+
<property key="exportDateFormat" type="expression" required="false">
81+
<caption>Export date format</caption>
82+
<description>Excel date format for exported Date/DateTime values (e.g. "yyyy-mm-dd", "dd/mm/yyyy hh mm").</description>
83+
<returnType type="String" />
84+
</property>
6585
<property key="header" type="textTemplate" required="false">
6686
<caption>Caption</caption>
6787
<description />

packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts

Lines changed: 105 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
import { isAvailable } from "@mendix/widget-plugin-platform/framework/is-available";
22
import Big from "big.js";
3-
import { ListValue, ObjectItem, ValueStatus } from "mendix";
3+
import { DynamicValue, ListValue, ObjectItem, ValueStatus } from "mendix";
44
import { createNanoEvents, Emitter, Unsubscribe } from "nanoevents";
55
import { ColumnsType, ShowContentAsEnum } from "../../../typings/DatagridProps";
66

7-
type RowData = Array<string | number | boolean>;
7+
/** Represents a single Excel cell (SheetJS compatible) */
8+
interface ExcelCell {
9+
/** Cell type: 's' = string, 'n' = number, 'b' = boolean, 'd' = date */
10+
t: "s" | "n" | "b" | "d";
11+
/** Underlying value */
12+
v: string | number | boolean | Date;
13+
/** Optional Excel number/date format, e.g. "yyyy-mm-dd" or "$0.00" */
14+
z?: string;
15+
/** Optional pre-formatted display text */
16+
w?: string;
17+
}
18+
19+
type RowData = ExcelCell[];
820

921
type HeaderDefinition = {
1022
name: string;
1123
type: string;
1224
};
1325

14-
type ValueReader = (item: ObjectItem, props: ColumnsType) => string | boolean | number;
26+
type ValueReader = (item: ObjectItem, props: ColumnsType) => ExcelCell;
1527

1628
type ReadersByType = Record<ShowContentAsEnum, ValueReader>;
1729

@@ -252,49 +264,119 @@ export class DSExportRequest {
252264

253265
const readers: ReadersByType = {
254266
attribute(item, props) {
255-
if (props.attribute === undefined) {
256-
return "";
267+
const data = props.attribute?.get(item);
268+
269+
if (data?.status !== "available") {
270+
return makeEmptyCell();
257271
}
258272

259-
const data = props.attribute.get(item);
273+
const value = data.value;
274+
const format = getCellFormat({
275+
exportType: props.exportType,
276+
exportDateFormat: props.exportDateFormat,
277+
exportNumberFormat: props.exportNumberFormat
278+
});
260279

261-
if (data.status !== "available") {
262-
return "";
280+
if (value instanceof Date) {
281+
return excelDate(format === undefined ? data.displayValue : value, format);
263282
}
264283

265-
if (typeof data.value === "boolean") {
266-
return data.value;
284+
if (typeof value === "boolean") {
285+
return excelBoolean(value);
267286
}
268287

269-
if (data.value instanceof Big) {
270-
return data.value.toNumber();
288+
if (value instanceof Big || typeof value === "number") {
289+
const num = value instanceof Big ? value.toNumber() : value;
290+
return excelNumber(num, format);
271291
}
272292

273-
return data.displayValue;
293+
return excelString(data.displayValue ?? "");
274294
},
275295

276296
dynamicText(item, props) {
277-
if (props.dynamicText === undefined) {
278-
return "";
279-
}
280-
281-
const data = props.dynamicText.get(item);
297+
const data = props.dynamicText?.get(item);
282298

283-
switch (data.status) {
299+
switch (data?.status) {
284300
case "available":
285-
return data.value;
301+
const format = getCellFormat({
302+
exportType: props.exportType,
303+
exportDateFormat: props.exportDateFormat,
304+
exportNumberFormat: props.exportNumberFormat
305+
});
306+
307+
return excelString(data.value ?? "", format);
286308
case "unavailable":
287-
return "n/a";
309+
return excelString("n/a");
288310
default:
289-
return "";
311+
return makeEmptyCell();
290312
}
291313
},
292314

293315
customContent(item, props) {
294-
return props.exportValue?.get(item).value ?? "";
316+
const value = props.exportValue?.get(item).value ?? "";
317+
const format = getCellFormat({
318+
exportType: props.exportType,
319+
exportDateFormat: props.exportDateFormat,
320+
exportNumberFormat: props.exportNumberFormat
321+
});
322+
323+
return excelString(value, format);
295324
}
296325
};
297326

327+
function makeEmptyCell(): ExcelCell {
328+
return { t: "s", v: "" };
329+
}
330+
331+
function excelNumber(value: number, format?: string): ExcelCell {
332+
return {
333+
t: "n",
334+
v: value,
335+
z: format
336+
};
337+
}
338+
339+
function excelString(value: string, format?: string): ExcelCell {
340+
return {
341+
t: "s",
342+
v: value,
343+
z: format ?? undefined
344+
};
345+
}
346+
347+
function excelDate(value: string | Date, format?: string): ExcelCell {
348+
return {
349+
t: format === undefined ? "s" : "d",
350+
v: value,
351+
z: format
352+
};
353+
}
354+
355+
function excelBoolean(value: boolean): ExcelCell {
356+
return {
357+
t: "b",
358+
v: value,
359+
w: value ? "TRUE" : "FALSE"
360+
};
361+
}
362+
363+
interface DataExportProps {
364+
exportType: "default" | "number" | "date" | "boolean";
365+
exportDateFormat?: DynamicValue<string>;
366+
exportNumberFormat?: DynamicValue<string>;
367+
}
368+
369+
function getCellFormat({ exportType, exportDateFormat, exportNumberFormat }: DataExportProps): string | undefined {
370+
switch (exportType) {
371+
case "date":
372+
return exportDateFormat?.status === "available" ? exportDateFormat.value : undefined;
373+
case "number":
374+
return exportNumberFormat?.status === "available" ? exportNumberFormat.value : undefined;
375+
default:
376+
return undefined;
377+
}
378+
}
379+
298380
function createRowReader(columns: ColumnsType[]): RowReader {
299381
return item =>
300382
columns.map(col => {

packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export const column = (header = "Test", patch?: (col: ColumnsType) => void): Col
2828
visible: dynamicValue(true),
2929
minWidth: "auto",
3030
minWidthLimit: 100,
31-
allowEventPropagation: true
31+
allowEventPropagation: true,
32+
exportType: "default"
3233
};
3334

3435
if (patch) {

packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { Big } from "big.js";
99

1010
export type ShowContentAsEnum = "attribute" | "dynamicText" | "customContent";
1111

12+
export type ExportTypeEnum = "default" | "number" | "date" | "boolean";
13+
1214
export type HidableEnum = "yes" | "hidden" | "no";
1315

1416
export type WidthEnum = "autoFill" | "autoFit" | "manual";
@@ -23,6 +25,9 @@ export interface ColumnsType {
2325
content?: ListWidgetValue;
2426
dynamicText?: ListExpressionValue<string>;
2527
exportValue?: ListExpressionValue<string>;
28+
exportType: ExportTypeEnum;
29+
exportNumberFormat?: DynamicValue<string>;
30+
exportDateFormat?: DynamicValue<string>;
2631
header?: DynamicValue<string>;
2732
tooltip?: ListExpressionValue<string>;
2833
filter?: ReactNode;
@@ -67,6 +72,9 @@ export interface ColumnsPreviewType {
6772
content: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
6873
dynamicText: string;
6974
exportValue: string;
75+
exportType: ExportTypeEnum;
76+
exportNumberFormat: string;
77+
exportDateFormat: string;
7078
header: string;
7179
tooltip: string;
7280
filter: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };

0 commit comments

Comments
 (0)