From 17998636a05cdb991f0eebe8025eba9e183c2752 Mon Sep 17 00:00:00 2001 From: Claudio Guglielmo Date: Fri, 8 May 2026 10:39:10 +0200 Subject: [PATCH] Column: add formatter Add a formatter property similar to the one of the ValueField. This makes it possible to change the format function without the need to subclass the Column. 331671 --- .../src/form/fields/ValueField.ts | 24 +++-- .../src/form/fields/ValueFieldModel.ts | 6 +- .../src/table/columns/Column.ts | 63 ++++++++++--- .../src/table/columns/ColumnModel.ts | 15 +++- .../test/table/columns/ColumnSpec.ts | 89 ++++++++++++++++--- 5 files changed, 157 insertions(+), 40 deletions(-) diff --git a/eclipse-scout-core/src/form/fields/ValueField.ts b/eclipse-scout-core/src/form/fields/ValueField.ts index 17d60365b82..429bddba9eb 100644 --- a/eclipse-scout-core/src/form/fields/ValueField.ts +++ b/eclipse-scout-core/src/form/fields/ValueField.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023 BSI Business Systems Integration AG + * Copyright (c) 2010, 2026 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -586,35 +586,33 @@ export class ValueField extend } /** - * Replaces the existing formatter. The formatter is called during {@link formatValue}. + * Replaces the existing {@link ValueFieldModel.formatter}. * - * Remember calling the default formatter which is passed as parameter to the format function, if needed. * @param formatter the new formatter. If null, the default formatter is used. - * - * @see ValueFieldModel.formatter */ setFormatter(formatter: ValueFieldFormatter) { + if (!formatter) { + formatter = this._formatValue.bind(this); + } this.setProperty('formatter', formatter); if (this.initialized) { this.validate(); } } - protected _setFormatter(formatter: ValueFieldFormatter) { - if (!formatter) { - formatter = this._formatValue.bind(this); - } - this._setProperty('formatter', formatter); - } - /** - * @returns the formatted display text + * Uses the {@link ValueFieldModel.formatter} to format the value. + * + * @returns the formatted value as display text or a promise if the formatting happens asynchronously. */ formatValue(value: TValue): string | JQuery.Promise { let defaultFormatter = this._formatValue.bind(this); return this.formatter(value, defaultFormatter); } + /** + * @returns the formatted value as display text or a promise if the formatting happens asynchronously. + */ protected _formatValue(value: TValue): string | JQuery.Promise { return scout.nvl(value, '') + ''; } diff --git a/eclipse-scout-core/src/form/fields/ValueFieldModel.ts b/eclipse-scout-core/src/form/fields/ValueFieldModel.ts index cffcb52570f..90141071968 100644 --- a/eclipse-scout-core/src/form/fields/ValueFieldModel.ts +++ b/eclipse-scout-core/src/form/fields/ValueFieldModel.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023 BSI Business Systems Integration AG + * Copyright (c) 2010, 2026 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -28,7 +28,9 @@ export interface ValueFieldModel extends PropertyEventEmitter implements Col modifiable: boolean; fixedWidth: boolean; fixedPosition: boolean; + formatter: ColumnFormatter; grouped: boolean; headerCssClass: string; headerIconId: string; @@ -106,6 +107,7 @@ export class Column extends PropertyEventEmitter implements Col this.modifiable = true; this.fixedWidth = false; this.fixedPosition = false; + this.formatter = this._formatValue.bind(this); this.grouped = false; this.headerCssClass = null; this.headerIconId = null; @@ -253,6 +255,19 @@ export class Column extends PropertyEventEmitter implements Col return cell; } + /** + * If cell does not define properties, use column values. + * Override this function to implement type specific init cell behavior. + */ + protected _initCell(cell: Cell): Cell { + cell.cssClass = scout.nvl(cell.cssClass, this.cssClass); + cell.editable = scout.nvl(cell.editable, this.editable); + cell.horizontalAlignment = scout.nvl(cell.horizontalAlignment, this.horizontalAlignment); + cell.htmlEnabled = scout.nvl(cell.htmlEnabled, this.htmlEnabled); + cell.mandatory = scout.nvl(cell.mandatory, this.mandatory); + return cell; + } + /** * Ensures that a Cell instance is returned. * When vararg is a raw value a new Cell instance is created and the value is set as {@link cell.value} property. @@ -292,7 +307,7 @@ export class Column extends PropertyEventEmitter implements Col return; } - let returned = this._formatValue(value, row); + let returned = this.formatValue(value, row); if (objects.isPromise(returned)) { // Promise is returned -> set display text later this.setCellTextDeferred(returned, row, cell); @@ -301,22 +316,45 @@ export class Column extends PropertyEventEmitter implements Col } } - protected _formatValue(value: TValue, row?: TableRow): string | JQuery.Promise { - return scout.nvl(value, ''); + /** + * Updates the cell text for every row of this column. + */ + protected _updateCellTexts() { + for (const row of this.table.rows) { + this._updateCellText(row, this.cell(row)); + } } /** - * If cell does not define properties, use column values. - * Override this function to implement type specific init cell behavior. + * Replaces the existing {@link ColumnModel.formatter}. * + * @param formatter the new formatter. If null, the default formatter is used. */ - protected _initCell(cell: Cell): Cell { - cell.cssClass = scout.nvl(cell.cssClass, this.cssClass); - cell.editable = scout.nvl(cell.editable, this.editable); - cell.horizontalAlignment = scout.nvl(cell.horizontalAlignment, this.horizontalAlignment); - cell.htmlEnabled = scout.nvl(cell.htmlEnabled, this.htmlEnabled); - cell.mandatory = scout.nvl(cell.mandatory, this.mandatory); - return cell; + setFormatter(formatter: ColumnFormatter) { + if (!formatter) { + formatter = this._formatValue.bind(this); + } + this.setProperty('formatter', formatter); + if (this.initialized) { + this._updateCellTexts(); + } + } + + /** + * Uses the {@link ColumnModel.formatter} to format the cell value. + * + * @returns the formatted cell value as text or a promise if the formatting happens asynchronously. + */ + formatValue(value: TValue, row: TableRow): string | JQuery.Promise { + let defaultFormatter = this._formatValue.bind(this); + return this.formatter(value, row, defaultFormatter); + } + + /** + * @returns the formatted cell value as text or a promise if the formatting happens asynchronously. + */ + protected _formatValue(value: TValue, row?: TableRow): string | JQuery.Promise { + return scout.nvl(value, ''); } buildCellForRow(row: TableRow): string { @@ -1226,3 +1264,4 @@ export class Column extends PropertyEventEmitter implements Col } export type ColumnValidationResult = { valid: boolean; validByMandatory: boolean; errorStatus: Status }; +export type ColumnFormatter = (value: TValue, row: TableRow, defaultFormatter?: ColumnFormatter) => string | JQuery.Promise; diff --git a/eclipse-scout-core/src/table/columns/ColumnModel.ts b/eclipse-scout-core/src/table/columns/ColumnModel.ts index 1aad2dc0c21..eb23e685772 100644 --- a/eclipse-scout-core/src/table/columns/ColumnModel.ts +++ b/eclipse-scout-core/src/table/columns/ColumnModel.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025 BSI Business Systems Integration AG + * Copyright (c) 2010, 2026 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -import {Alignment, Column, ColumnComparator, ObjectModelWithId, ObjectModelWithUuid, Session, Table} from '../../index'; +import {Alignment, Column, ColumnComparator, ColumnFormatter, ObjectModelWithId, ObjectModelWithUuid, Session, Table} from '../../index'; export interface ColumnModel extends ObjectModelWithUuid>, ObjectModelWithId { /** @@ -103,6 +103,17 @@ export interface ColumnModel extends ObjectModelWithUuid; + /** * Configures if the table is grouped by this column. * diff --git a/eclipse-scout-core/test/table/columns/ColumnSpec.ts b/eclipse-scout-core/test/table/columns/ColumnSpec.ts index f77bfd53d89..a55514653d0 100644 --- a/eclipse-scout-core/test/table/columns/ColumnSpec.ts +++ b/eclipse-scout-core/test/table/columns/ColumnSpec.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025 BSI Business Systems Integration AG + * Copyright (c) 2010, 2026 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -import {BooleanColumn, Cell, Column, ListBoxAriaRules, NumberColumn, scout, Table, Widget} from '../../../src/index'; +import {BooleanColumn, Cell, Column, ListBoxAriaRules, NumberColumn, scout, Table, TableModel, Widget} from '../../../src/index'; import {JQueryTesting, SpecTable, TableSpecHelper} from '../../../src/testing/index'; describe('Column', () => { @@ -227,7 +227,7 @@ describe('Column', () => { }); describe('textWrap', () => { - let table, model, $rows, $cells0, $cell0_0; + let table: Table, model: TableModel, $rows: JQuery, $cells0: JQuery, $cell0_0: JQuery; beforeEach(() => { model = helper.createModelFixture(2, 2); @@ -285,7 +285,7 @@ describe('Column', () => { }); describe('initCell', () => { - let table, model; + let table: Table, model: TableModel; beforeEach(() => { model = helper.createModelFixture(1, 0); @@ -302,7 +302,7 @@ describe('Column', () => { }); it('calls formatValue to format the text', () => { - table.columns[0]._formatValue = value => value.toUpperCase(); + table.columns[0].setFormatter(value => value.toUpperCase()); table.insertRows([{ cells: ['cell 1'] }]); @@ -312,7 +312,7 @@ describe('Column', () => { }); it('calls formatValue to format the text, also for cell objects', () => { - table.columns[0]._formatValue = value => value.toUpperCase(); + table.columns[0].setFormatter(value => value.toUpperCase()); table.insertRows([{ cells: [scout.create(Cell, { value: 'cell 1' @@ -324,7 +324,7 @@ describe('Column', () => { }); it('does not format the value if a text is provided', () => { - table.columns[0]._formatValue = value => value.toUpperCase(); + table.columns[0].setFormatter(value => value.toUpperCase()); table.insertRows([{ cells: [scout.create(Cell, { value: 'cell 1', @@ -337,7 +337,7 @@ describe('Column', () => { }); it('sets the value to null if only text is provided', () => { - table.columns[0]._formatValue = value => value.toUpperCase(); + table.columns[0].setFormatter(value => value.toUpperCase()); table.insertRows([{ cells: [scout.create(Cell, { text: 'cell text 1' @@ -351,7 +351,7 @@ describe('Column', () => { }); describe('setCellValue', () => { - let table, model; + let table: Table, model: TableModel; beforeEach(() => { model = helper.createModelFixture(2, 0); @@ -371,8 +371,8 @@ describe('Column', () => { expect(row.cells[0].text).toEqual('new cell value'); }); - it('calls formatValue to format the text', () => { - table.columns[0]._formatValue = value => value.toUpperCase(); + it('calls formatValue to format the value', () => { + table.columns[0].setFormatter(value => value.toUpperCase()); table.insertRows([{ cells: ['cell 1', 'cell 2'] }]); @@ -384,7 +384,74 @@ describe('Column', () => { expect(row.cells[0].value).toEqual('new cell value'); expect(row.cells[0].text).toEqual('NEW CELL VALUE'); }); + }); + + describe('formatter', () => { + it('formats the value', () => { + const table = scout.create(Table, { + parent: session.desktop, + columns: [{ + objectType: Column, + formatter: (value, row, defaultFormatter) => { + let text = defaultFormatter(value, row) as string; + if (!text) { + return text; + } + return text.match(/.{4}/g).join('-'); + } + }, { + objectType: Column + // Uses the default formatter _formatValue + }] + }); + table.insertRows([{ + cells: ['1234123412341234', '1234123412341234'] + }]); + expect(table.rows[0].cells[0].value).toEqual('1234123412341234'); + expect(table.rows[0].cells[0].text).toEqual('1234-1234-1234-1234'); + expect(table.rows[0].cells[1].value).toEqual('1234123412341234'); + expect(table.rows[0].cells[1].text).toEqual('1234123412341234'); + + table.setCellValue(table.columns[0], table.rows[0], 'abcdabcdabcdabcd'); + expect(table.rows[0].cells[0].value).toEqual('abcdabcdabcdabcd'); + expect(table.rows[0].cells[0].text).toEqual('abcd-abcd-abcd-abcd'); + + table.setCellValue(table.columns[1], table.rows[0], 'abcdabcdabcdabcd'); + expect(table.rows[0].cells[1].value).toEqual('abcdabcdabcdabcd'); + expect(table.rows[0].cells[1].text).toEqual('abcdabcdabcdabcd'); + }); + it('can be changed dynamically', () => { + const table = scout.create(Table, { + parent: session.desktop, + columns: [{ + objectType: Column + }] + }); + table.insertRows([{ + cells: ['1234123412341234'] + }]); + expect(table.rows[0].cells[0].value).toEqual('1234123412341234'); + expect(table.rows[0].cells[0].text).toEqual('1234123412341234'); + + table.columns[0].setFormatter((value, row, defaultFormatter) => { + let text = defaultFormatter(value, row) as string; + if (!text) { + return text; + } + return text.match(/.{4}/g).join('-'); + }); + expect(table.rows[0].cells[0].value).toEqual('1234123412341234'); + expect(table.rows[0].cells[0].text).toEqual('1234-1234-1234-1234'); + + table.setCellValue(table.columns[0], table.rows[0], 'abcdabcdabcdabcd'); + expect(table.rows[0].cells[0].value).toEqual('abcdabcdabcdabcd'); + expect(table.rows[0].cells[0].text).toEqual('abcd-abcd-abcd-abcd'); + + table.columns[0].setFormatter(null); + expect(table.rows[0].cells[0].value).toEqual('abcdabcdabcdabcd'); + expect(table.rows[0].cells[0].text).toEqual('abcdabcdabcdabcd'); + }); }); describe('cell getters', () => {