diff --git a/packages/pluggableWidgets/datagrid-web/CHANGELOG.md b/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
index e766b412b9..a90fe19952 100644
--- a/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
+++ b/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Added
+
+- 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.
+
## [3.7.0] - 2025-11-11
### Added
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
index 5cc80ac52e..f242a39ef3 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
@@ -60,6 +60,14 @@ export function getProperties(values: DatagridPreviewProps, defaultProperties: P
if (column.minWidth !== "manual") {
hidePropertyIn(defaultProperties, values, "columns", index, "minWidthLimit");
}
+ // Hide exportNumberFormat if exportType is not 'number'
+ if (column.exportType !== "number") {
+ hidePropertyIn(defaultProperties, values, "columns", index, "exportNumberFormat" as any);
+ }
+ // Hide exportDateFormat if exportType is not 'date'
+ if (column.exportType !== "date") {
+ hidePropertyIn(defaultProperties, values, "columns", index, "exportDateFormat" as any);
+ }
});
if (values.pagination === "buttons") {
@@ -179,7 +187,10 @@ export const getPreview = (
minWidth: "auto",
minWidthLimit: 100,
allowEventPropagation: true,
- exportValue: ""
+ exportValue: "",
+ exportType: "default",
+ exportDateFormat: "",
+ exportNumberFormat: ""
}
];
const columns = rowLayout({
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
index d38bf5c50b..7d666397a3 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
@@ -38,7 +38,10 @@ const defaultColumn: ColumnsPreviewType = {
minWidth: "auto",
minWidthLimit: 100,
allowEventPropagation: true,
- exportValue: ""
+ exportValue: "",
+ exportDateFormat: "",
+ exportNumberFormat: "",
+ exportType: "default"
};
const initColumns: ColumnsPreviewType[] = [defaultColumn];
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
index 9ccf6b8d2a..1366fbe612 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
@@ -62,6 +62,26 @@
Export value
+
+ Export type
+
+
+ Default
+ Number
+ Date
+ Boolean
+
+
+
+ Export number format
+ 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/
+
+
+
+ Export date format
+ Excel date format for exported Date/DateTime values (e.g. "yyyy-mm-dd", "dd/mm/yyyy hh mm").
+
+
Caption
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts b/packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts
index 7898b5a76e..47bb3f51a4 100644
--- a/packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts
@@ -1,17 +1,29 @@
import { isAvailable } from "@mendix/widget-plugin-platform/framework/is-available";
import Big from "big.js";
-import { ListValue, ObjectItem, ValueStatus } from "mendix";
+import { DynamicValue, ListValue, ObjectItem, ValueStatus } from "mendix";
import { createNanoEvents, Emitter, Unsubscribe } from "nanoevents";
import { ColumnsType, ShowContentAsEnum } from "../../../typings/DatagridProps";
-type RowData = Array;
+/** Represents a single Excel cell (SheetJS compatible) */
+interface ExcelCell {
+ /** Cell type: 's' = string, 'n' = number, 'b' = boolean, 'd' = date */
+ t: "s" | "n" | "b" | "d";
+ /** Underlying value */
+ v: string | number | boolean | Date;
+ /** Optional Excel number/date format, e.g. "yyyy-mm-dd" or "$0.00" */
+ z?: string;
+ /** Optional pre-formatted display text */
+ w?: string;
+}
+
+type RowData = ExcelCell[];
type HeaderDefinition = {
name: string;
type: string;
};
-type ValueReader = (item: ObjectItem, props: ColumnsType) => string | boolean | number;
+type ValueReader = (item: ObjectItem, props: ColumnsType) => ExcelCell;
type ReadersByType = Record;
@@ -252,49 +264,119 @@ export class DSExportRequest {
const readers: ReadersByType = {
attribute(item, props) {
- if (props.attribute === undefined) {
- return "";
+ const data = props.attribute?.get(item);
+
+ if (data?.status !== "available") {
+ return makeEmptyCell();
}
- const data = props.attribute.get(item);
+ const value = data.value;
+ const format = getCellFormat({
+ exportType: props.exportType,
+ exportDateFormat: props.exportDateFormat,
+ exportNumberFormat: props.exportNumberFormat
+ });
- if (data.status !== "available") {
- return "";
+ if (value instanceof Date) {
+ return excelDate(format === undefined ? data.displayValue : value, format);
}
- if (typeof data.value === "boolean") {
- return data.value;
+ if (typeof value === "boolean") {
+ return excelBoolean(value);
}
- if (data.value instanceof Big) {
- return data.value.toNumber();
+ if (value instanceof Big || typeof value === "number") {
+ const num = value instanceof Big ? value.toNumber() : value;
+ return excelNumber(num, format);
}
- return data.displayValue;
+ return excelString(data.displayValue ?? "");
},
dynamicText(item, props) {
- if (props.dynamicText === undefined) {
- return "";
- }
-
- const data = props.dynamicText.get(item);
+ const data = props.dynamicText?.get(item);
- switch (data.status) {
+ switch (data?.status) {
case "available":
- return data.value;
+ const format = getCellFormat({
+ exportType: props.exportType,
+ exportDateFormat: props.exportDateFormat,
+ exportNumberFormat: props.exportNumberFormat
+ });
+
+ return excelString(data.value ?? "", format);
case "unavailable":
- return "n/a";
+ return excelString("n/a");
default:
- return "";
+ return makeEmptyCell();
}
},
customContent(item, props) {
- return props.exportValue?.get(item).value ?? "";
+ const value = props.exportValue?.get(item).value ?? "";
+ const format = getCellFormat({
+ exportType: props.exportType,
+ exportDateFormat: props.exportDateFormat,
+ exportNumberFormat: props.exportNumberFormat
+ });
+
+ return excelString(value, format);
}
};
+function makeEmptyCell(): ExcelCell {
+ return { t: "s", v: "" };
+}
+
+function excelNumber(value: number, format?: string): ExcelCell {
+ return {
+ t: "n",
+ v: value,
+ z: format
+ };
+}
+
+function excelString(value: string, format?: string): ExcelCell {
+ return {
+ t: "s",
+ v: value,
+ z: format ?? undefined
+ };
+}
+
+function excelDate(value: string | Date, format?: string): ExcelCell {
+ return {
+ t: format === undefined ? "s" : "d",
+ v: value,
+ z: format
+ };
+}
+
+function excelBoolean(value: boolean): ExcelCell {
+ return {
+ t: "b",
+ v: value,
+ w: value ? "TRUE" : "FALSE"
+ };
+}
+
+interface DataExportProps {
+ exportType: "default" | "number" | "date" | "boolean";
+ exportDateFormat?: DynamicValue;
+ exportNumberFormat?: DynamicValue;
+}
+
+function getCellFormat({ exportType, exportDateFormat, exportNumberFormat }: DataExportProps): string | undefined {
+ switch (exportType) {
+ case "date":
+ return exportDateFormat?.status === "available" ? exportDateFormat.value : undefined;
+ case "number":
+ return exportNumberFormat?.status === "available" ? exportNumberFormat.value : undefined;
+ default:
+ return undefined;
+ }
+}
+
function createRowReader(columns: ColumnsType[]): RowReader {
return item =>
columns.map(col => {
diff --git a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx
index bd16125514..9fe9f153d5 100644
--- a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx
@@ -28,7 +28,8 @@ export const column = (header = "Test", patch?: (col: ColumnsType) => void): Col
visible: dynamicValue(true),
minWidth: "auto",
minWidthLimit: 100,
- allowEventPropagation: true
+ allowEventPropagation: true,
+ exportType: "default"
};
if (patch) {
diff --git a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
index 460fb68e33..056c487def 100644
--- a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
@@ -9,6 +9,8 @@ import { Big } from "big.js";
export type ShowContentAsEnum = "attribute" | "dynamicText" | "customContent";
+export type ExportTypeEnum = "default" | "number" | "date" | "boolean";
+
export type HidableEnum = "yes" | "hidden" | "no";
export type WidthEnum = "autoFill" | "autoFit" | "manual";
@@ -23,6 +25,9 @@ export interface ColumnsType {
content?: ListWidgetValue;
dynamicText?: ListExpressionValue;
exportValue?: ListExpressionValue;
+ exportType: ExportTypeEnum;
+ exportNumberFormat?: DynamicValue;
+ exportDateFormat?: DynamicValue;
header?: DynamicValue;
tooltip?: ListExpressionValue;
filter?: ReactNode;
@@ -67,6 +72,9 @@ export interface ColumnsPreviewType {
content: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
dynamicText: string;
exportValue: string;
+ exportType: ExportTypeEnum;
+ exportNumberFormat: string;
+ exportDateFormat: string;
header: string;
tooltip: string;
filter: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };