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
10 changes: 10 additions & 0 deletions packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed

- We fixed an issue where custom content columns ignored the export type setting, causing numbers and dates to always export as text in Excel.

- We fixed an issue where exported date values included a hidden time component even when the format specified date-only parts.

- We fixed an issue where boolean values exported as TRUE/FALSE instead of Yes/No to match the display in the grid.

- We fixed an issue where numbers with more than 15 significant digits lost precision during Excel export. Such values are now exported as text to preserve all digits.

## [3.9.0] - 2026-03-23

### Changed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,8 @@
import { isAvailable } from "@mendix/widget-plugin-platform/framework/is-available";
import Big from "big.js";
import { DynamicValue, ListValue, ObjectItem, ValueStatus } from "mendix";
import { ListValue, ObjectItem, ValueStatus } from "mendix";
import { createNanoEvents, Emitter, Unsubscribe } from "nanoevents";
import { ColumnsType, ShowContentAsEnum } from "../../../typings/DatagridProps";

/** 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) => ExcelCell;

type ReadersByType = Record<ShowContentAsEnum, ValueReader>;

type RowReader = (item: ObjectItem) => RowData;
import { ColumnsType } from "../../../typings/DatagridProps";
import { HeaderDefinition, RowData, readChunk } from "./cell-readers";

type ColumnReader = (props: ColumnsType) => HeaderDefinition;

Expand Down Expand Up @@ -262,132 +237,6 @@ export class DSExportRequest {
}
}

const readers: ReadersByType = {
attribute(item, props) {
const data = props.attribute?.get(item);

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

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

if (value instanceof Date) {
return excelDate(format === undefined ? data.displayValue : value, format);
}

if (typeof value === "boolean") {
return excelBoolean(value);
}

if (value instanceof Big || typeof value === "number") {
const num = value instanceof Big ? value.toNumber() : value;
return excelNumber(num, format);
}

return excelString(data.displayValue ?? "");
},

dynamicText(item, props) {
const data = props.dynamicText?.get(item);

switch (data?.status) {
case "available":
const format = getCellFormat({
exportType: props.exportType,
exportDateFormat: props.exportDateFormat,
exportNumberFormat: props.exportNumberFormat
});

return excelString(data.value ?? "", format);
case "unavailable":
return excelString("n/a");
default:
return makeEmptyCell();
}
},

customContent(item, props) {
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<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 => {
return readers[col.showContentAs](item, col);
});
}

function readChunk(data: ObjectItem[], columns: ColumnsType[]): RowData[] {
return data.map(createRowReader(columns));
}

declare global {
interface Window {
scheduler: {
Expand Down
Loading
Loading