@eclipse-scout/core
Version:
Eclipse Scout runtime
808 lines (707 loc) • 29.1 kB
text/typescript
/*
* Copyright (c) 2010, 2025 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 {
AdapterData, App, arrays, BooleanColumn, Cell, ChildModelOf, Column, ColumnModel, ColumnUserFilter, defaultValues, Event, Filter, ModelAdapter, NumberColumn, ObjectOrModel, objects, RemoteEvent, RemoteTableOrganizer, scout, Table,
TableAggregationFunctionChangedEvent, TableAppLinkActionEvent, TableCancelCellEditEvent, TableColumnBackgroundEffectChangedEvent, TableColumnMovedEvent, TableColumnResizedEvent, TableCompleteCellEditEvent, TableDropEvent,
TableFilterAddedEvent, TableFilterRemovedEvent, TableGroupEvent, TableModel, TablePrepareCellEditEvent, TableReloadEvent, TableRow, TableRowActionEvent, TableRowClickEvent, TableRowModel, TableRowsCheckedEvent, TableRowsExpandedEvent,
TableRowsSelectedEvent, TableSortEvent, TableUserFilter, ValueField
} from '../index';
import $ from 'jquery';
export class TableAdapter extends ModelAdapter {
declare widget: Table;
/** @internal */
_rebuildingTable: boolean;
constructor() {
super();
this._addRemoteProperties(['contextColumn']);
}
protected override _initProperties(model: TableModel) {
super._initProperties(model);
model.compactHandler = null; // Disable Scout JS compact handling, will be done on the server
model.organizer = scout.create(RemoteTableOrganizer); // table is organized on the server in classic mode
}
/** @internal */
override _postCreateWidget() {
// if a newly created table has already a user-filter defined, we need to fire the filter event after creation
// because the original event had been fired before the event-handler was registered.
if (this.widget.hasUserFilter()) {
this._onWidgetFilter();
}
}
protected _sendRowsSelected(rowIds: string[], debounceSend?: boolean) {
let eventData = {
rowIds: rowIds
};
// send delayed to avoid a lot of requests while selecting
// coalesce: only send the latest selection changed event for a field
this._send('rowsSelected', eventData, {
delay: debounceSend ? 250 : 0,
coalesce: function(previous) {
return this.target === previous.target && this.type === previous.type;
}
});
}
protected _sendRowClick(rowId: string, mouseButton: number, columnId: string) {
let data = {
rowId: rowId,
columnId: columnId,
mouseButton: mouseButton
};
this._send('rowClick', data);
}
protected _onWidgetRowsSelected(event: TableRowsSelectedEvent) {
let rowIds = this.widget.rowsToIds(this.widget.selectedRows);
this._sendRowsSelected(rowIds, event.debounce);
}
protected _onWidgetRowClick(event: TableRowClickEvent) {
let columnId: string;
if (event.column !== undefined) {
columnId = event.column.id;
}
this._sendRowClick(event.row.id, event.mouseButton, columnId);
}
protected _onWidgetFilterAdded(event: TableFilterAddedEvent) {
let filter = event.filter;
if (!(filter instanceof TableUserFilter) || (filter instanceof ColumnUserFilter && filter.column.guiOnly)) {
return;
}
this._send('filterAdded', filter.createFilterAddedEventData());
}
protected _onWidgetFilterRemoved(event: TableFilterRemovedEvent) {
let filter = event.filter;
if (!(filter instanceof TableUserFilter) || (filter instanceof ColumnUserFilter && filter.column.guiOnly)) {
return;
}
this._send('filterRemoved', filter.createFilterRemovedEventData());
}
protected _onWidgetColumnResized(event: TableColumnResizedEvent) {
this._sendColumnResized(event.column);
}
protected _sendColumnResized(column: Column<any>) {
if (column.fixedWidth || column.guiOnly || this.widget.autoResizeColumns) {
return;
}
let eventData = {
columnId: column.id,
width: column.width
};
// send delayed to avoid a lot of requests while resizing
// coalesce: only send the latest resize event for a column
this._send('columnResized', eventData, {
delay: 750,
coalesce: function(previous) {
return this.target === previous.target && this.type === previous.type && this.columnId === previous.columnId;
},
showBusyIndicator: false
});
}
protected _onWidgetAggregationFunctionChanged(event: TableAggregationFunctionChangedEvent) {
this._sendAggregationFunctionChanged(event.column);
}
protected _sendAggregationFunctionChanged(column: NumberColumn) {
if (column.guiOnly) {
return;
}
let data = {
columnId: column.id,
aggregationFunction: column.aggregationFunction
};
this._send('aggregationFunctionChanged', data);
}
protected _onWidgetColumnBackgroundEffectChanged(event: TableColumnBackgroundEffectChangedEvent) {
this._sendColumnBackgroundEffectChanged(event.column);
}
protected _sendColumnBackgroundEffectChanged(column: NumberColumn) {
if (column.guiOnly) {
return;
}
let data = {
columnId: column.id,
backgroundEffect: column.backgroundEffect
};
this._send('columnBackgroundEffectChanged', data);
}
sendColumnOrganizeAction(column: Column<any>, action: 'add' | 'remove' | 'modify') {
this._send('columnOrganizeAction', {
action: action,
columnId: column.id
});
}
protected _onWidgetColumnMoved(event: TableColumnMovedEvent) {
let index = event.newPos;
this.widget.columns.forEach((iteratingColumn, i) => {
// Adjust index if column is only known on the gui
if (iteratingColumn.guiOnly) {
index--;
}
});
this._sendColumnMoved(event.column, index);
}
protected _sendColumnMoved(column: Column<any>, index: number) {
if (column.guiOnly) {
return;
}
let data = {
columnId: column.id,
index: index
};
this._send('columnMoved', data);
}
protected _onWidgetPrepareCellEdit(event: TablePrepareCellEditEvent) {
event.preventDefault();
this._sendPrepareCellEdit(event.row, event.column);
}
protected _sendPrepareCellEdit(row: TableRow, column: Column<any>) {
if (column.guiOnly) {
return;
}
let data = {
rowId: row.id,
columnId: column.id
};
this._send('prepareCellEdit', data);
}
protected _onWidgetCompleteCellEdit(event: TableCompleteCellEditEvent) {
event.preventDefault();
this._sendCompleteCellEdit();
}
protected _sendCompleteCellEdit() {
this._send('completeCellEdit');
}
protected _onWidgetCancelCellEdit(event: TableCancelCellEditEvent) {
event.preventDefault();
this._sendCancelCellEdit();
}
protected _sendCancelCellEdit() {
this._send('cancelCellEdit');
}
protected _onWidgetRowsChecked(event: TableRowsCheckedEvent) {
this._sendRowsChecked(event.rows);
}
protected _sendRowsChecked(rows: TableRow[]) {
let data = {
rows: []
};
for (let i = 0; i < rows.length; i++) {
data.rows.push({
rowId: rows[i].id,
checked: rows[i].checked
});
}
this._send('rowsChecked', data);
}
protected _onWidgetRowsExpanded(event: TableRowsExpandedEvent) {
this._sendRowsExpanded(event.rows);
}
protected _sendRowsExpanded(rows: TableRow[]) {
let data = {
rows: rows.map(row => {
return {
rowId: row.id,
expanded: row.expanded
};
})
};
this._send('rowsExpanded', data);
}
protected _onWidgetFilter() {
let rowIds = this.widget.rowsToIds(this.widget.filteredRows());
this._sendFilter(rowIds);
}
protected _sendFilter(rowIds: string[]) {
let eventData: { remove?: boolean; rowIds?: string[] } = {};
if (rowIds.length === this.widget.rows.length) {
eventData.remove = true;
} else {
eventData.rowIds = rowIds;
}
// send with timeout, mainly for incremental load of a large table
// coalesce: only send last event (don't coalesce remove and 'add' events, the UI server needs both)
this._send('filter', eventData, {
delay: 250,
coalesce: function(previous) {
return this.target === previous.target && this.type === previous.type && this.remove === previous.remove;
},
showBusyIndicator: false
});
}
protected _onWidgetSort(event: TableSortEvent) {
if (event.column.guiOnly) {
return;
}
this._send('sort', {
columnId: event.column.id,
sortAscending: event.sortAscending,
sortingRemoved: event.sortingRemoved,
multiSort: event.multiSort,
sortingRequested: event.sortingRequested
});
}
protected _onWidgetGroup(event: TableGroupEvent) {
if (event.column.guiOnly) {
return;
}
this._send('group', {
columnId: event.column.id,
groupAscending: event.groupAscending,
groupingRemoved: event.groupingRemoved,
multiGroup: event.multiGroup,
groupingRequested: event.groupingRequested
});
}
protected _onWidgetRowAction(event: TableRowActionEvent) {
this._sendRowAction(event.row, event.column);
}
protected _sendRowAction(row: TableRow, column: Column<any>) {
if (column.guiOnly) {
// Send row action with a real column
// If there is only one guiOnly column (e.g. CompactColumn), sent column will be null
column = arrays.find(this.widget.columns, col => !col.guiOnly);
}
let columnId = column ? column.id : null;
this._send('rowAction', {
rowId: row.id,
columnId: columnId
});
}
protected _onWidgetAppLinkAction(event: TableAppLinkActionEvent) {
this._sendAppLinkAction(event.column, event.ref);
}
protected _sendAppLinkAction(column: Column<any>, ref: string) {
this._send('appLinkAction', {
columnId: column.id,
ref: ref
});
}
protected _sendContextColumn(contextColumn: Column<any>) {
if (contextColumn.guiOnly) {
contextColumn = null;
this.widget.contextColumn = null;
}
let columnId: string = null;
if (contextColumn) {
columnId = contextColumn.id;
}
this._send('property', {
contextColumn: columnId
});
}
protected _onWidgetReload(event: TableReloadEvent) {
let data = {
reloadReason: event.reloadReason
};
this._send('reload', data);
}
protected _onWidgetExportToClipboard(event: Event<Table>) {
this._send('clipboardExport');
event.preventDefault();
}
protected override _onWidgetEvent(event: Event<Table>) {
if (event.type === 'rowsSelected') {
this._onWidgetRowsSelected(event as TableRowsSelectedEvent);
} else if (event.type === 'rowsChecked') {
this._onWidgetRowsChecked(event as TableRowsCheckedEvent);
} else if (event.type === 'rowsExpanded') {
this._onWidgetRowsExpanded(event as TableRowsExpandedEvent);
} else if (event.type === 'filter') {
this._onWidgetFilter();
} else if (event.type === 'sort') {
this._onWidgetSort(event as TableSortEvent);
} else if (event.type === 'group') {
this._onWidgetGroup(event as TableGroupEvent);
} else if (event.type === 'rowClick') {
this._onWidgetRowClick(event as TableRowClickEvent);
} else if (event.type === 'rowAction') {
this._onWidgetRowAction(event as TableRowActionEvent);
} else if (event.type === 'prepareCellEdit') {
this._onWidgetPrepareCellEdit(event as TablePrepareCellEditEvent);
} else if (event.type === 'completeCellEdit') {
this._onWidgetCompleteCellEdit(event as TableCompleteCellEditEvent);
} else if (event.type === 'cancelCellEdit') {
this._onWidgetCancelCellEdit(event as TableCancelCellEditEvent);
} else if (event.type === 'appLinkAction') {
this._onWidgetAppLinkAction(event as TableAppLinkActionEvent);
} else if (event.type === 'clipboardExport') {
this._onWidgetExportToClipboard(event);
} else if (event.type === 'reload') {
this._onWidgetReload(event as TableReloadEvent);
} else if (event.type === 'filterAdded') {
this._onWidgetFilterAdded(event as TableFilterAddedEvent);
} else if (event.type === 'filterRemoved') {
this._onWidgetFilterRemoved(event as TableFilterRemovedEvent);
} else if (event.type === 'columnResized') {
this._onWidgetColumnResized(event as TableColumnResizedEvent);
} else if (event.type === 'columnMoved') {
this._onWidgetColumnMoved(event as TableColumnMovedEvent);
} else if (event.type === 'columnBackgroundEffectChanged') {
this._onWidgetColumnBackgroundEffectChanged(event as TableColumnBackgroundEffectChangedEvent);
} else if (event.type === 'aggregationFunctionChanged') {
this._onWidgetAggregationFunctionChanged(event as TableAggregationFunctionChangedEvent);
} else if (event.type === 'drop' && this.widget.dragAndDropHandler) {
this.widget.dragAndDropHandler.uploadFiles(event as TableDropEvent);
} else {
super._onWidgetEvent(event);
}
}
protected _onRowsInserted(rows: ObjectOrModel<TableRow> | ObjectOrModel<TableRow>[]) {
this.widget.insertRows(rows);
this._rebuildingTable = false;
}
protected _onRowsDeleted(rowIds: string[]) {
let rows = this.widget.rowsByIds(rowIds);
this.addFilterForWidgetEventType('rowsSelected');
this.widget.deleteRows(rows);
}
protected _onAllRowsDeleted() {
this.addFilterForWidgetEventType('rowsSelected');
this.widget.deleteAllRows();
}
protected _onRowsUpdated(rows: TableRow | TableRow[]) {
this.widget.updateRows(rows);
}
protected _onRowsSelected(rowIds: string[]) {
let rows = this.widget.rowsByIds(rowIds);
this.addFilterForWidgetEventType('rowsSelected');
this.widget.selectRows(rows);
// TODO [7.0] cgu what is this for? seems wrong here
this.widget.selectionHandler.clearLastSelectedRowMarker();
}
protected _onRowsChecked(rows: TableRowModel[]) {
let checkedRows: TableRow[] = [],
uncheckedRows: TableRow[] = [];
rows.forEach(rowData => {
let row = this.widget.rowById(rowData.id);
if (rowData.checked) {
checkedRows.push(row);
} else {
uncheckedRows.push(row);
}
});
this.addFilterForWidgetEventType('rowsChecked');
this.widget.checkRows(checkedRows, {
checked: true,
checkOnlyEnabled: false
});
this.widget.uncheckRows(uncheckedRows, {
checkOnlyEnabled: false
});
}
protected _onRowsExpanded(rows: TableRowModel[]) {
let expandedRows: TableRow[] = [],
collapsedRows: TableRow[] = [];
rows.forEach(rowData => {
let row = this.widget.rowById(rowData.id);
if (rowData.expanded) {
expandedRows.push(row);
} else {
collapsedRows.push(row);
}
});
this.addFilterForWidgetEventType('rowsExpanded');
this.widget.expandRows(expandedRows);
this.widget.collapseRows(collapsedRows);
}
protected _onRowOrderChanged(rowIds: string[]) {
let rows = this.widget.rowsByIds(rowIds);
this.widget.updateRowOrder(rows);
}
protected _onColumnStructureChanged(columns: Column<any>[]) {
this._rebuildingTable = true;
this.widget.updateColumnStructure(columns);
}
protected _onColumnOrderChanged(columnIds: string[]) {
let columns = this.widget.columnsByIds(columnIds);
this.widget.updateColumnOrder(columns);
}
protected _onColumnHeadersUpdated(columns: Column<any>[]) {
columns.forEach(column => defaultValues.applyTo(column));
this.widget.updateColumnHeaders(columns);
if (this.widget.tileMode && this.widget.tableTileGridMediator) {
// grouping might have changed, trigger re-init of the groups on the tileGrid in tileMode
this.widget.tableTileGridMediator._onTableGroup();
// removing of a group column doesn't cause a rowOrderChange, nonetheless aggregation columns might need to be removed.
this.widget.updateRowOrder(this.widget.rows);
}
}
protected _onStartCellEdit(columnId: string, rowId: string, fieldId: string) {
let column = this.widget.columnById(columnId),
row = this.widget.rowById(rowId),
field = this.session.getOrCreateWidget(fieldId, this.widget) as ValueField<any>;
this.widget.startCellEdit(column, row, field);
}
protected _onEndCellEdit(fieldId: string) {
let field = this.session.getModelAdapter(fieldId);
if (!field) {
throw new Error('Field adapter could not be resolved. Id: ' + fieldId);
}
this.widget.endCellEdit(field.widget as ValueField<any>);
}
protected _onRequestFocus() {
this.widget.focus();
}
protected _onScrollToSelection() {
this.widget.revealSelection();
}
protected _onColumnBackgroundEffectChanged(event: RemoteEvent) {
event.eventParts.forEach(function(eventPart) {
let column = this.widget.columnById(eventPart.columnId),
backgroundEffect = eventPart.backgroundEffect;
this.addFilterForWidgetEvent(widgetEvent => {
return (widgetEvent.type === 'columnBackgroundEffectChanged' &&
widgetEvent.column.id === column.id &&
widgetEvent.column.backgroundEffect === backgroundEffect);
});
column.setBackgroundEffect(backgroundEffect);
}, this);
}
protected _onRequestFocusInCell(event: RemoteEvent) {
let row = this.widget.rowById(event.rowId),
column = this.widget.columnById(event.columnId);
this.widget.focusCell(column, row);
}
protected _onAggregationFunctionChanged(event: RemoteEvent) {
let columns = [],
functions = [];
event.eventParts.forEach(function(eventPart) {
let func = eventPart.aggregationFunction,
column = this.widget.columnById(eventPart.columnId);
this.addFilterForWidgetEvent(widgetEvent => {
return (widgetEvent.type === 'aggregationFunctionChanged' &&
widgetEvent.column.id === column.id &&
widgetEvent.column.aggregationFunction === func);
});
columns.push(column);
functions.push(func);
}, this);
this.widget.changeAggregations(columns, functions);
}
protected _onFiltersChanged(filters: (ObjectOrModel<TableUserFilter> | Filter<TableRow>)[]) {
this.addFilterForWidgetEventType('filterAdded');
this.addFilterForWidgetEventType('filterRemoved');
this.widget.setFilters(filters);
// do not re-filter while the table is being rebuilt (because column.index in filter and row.cells may be inconsistent)
if (!this._rebuildingTable) {
this.widget.filter();
}
}
override onModelAction(event: RemoteEvent) {
if (event.type === 'rowsInserted') {
this._onRowsInserted(event.rows);
} else if (event.type === 'rowsDeleted') {
this._onRowsDeleted(event.rowIds);
} else if (event.type === 'allRowsDeleted') {
this._onAllRowsDeleted();
} else if (event.type === 'rowsSelected') {
this._onRowsSelected(event.rowIds);
} else if (event.type === 'rowOrderChanged') {
this._onRowOrderChanged(event.rowIds);
} else if (event.type === 'rowsUpdated') {
this._onRowsUpdated(event.rows);
} else if (event.type === 'filtersChanged') {
this._onFiltersChanged(event.filters);
} else if (event.type === 'rowsChecked') {
this._onRowsChecked(event.rows);
} else if (event.type === 'rowsExpanded') {
this._onRowsExpanded(event.rows);
} else if (event.type === 'columnStructureChanged') {
this._onColumnStructureChanged(event.columns);
} else if (event.type === 'columnOrderChanged') {
this._onColumnOrderChanged(event.columnIds);
} else if (event.type === 'columnHeadersUpdated') {
this._onColumnHeadersUpdated(event.columns);
} else if (event.type === 'startCellEdit') {
this._onStartCellEdit(event.columnId, event.rowId, event.fieldId);
} else if (event.type === 'endCellEdit') {
this._onEndCellEdit(event.fieldId);
} else if (event.type === 'requestFocus') {
this._onRequestFocus();
} else if (event.type === 'scrollToSelection') {
this._onScrollToSelection();
} else if (event.type === 'aggregationFunctionChanged') {
this._onAggregationFunctionChanged(event);
} else if (event.type === 'columnBackgroundEffectChanged') {
this._onColumnBackgroundEffectChanged(event);
} else if (event.type === 'requestFocusInCell') {
this._onRequestFocusInCell(event);
} else {
super.onModelAction(event);
}
}
override exportAdapterData(adapterData: AdapterData): AdapterData {
adapterData = super.exportAdapterData(adapterData);
delete adapterData.selectedRows;
adapterData.rows = [];
adapterData.columns.forEach(column => {
delete column.classId;
delete column.modelClass;
});
return adapterData;
}
protected _initRowModel(rowModel?: TableRowModel): ChildModelOf<TableRow> {
let model = (rowModel || {}) as ChildModelOf<TableRow>;
model.objectType = scout.nvl(model.objectType, 'TableRow');
defaultValues.applyTo(model);
return model;
}
protected static _createRowRemote(this: Table & { modelAdapter: TableAdapter; _createRowOrig }, rowModel: TableRowModel): TableRow {
if (this.modelAdapter) {
rowModel = this.modelAdapter._initRowModel(rowModel);
}
return this._createRowOrig(rowModel);
}
/**
* Static method to modify the prototype of Table.
*/
static modifyTablePrototype() {
if (!App.get().remote) {
return;
}
objects.replacePrototypeFunction(Table, '_createRow', TableAdapter._createRowRemote, true);
// _sortWhileInit
objects.replacePrototypeFunction(Table, '_sortWhileInit', function(this: Table & { _sortWhileInitOrig }) {
if (this.modelAdapter) {
// Scout classic: not necessary to sort in init as the rows are already sorted on the Java UI. Only apply grouping here.
this._group();
} else {
this._sortWhileInitOrig();
}
}, true);
// _sortAfterInsert
objects.replacePrototypeFunction(Table, '_sortAfterInsert', function(this: Table & { _sortAfterInsertOrig }, wasEmpty: boolean) {
if (this.modelAdapter) {
// There will only be a row order changed event if table was not empty.
// If it was empty, there will be NO row order changed event (tableEventBuffer) -> inserted rows are already in correct order -> no sort necessary but group is
if (wasEmpty) {
this._group();
}
} else {
this._sortAfterInsertOrig(wasEmpty);
}
}, true);
// _sortAfterUpdate
objects.replacePrototypeFunction(Table, '_sortAfterUpdate', function(this: Table & { _sortAfterUpdateOrig }) {
if (this.modelAdapter) {
this._group();
} else {
this._sortAfterUpdateOrig();
}
}, true);
// uiSortPossible
objects.replacePrototypeFunction(Table, '_isSortingPossible', function(this: Table & { uiSortPossible: boolean; _isSortingPossibleOrig }, sortColumns: Column<any>[]) {
if (this.modelAdapter) {
// In a JS only app the flag 'uiSortPossible' is never set and thus defaults to true. Additionally, we check if each column can install
// its comparator used to sort. If installation failed for some reason, sorting is not possible. In a remote app the server sets the
// 'uiSortPossible' flag, which decides if the column must be sorted by the server or can be sorted by the client.
let uiSortPossible = scout.nvl(this.uiSortPossible, true);
return uiSortPossible && this._isSortingPossibleOrig(sortColumns);
}
return this._isSortingPossibleOrig(sortColumns);
}, true);
// sort
objects.replacePrototypeFunction(Table, 'sort', function(this: Table & { sortOrig }, column: Column<any>, direction?: 'asc' | 'desc', multiSort?: boolean, remove?: boolean) {
if (this.modelAdapter && column.guiOnly) {
return;
}
this.sortOrig(column, direction, multiSort, remove);
}, true);
// no js default tileTableHeader in classic mode
objects.replacePrototypeFunction(Table, '_createTileTableHeader', function(this: Table & { _createTileTableHeaderOrig }) {
if (this.modelAdapter) {
return null;
}
return this._createTileTableHeaderOrig();
}, true);
// not used in classic mode since tiles are created by the server
objects.replacePrototypeFunction(Table, 'createTiles', function(this: Table & { createTilesOrig: typeof Table.prototype.createTiles }, rows: TableRow[]) {
if (this.modelAdapter) {
return null;
}
return this.createTilesOrig(rows);
}, true);
}
static modifyColumnPrototype() {
if (!App.get().remote) {
return;
}
// init
objects.replacePrototypeFunction(Column, 'init', function(this: Column & { initOrig }, model: ColumnModel<any>) {
if (model.parent && model.parent.modelAdapter && !model.guiOnly) {
// Fill in the missing default values only in remote case, don't do it JS case to not accidentally set undefined properties (e.g. uiSortEnabled)
model = $.extend({}, model);
defaultValues.applyTo(model);
}
this.initOrig(model);
}, true);
// _ensureCell
objects.replacePrototypeFunction(Column, '_ensureCell', function(this: Column & { _ensureCellOrig; _ensureValue }, vararg: any) {
if (this.table.modelAdapter) {
// Note: we do almost the same thing as in _ensureCellOrig, the difference is that
// we treat a plain object always as cell-model and we always must apply defaultValues
// to this cell model. In the JS only case a plain-object has no special meaning and
// can be used as cell-value in the same way as a scalar value. Also, we must not apply
// defaultValues in JS only case, because it would destroy the 'undefined' state of the
// cell properties, which is required because the Column checks, whether it should apply
// defaults from the Column instance to a cell, or use the values from the cell.
let model;
if (objects.isObject(vararg)) {
model = vararg;
model.value = this._ensureValue(model.value);
// Parse the value if a text but no value is provided. The server does only set the text if value and text are equal.
// It is also necessary for custom columns which don't have a UI representation and never send the value.
// Do not parse the value if there is an error status.
// If editing fails, the display text will be the user input, the value unchanged, and the server will set the error status.
if (model.text && model.value === undefined && !model.errorStatus) {
model.value = this._ensureValue(model.text);
}
// use null instead of undefined
if (model.value === undefined) {
model.value = null;
}
} else {
model = {
value: this._ensureValue(vararg)
};
}
defaultValues.applyTo(model, 'Cell');
return scout.create(Cell, model);
}
return this._ensureCellOrig(vararg);
}, true);
// uiSortPossible
objects.replacePrototypeFunction(Column, 'isSortingPossible', function(this: Column & { uiSortPossible: boolean; isSortingPossibleOrig: typeof Column.prototype.isSortingPossible }) {
if (this.table.modelAdapter) {
// Returns whether this column can be used to sort on the client side. In a JS only app the flag 'uiSortPossible'
// is never set and defaults to true. As a side effect of this function a comparator is installed.
// The comparator returns false if it could not be installed which means sorting should be delegated to server (e.g. collator is not available).
// In a remote app the server sets the 'uiSortPossible' flag, which decides if the column must be sorted by the
// server or can be sorted by the client.
let uiSortPossible = scout.nvl(this.uiSortPossible, true);
return uiSortPossible && this.installComparator();
}
return this.isSortingPossibleOrig();
}, true);
}
static modifyBooleanColumnPrototype() {
if (!App.get().remote) {
return;
}
// _toggleCellValue
objects.replacePrototypeFunction(BooleanColumn, '_toggleCellValue', function(this: BooleanColumn & { _toggleCellValueOrig }, row: TableRow, cell: Cell<boolean>) {
if (this.table.modelAdapter) {
// NOP - do nothing, since server will handle the click, see Java AbstractTable#interceptRowClickSingleObserver
} else {
this._toggleCellValueOrig(row, cell);
}
}, true);
}
}
App.addListener('bootstrap', TableAdapter.modifyTablePrototype);
App.addListener('bootstrap', TableAdapter.modifyColumnPrototype);
App.addListener('bootstrap', TableAdapter.modifyBooleanColumnPrototype);