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
24 changes: 11 additions & 13 deletions eclipse-scout-core/src/form/fields/ValueField.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -586,35 +586,33 @@ export class ValueField<TValue extends TModelValue, TModelValue = TValue> 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<TValue>) {
if (!formatter) {
formatter = this._formatValue.bind(this);
}
this.setProperty('formatter', formatter);
if (this.initialized) {
this.validate();
}
}

protected _setFormatter(formatter: ValueFieldFormatter<TValue>) {
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<string> {
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<string> {
return scout.nvl(value, '') + '';
}
Expand Down
6 changes: 4 additions & 2 deletions eclipse-scout-core/src/form/fields/ValueFieldModel.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -28,7 +28,9 @@ export interface ValueFieldModel<TValue extends TModelValue, TModelValue = TValu
/**
* The formatter is responsible to format the {@link value} so it can be used as {@link displayText}.
*
* Default is {@link _formatValue}.
* The default formatter is passed as parameter and can be called during formatting, if needed.
*
* Default is {@link ValueField._formatValue}.
*
* @see ValueField.formatValue
*/
Expand Down
63 changes: 51 additions & 12 deletions eclipse-scout-core/src/table/columns/Column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class Column<TValue = string> extends PropertyEventEmitter implements Col
modifiable: boolean;
fixedWidth: boolean;
fixedPosition: boolean;
formatter: ColumnFormatter<TValue>;
grouped: boolean;
headerCssClass: string;
headerIconId: string;
Expand Down Expand Up @@ -106,6 +107,7 @@ export class Column<TValue = string> 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;
Expand Down Expand Up @@ -253,6 +255,19 @@ export class Column<TValue = string> 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<TValue>): Cell<TValue> {
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.
Expand Down Expand Up @@ -292,7 +307,7 @@ export class Column<TValue = string> 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);
Expand All @@ -301,22 +316,45 @@ export class Column<TValue = string> extends PropertyEventEmitter implements Col
}
}

protected _formatValue(value: TValue, row?: TableRow): string | JQuery.Promise<string> {
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<TValue>): Cell<TValue> {
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<TValue>) {
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<string> {
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<string> {
return scout.nvl(value, '');
}

buildCellForRow(row: TableRow): string {
Expand Down Expand Up @@ -1226,3 +1264,4 @@ export class Column<TValue = string> extends PropertyEventEmitter implements Col
}

export type ColumnValidationResult = { valid: boolean; validByMandatory: boolean; errorStatus: Status };
export type ColumnFormatter<TValue> = (value: TValue, row: TableRow, defaultFormatter?: ColumnFormatter<TValue>) => string | JQuery.Promise<string>;
15 changes: 13 additions & 2 deletions eclipse-scout-core/src/table/columns/ColumnModel.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* 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
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* 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<TValue = string> extends ObjectModelWithUuid<Column<TValue>>, ObjectModelWithId {
/**
Expand Down Expand Up @@ -103,6 +103,17 @@ export interface ColumnModel<TValue = string> extends ObjectModelWithUuid<Column
*/
fixedPosition?: boolean;

/**
* The formatter is responsible to format the {@link Cell.value} so it can be used as {@link Cell.text}.
*
* The default formatter is passed as parameter and can be called during formatting, if needed.
*
* Default is {@link Column._formatValue}.
*
* @see Column.formatValue
*/
formatter?: ColumnFormatter<TValue>;

/**
* Configures if the table is grouped by this column.
*
Expand Down
89 changes: 78 additions & 11 deletions eclipse-scout-core/test/table/columns/ColumnSpec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* 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
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* 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', () => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -285,7 +285,7 @@ describe('Column', () => {
});

describe('initCell', () => {
let table, model;
let table: Table, model: TableModel;

beforeEach(() => {
model = helper.createModelFixture(1, 0);
Expand All @@ -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']
}]);
Expand All @@ -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'
Expand All @@ -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',
Expand All @@ -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'
Expand All @@ -351,7 +351,7 @@ describe('Column', () => {
});

describe('setCellValue', () => {
let table, model;
let table: Table, model: TableModel;

beforeEach(() => {
model = helper.createModelFixture(2, 0);
Expand All @@ -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']
}]);
Expand All @@ -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', () => {
Expand Down
Loading