Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -209,7 +217,10 @@ export const getPreview = (
minWidth: "auto",
minWidthLimit: 100,
allowEventPropagation: true,
exportValue: ""
exportValue: "",
exportType: "text",
exportDateFormat: "",
exportNumberFormat: ""
}
];
const columns = rowLayout({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ const initColumns: ColumnsPreviewType[] = [
minWidth: "auto",
minWidthLimit: 100,
allowEventPropagation: true,
exportValue: ""
exportValue: "",
exportDateFormat: "",
exportNumberFormat: "",
exportType: "text"
}
];

Expand Down
20 changes: 20 additions & 0 deletions packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,26 @@
<caption>Export value</caption>
<description />
</property>
<property key="exportType" type="enumeration" defaultValue="text">
<caption>Export type</caption>
<description />
<enumerationValues>
<enumerationValue key="text">Text</enumerationValue>
<enumerationValue key="number">Number</enumerationValue>
<enumerationValue key="date">Date</enumerationValue>
<enumerationValue key="boolean">Boolean</enumerationValue>
</enumerationValues>
</property>
<property key="exportNumberFormat" type="expression" required="false">
<caption>Export number format</caption>
<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>
<returnType type="String" />
</property>
<property key="exportDateFormat" type="expression" required="false">
<caption>Export date format</caption>
<description>Excel date format for exported Date/DateTime values (e.g. "yyyy-mm-dd", "dd/mm/yyyy hh mm").</description>
<returnType type="String" />
</property>
<property key="header" type="textTemplate" required="false">
<caption>Caption</caption>
<description />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string | number | boolean>;
/** 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<ShowContentAsEnum, ValueReader>;

Expand Down Expand Up @@ -253,48 +265,107 @@ export class DSExportRequest {
const readers: ReadersByType = {
attribute(item, props) {
if (props.attribute === undefined) {
return "";
return makeEmptyCell();
}

const data = props.attribute.get(item);

if (data.status !== "available") {
return "";
return makeEmptyCell();
}

const value = data.value;
const format = getCellFormat({
exportType: props.exportType,
exportDateFormat: props.exportDateFormat,
exportNumberFormat: props.exportNumberFormat
});

if (value instanceof Date) {
return {
t: format === undefined ? "s" : "d",
v: format === undefined ? data.displayValue : value,
z: format
};
}

if (typeof data.value === "boolean") {
return data.value;
if (typeof value === "boolean") {
return {
t: "b",
v: value,
w: value ? "TRUE" : "FALSE"
};
}

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 {
t: "n",
v: num,
z: format
};
}

return data.displayValue;
return {
t: "s",
v: data.displayValue ?? ""
};
},

dynamicText(item, props) {
if (props.dynamicText === undefined) {
return "";
return makeEmptyCell();
}

const data = props.dynamicText.get(item);

switch (data.status) {
case "available":
return data.value;
const format = getCellFormat({
exportType: props.exportType,
exportDateFormat: props.exportDateFormat,
exportNumberFormat: props.exportNumberFormat
});
return { t: "s", v: data.value ?? "", z: format };
case "unavailable":
return "n/a";
return { t: "s", v: "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 { t: "s", v: value, z: format };
}
};

function makeEmptyCell(): ExcelCell {
return { t: "s", v: "" };
}

interface DataExportProps {
exportType: "text" | "number" | "date" | "boolean";
exportDateFormat?: DynamicValue<string>;
exportNumberFormat?: DynamicValue<string>;
}

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 => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: "text"
};

if (patch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export type LoadingTypeEnum = "spinner" | "skeleton";

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

export type ExportTypeEnum = "text" | "number" | "date" | "boolean";

export type HidableEnum = "yes" | "hidden" | "no";

export type WidthEnum = "autoFill" | "autoFit" | "manual";
Expand All @@ -29,6 +31,9 @@ export interface ColumnsType {
content?: ListWidgetValue;
dynamicText?: ListExpressionValue<string>;
exportValue?: ListExpressionValue<string>;
exportType: ExportTypeEnum;
exportNumberFormat?: DynamicValue<string>;
exportDateFormat?: DynamicValue<string>;
header?: DynamicValue<string>;
tooltip?: ListExpressionValue<string>;
filter?: ReactNode;
Expand Down Expand Up @@ -65,6 +70,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 }> };
Expand Down
Loading