blob: a5f14e1684005f957f4d6a539becccdf259d1004 [file] [log] [blame]
/*
* Copyright (c) 2014-2021 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
import {codes, Column, LookupCall, LookupRow, scout, SmartField, strings} from '../../index';
import objects from '../../util/objects';
/**
* Column where each cell fetches its value using a lookup call.
*
* A 'prepareLookupCall' event gets triggered before executing the lookup call and contains two properties, 'lookupCall' and 'row'. Here, 'lookupCall' is the
* lookup call which is used to fetch one ore more values for a cell. 'row' is the row containing the cell and usually corresponds to the selected row.
* It should be used instead of the property selectedRows from Table.js which must not be used here.
* 'row' can be null or undefined in some cases. Hence some care is needed when listening to this event.
*/
export default class SmartColumn extends Column {
constructor() {
super();
this.codeType = null;
this.lookupCall = null;
this.browseHierarchy = false;
this.browseMaxRowCount = SmartField.DEFAULT_BROWSE_MAX_COUNT;
this.browseAutoExpandAll = true;
this.browseLoadIncremental = false;
this.activeFilterEnabled = false;
this._lookupCallBatchContext = null;
}
/**
* @override
*/
_init(model) {
super._init(model);
this._setLookupCall(this.lookupCall);
this._setCodeType(this.codeType);
}
_initCell(cell) {
super._initCell(cell);
cell.sortCode = this._calculateCellSortCode(cell);
return cell;
}
_calculateCellSortCode(cell) {
if (!this.codeType) {
return null;
}
let code = codes.get(this.codeType, cell.value);
return code ? code.sortCode : null;
}
_updateAllCellSortCodes() {
this.table.rows.map(row => this.cell(row)).forEach(cell => cell.setSortCode(this._calculateCellSortCode(cell)));
}
setLookupCall(lookupCall) {
if (this.lookupCall === lookupCall) {
return;
}
this._setLookupCall(lookupCall);
this._updateAllCellSortCodes();
}
_setLookupCall(lookupCall) {
this.lookupCall = LookupCall.ensure(lookupCall, this.session);
}
setCodeType(codeType) {
if (this.codeType === codeType) {
return;
}
this._setCodeType(codeType);
this._updateAllCellSortCodes();
}
_setCodeType(codeType) {
this.codeType = codeType;
if (!codeType) {
return;
}
this.lookupCall = scout.create('CodeLookupCall', {
session: this.session,
codeType: codeType
});
}
setBrowseHierarchy(browseHierarchy) {
this.browseHierarchy = browseHierarchy;
}
setBrowseMaxRowCount(browseMaxRowCount) {
this.browseMaxRowCount = browseMaxRowCount;
}
setBrowseAutoExpandAll(browseAutoExpandAll) {
this.browseAutoExpandAll = browseAutoExpandAll;
}
setBrowseLoadIncremental(browseLoadIncremental) {
this.browseLoadIncremental = browseLoadIncremental;
}
setActiveFilterEnabled(activeFilterEnabled) {
this.activeFilterEnabled = activeFilterEnabled;
}
_formatValue(value, row) {
if (!this.lookupCall) {
return strings.nvl(value) + '';
}
if (this.lookupCall.batch) {
return this._batchFormatValue(value);
}
let lookupCall = this.lookupCall.clone();
this.trigger('prepareLookupCall', {
lookupCall: lookupCall,
row: row
});
return lookupCall.textByKey(value);
}
/**
* Defers all invocations of the lookup call for the duration of the current event handler.
* Once the current event handler completes, all lookup calls are resolved in a single batch.
*/
_batchFormatValue(key) {
if (objects.isNullOrUndefined(key)) {
return $.resolvedPromise('');
}
let currentBatchContext = this._lookupCallBatchContext;
if (!currentBatchContext) {
// create new batch context for this column
const batchResult = $.Deferred();
currentBatchContext = {
keySet: {},
result: batchResult.promise()
};
this._lookupCallBatchContext = currentBatchContext;
setTimeout(() => {
// reset batch context for next batch run
this._lookupCallBatchContext = null;
let lookupCall = this.lookupCall.clone();
this.trigger('prepareLookupCall', {
lookupCall: lookupCall
});
// batch lookup texts
lookupCall.textsByKeys(Object.keys(currentBatchContext.keySet))
.then(textMap => batchResult.resolve(textMap)) // resolve result in current batch context
.catch(e => batchResult.reject(e)); // reject any errors
});
}
// add key to current batch
currentBatchContext.keySet[key] = true;
// return text for current key
return currentBatchContext.result.then(textMap => textMap[key] || '');
}
/**
* Create and set the lookup-row instead of call setValue() as this would execute a lookup by key
* which is not necessary, since the cell already contains text and value. This also avoids a problem
* with multiple lookups running at once, see ticket 236960.
*/
_updateEditorFromValidCell(field, cell) {
if (objects.isNullOrUndefined(cell.value)) {
field.setValue(null);
return;
}
let lookupRow = new LookupRow();
lookupRow.key = cell.value;
lookupRow.text = cell.text;
field.setLookupRow(lookupRow);
}
_createEditor(row) {
let field = scout.create('SmartField', {
parent: this.table,
codeType: this.codeType,
lookupCall: this.lookupCall ? this.lookupCall.clone() : null,
browseHierarchy: this.browseHierarchy,
browseMaxRowCount: this.browseMaxRowCount,
browseAutoExpandAll: this.browseAutoExpandAll,
browseLoadIncremental: this.browseLoadIncremental,
activeFilterEnabled: this.activeFilterEnabled
});
field.on('prepareLookupCall', event => {
this.trigger('prepareLookupCall', {
lookupCall: event.lookupCall,
row: row
});
});
field.on('lookupCallDone', event => {
this.trigger('lookupCallDone', {
result: event.result
});
});
return field;
}
_updateCellFromValidEditor(row, field) {
// The following code is only necessary to prevent flickering because the text is updated async.
// Instead of only calling setCellValue which itself would update the display text, we set the text manually before calling setCellValue.
// This works because in most of the cases the text computed by the column will be the same as the one computed by the editor field.
// Clear error status first (regular behavior)
this.setCellErrorStatus(row, null);
// Update cell text
// We cannot use setCellText to not trigger updateRows yet -> it has to be done after the value and row.status are updated correctly.
let cell = this.cell(row);
let oldText = cell.text;
let newText = field.displayText;
cell.setText(newText);
// Update cell value
// We cannot use setCellValue since it would add the update event to the updateBuffer but we need the row update to be sync to prevent the flickering
this._setCellValue(row, field.value, cell);
// Update row -> Render row, trigger update event
// Only trigger update row event if text has changed (same as setCellText would do)
if (row.initialized && oldText !== newText && cell.text === newText) {
this.table.updateRow(row);
}
// Ensure display text is correct (for the rare case that the column computes a different text than the editor field).
this._updateCellText(row, cell);
}
/**
* Since we don't know the type of the key from the lookup-row we must deal with numeric and string types here.
*/
_hasCellValue(cell) {
let value = cell.value;
if (objects.isNumber(value)) {
return !objects.isNullOrUndefined(value); // Zero (0) is valid too
}
return !!value;
}
_setCellValue(row, value, cell) {
super._setCellValue(row, value, cell);
cell.setSortCode(this._calculateCellSortCode(cell));
}
}