diff --git a/packages/pluggableWidgets/datagrid-web/CHANGELOG.md b/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
index fbe7db8aa7..676ac9f7ab 100644
--- a/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
+++ b/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
@@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- We fixed an issue where missing consistency checks for the captions were causing runtime errors instead of in Studio Pro
+- 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.6.1] - 2025-10-14
### Fixed
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
index abd7f1cfb2..100229c5e9 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
@@ -65,6 +65,14 @@ export function getProperties(
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.advanced && platform === "web") {
hideNestedPropertiesIn(defaultProperties, values, "columns", index, [
"columnClass",
@@ -214,7 +222,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 6086784e80..b41aa58393 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx
@@ -55,7 +55,10 @@ const initColumns: ColumnsPreviewType[] = [
minWidth: "auto",
minWidthLimit: 100,
allowEventPropagation: true,
- exportValue: ""
+ exportValue: "",
+ exportDateFormat: "",
+ exportNumberFormat: "",
+ exportType: "default"
}
];
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
index afb1d00ec4..1a404dd6f9 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
@@ -127,6 +127,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 513d2d6282..2682962226 100644
--- a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
@@ -17,6 +17,8 @@ export type LoadingTypeEnum = "spinner" | "skeleton";
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";
@@ -31,6 +33,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 }> };