blob: 556f15f5f9e633435bc86bca6f2d31251b5b30f2 [file] [log] [blame]
/*
* Copyright (c) 2010-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 {Cell, ColumnOptimalWidthMeasurer, comparators, EventSupport, FormField, GridData, icons, objects, scout, Status, strings, styles, Table, TableRow, texts} from '../../index';
import $ from 'jquery';
export default class Column {
constructor() {
this.autoOptimizeWidth = false;
this.autoOptimizeWidthRequired = false; // true if content of the column changed and width has to be optimized
this.autoOptimizeMaxWidth = -1;
this.cssClass = null;
this.compacted = false;
this.editable = false;
this.removable = false;
this.modifiable = false;
this.fixedWidth = false;
this.fixedPosition = false;
this.grouped = false;
this.headerCssClass = null;
this.headerIconId = null;
this.headerHtmlEnabled = false;
this.headerTooltipText = null;
this.horizontalAlignment = -1;
this.htmlEnabled = false;
this.index = -1;
this.initialized = false;
this.mandatory = false;
this.optimalWidthMeasurer = new ColumnOptimalWidthMeasurer(this);
this.sortActive = false;
this.sortAscending = true;
this.sortIndex = -1;
this.summary = false;
this.type = 'text';
this.width = 60;
this.initialWidth = undefined; // the width the column initially has
this.prefMinWidth = null;
this.minWidth = Column.DEFAULT_MIN_WIDTH; // the minimal width the column can have
this.showSeparator = true;
this.table = null;
this.tableNodeColumn = false;
this.maxLength = 4000;
this.text = null;
this.textWrap = false;
this.filterType = 'TextColumnUserFilter';
this.comparator = comparators.TEXT;
this.displayable = true;
this.visible = true;
this.textBased = true;
this.headerMenuEnabled = true;
this.tableNodeLevel0CellPadding = 28;
this.expandableIconLevel0CellPadding = 13;
this.nodeColumnCandidate = true;
this.events = this._createEventSupport();
this._tableColumnsChangedHandler = this._onTableColumnsChanged.bind(this);
// Contains the width the cells of the column really have (only set in Chrome due to a Chrome bug, see Table._updateRealColumnWidths)
this._realWidth = null;
this.$header = null; // Set by TableHeader.js
this.$separator = null;
}
static DEFAULT_MIN_WIDTH = 60;
static NARROW_MIN_WIDTH = 32; // for columns without text (icon, check box)
init(model) {
this.session = model.session;
// Copy all properties from model to this
$.extend(this, model);
// Initial width is only sent if it differs from width
if (this.initialWidth === undefined) {
this.initialWidth = scout.nvl(this.width, 0);
}
this._init(model);
this.initialized = true;
}
/**
* Override this function in order to implement custom init logic.
*/
_init(model) {
texts.resolveTextProperty(this, 'text');
texts.resolveTextProperty(this, 'headerTooltipText');
icons.resolveIconProperty(this, 'headerIconId');
this._setTable(this.table);
this._setDisplayable(this.displayable);
this._setAutoOptimizeWidth(this.autoOptimizeWidth);
// no need to call setEditable here. cell propagation is done in _initCell
}
destroy() {
this._destroy();
this._setTable(null);
}
/**
* Override this function in order to implement custom destroy logic.
*/
_destroy() {
// NOP
}
_setTable(table) {
if (this.table) {
this.table.off('columnMoved columnStructureChanged', this._tableColumnsChangedHandler);
}
this.table = table;
if (this.table) {
this.table.on('columnMoved columnStructureChanged', this._tableColumnsChangedHandler);
}
}
/**
* Converts the vararg if it is of type string to an object with
* a property 'text' with the original value.
*
* Example:
* 'My Company' --> { text: 'MyCompany'; }
*
* @see JsonCell.java
* @param {Cell|string|number|object} vararg either a Cell instance or a scalar value
*/
initCell(vararg, row) {
let cell = this._ensureCell(vararg);
this._initCell(cell);
// If a text is provided, use that text instead of using formatValue to generate a text based on the value
if (objects.isNullOrUndefined(cell.text)) {
this._updateCellText(row, cell);
}
return cell;
}
/**
* Ensures that a Cell instance is returned. When vararg is a scalar value a new Cell instance is created and
* the value is set as cell.value property.
*
* @param {Cell|string|number|object} vararg either a Cell instance or a scalar value
* @returns {*}
* @private
*/
_ensureCell(vararg) {
let cell;
if (vararg instanceof Cell) {
cell = vararg;
// value may be set but may have the wrong type (e.g. text instead of date) -> ensure type
cell.value = this._parseValue(cell.value);
} else {
// in this case 'vararg' is only a scalar value, typically a string
cell = scout.create('Cell', {
value: this._parseValue(vararg)
});
}
return cell;
}
/**
* Override this method to create a value based on the given scalar value.
*/
_parseValue(scalar) {
return scalar;
}
_updateCellText(row, cell) {
let value = cell.value;
if (!row) {
// row is omitted when creating aggregate cells
return;
}
let returned = this._formatValue(value, row);
if (returned && $.isFunction(returned.promise)) {
// Promise is returned -> set display text later
this.setCellTextDeferred(returned, row, cell);
} else {
this.setCellText(row, returned, cell);
}
}
_formatValue(value, row) {
return scout.nvl(value, '');
}
/**
* If cell does not define properties, use column values.
* Override this function to implement type specific init cell behavior.
*
* @param {Cell} cell
*/
_initCell(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;
}
buildCellForRow(row) {
let cell = this.cell(row);
return this.buildCell(cell, row);
}
buildCellForAggregateRow(aggregateRow) {
let cell;
if (this.grouped) {
let refRow = (this.table.groupingStyle === Table.GroupingStyle.TOP ? aggregateRow.nextRow : aggregateRow.prevRow);
cell = this.createAggrGroupCell(refRow);
} else {
let aggregateValue = aggregateRow.contents[this.table.columns.indexOf(this)];
cell = this.createAggrValueCell(aggregateValue);
}
return this.buildCell(cell, {});
}
buildCell(cell, row) {
scout.assertParameter('cell', cell, Cell);
let tableNodeColumn = this.table.isTableNodeColumn(this),
rowPadding = 0;
if (tableNodeColumn) {
rowPadding = this.table._calcRowLevelPadding(row);
}
let text = this._text(cell);
let iconId = cell.iconId;
let icon = this._icon(iconId, !!text) || '';
let cssClass = this._cellCssClass(cell, tableNodeColumn);
let style = this._cellStyle(cell, tableNodeColumn, rowPadding);
if (cell.errorStatus) {
row.hasError = true;
}
let content;
if (!text && !icon) {
// If every cell of a row is empty the row would collapse, using nbsp makes sure the row is as height as the others even if it is empty
content = ' ';
cssClass = strings.join(' ', cssClass, 'empty');
} else {
content = icon + text;
}
if (tableNodeColumn && row._expandable) {
this.tableNodeColumn = true;
content = this._expandIcon(row.expanded, rowPadding) + content;
if (row.expanded) {
cssClass += ' expanded';
}
}
return this._buildCell(content, style, cssClass);
}
_buildCell(content, style, cssClass) {
return '<div class="' + cssClass + '" style="' + style + '">' + content + '</div>';
}
_expandIcon(expanded, rowPadding) {
let style = 'padding-left: ' + (rowPadding + this.expandableIconLevel0CellPadding) + 'px';
let cssClasses = 'table-row-control';
if (expanded) {
cssClasses += ' expanded';
}
return '<div class="' + cssClasses + '" style="' + style + '"></div>';
}
_icon(iconId, hasText) {
let cssClass, icon;
if (!iconId) {
return;
}
cssClass = 'table-cell-icon';
if (hasText) {
cssClass += ' with-text';
}
icon = icons.parseIconId(iconId);
if (icon.isFontIcon()) {
cssClass += ' font-icon';
return '<span class="' + icon.appendCssClass(cssClass) + '">' + icon.iconCharacter + '</span>';
}
cssClass += ' image-icon';
return '<img class="' + cssClass + '" src="' + icon.iconUrl + '">';
}
_text(cell) {
let text = cell.text || '';
if (!cell.htmlEnabled) {
text = cell.encodedText() || '';
if (this.table.multilineText) {
text = strings.nl2br(text, false);
}
if (text) {
// Wrap in a span to make customization using css easier.
// An empty text will be replaced with nbsp later on. To make that work, only wrap it if there is text.
text = '<span class="text">' + text + '</span>';
}
}
return text;
}
_cellCssClass(cell, tableNode) {
let cssClass = 'table-cell';
if (cell.mandatory) {
cssClass += ' mandatory';
}
if (!this.table.multilineText || !this.textWrap) {
cssClass += ' white-space-nowrap';
}
if (cell.editable) {
cssClass += ' editable';
}
if (cell.errorStatus) {
cssClass += ' has-error';
}
if (cell.iconId && !cell.text) {
cssClass += ' icon-only';
}
cssClass += ' halign-' + Table.parseHorizontalAlignment(cell.horizontalAlignment);
let visibleColumns = this.table.visibleColumns();
let overAllColumnPosition = visibleColumns.indexOf(this);
if (overAllColumnPosition === 0) {
cssClass += ' first';
}
if (overAllColumnPosition === visibleColumns.length - 1) {
cssClass += ' last';
}
if (tableNode) {
cssClass += ' table-node';
}
if (cell.cssClass) {
cssClass += ' ' + cell.cssClass;
}
return cssClass;
}
_cellStyle(cell, tableNodeColumn, rowPadding) {
let style,
width = this.width;
if (width === 0) {
return 'display: none;';
}
style = 'min-width: ' + width + 'px; max-width: ' + width + 'px; ';
if (tableNodeColumn) {
// calculate padding
style += ' padding-left: ' + (this.tableNodeLevel0CellPadding + rowPadding) + 'px; ';
}
style += styles.legacyStyle(cell);
return style;
}
onMouseUp(event, $row) {
let row = $row.data('row'),
cell = this.cell(row);
if (this.isCellEditable(row, cell, event)) {
this.table.prepareCellEdit(this, row, true);
}
}
isCellEditable(row, cell, event) {
return this.table.enabledComputed && row.enabled && cell.editable && !event.ctrlKey && !event.shiftKey;
}
startCellEdit(row, field) {
let popup,
$row = row.$row,
cell = this.cell(row),
$cell = this.table.$cell(this, $row);
cell.field = field;
// Override field alignment with the cell's alignment
cell.field.gridData.horizontalAlignment = cell.horizontalAlignment;
popup = scout.create('CellEditorPopup', {
parent: this.table,
column: this,
row: row,
cell: cell
});
popup.$anchor = $cell;
popup.open(this.table.$data);
return popup;
}
/**
* @returns {Cell} the cell object for this column from the given row.
*/
cell(row) {
return this.table.cell(this, row);
}
/**
* Creates an artificial cell from the properties relevant for the column header.
* @returns {Cell}
*/
headerCell() {
return scout.create('Cell', {
value: this.text,
text: this.text,
iconId: this.headerIconId,
cssClass: this.headerCssClass,
tooltipText: this.headerTooltipText,
htmlEnabled: this.headerHtmlEnabled
});
}
/**
* @returns {Cell} the cell object for this column from the first selected row in the table.
*/
selectedCell() {
let selectedRow = this.table.selectedRow();
return this.table.cell(this, selectedRow);
}
/**
* @param row
* @returns {string|*} the text of the cell if {@link Column.textBased} is true, otherwise the value of the cell.
*/
cellValueOrText(row) {
if (this.textBased) {
return this.table.cellText(this, row);
}
return this.table.cellValue(this, row);
}
cellText(row) {
return this.table.cellText(this, row);
}
/**
* @returns {*} the cell value to be used for grouping and filtering (chart, column filter).
*/
cellValueOrTextForCalculation(row) {
let cell = this.cell(row);
let value = this.cellValueOrText(row);
if (objects.isNullOrUndefined(value)) {
return null;
}
return this._preprocessValueOrTextForCalculation(value, cell);
}
_preprocessValueOrTextForCalculation(value, cell) {
if (typeof value === 'string') {
// In case of string columns, value and text are equal -> use _preprocessStringForCalculation to handle html tags and new lines correctly
return this._preprocessTextForCalculation(value, cell.htmlEnabled);
}
return value;
}
_preprocessTextForCalculation(text, htmlEnabled) {
return this._preprocessText(text, {
removeHtmlTags: htmlEnabled,
removeNewlines: true,
trim: true
});
}
/**
* @returns {string} the cell text to be used for table grouping
*/
cellTextForGrouping(row) {
let cell = this.cell(row);
return this._preprocessTextForGrouping(cell.text, cell.htmlEnabled);
}
_preprocessTextForGrouping(text, htmlEnabled) {
return this._preprocessText(text, {
removeHtmlTags: htmlEnabled,
trim: true
});
}
/**
* @returns {string} the cell text to be used for the text filter
*/
cellTextForTextFilter(row) {
let cell = this.cell(row);
return this._preprocessTextForTextFilter(cell.text, cell.htmlEnabled);
}
_preprocessTextForTextFilter(text, htmlEnabled) {
return this._preprocessText(text, {
removeHtmlTags: htmlEnabled
});
}
/**
* @returns {string} the cell text to be used for the table row detail.
*/
cellTextForRowDetail(row) {
let cell = this.cell(row);
return this._preprocessText(this._text(cell), {
removeHtmlTags: cell.htmlEnabled
});
}
/**
* Removes html tags, converts to single line, removes leading and trailing whitespaces.
*/
_preprocessText(text, options) {
if (text === null || text === undefined) {
return text;
}
options = options || {};
if (options.removeHtmlTags) {
text = strings.plainText(text);
}
if (options.removeNewlines) {
text = text.replace('\n', ' ');
}
if (options.trim) {
text = text.trim();
}
return text;
}
setCellValue(row, value) {
let cell = this.cell(row);
this._setCellValue(row, value, cell);
this._updateCellText(row, cell);
}
_setCellValue(row, value, cell) {
// value may have the wrong type (e.g. text instead of date) -> ensure type
value = this._parseValue(value);
// Only update row status when value changed.
// Cell text needs to be updated even if value did not change
// (text may cause an invalid value that won't be saved on the cell, reverting to the valid value needs to update the text again)
if (cell.value !== value && row.status === TableRow.Status.NON_CHANGED) {
row.status = TableRow.Status.UPDATED;
}
cell.setValue(value);
}
setCellTextDeferred(promise, row, cell) {
promise
.done(text => {
this.setCellText(row, text, cell);
})
.fail(error => {
this.setCellText(row, '', cell);
$.log.error('Could not resolve cell text for value ' + cell.value, error);
});
// (then) promises always resolve asynchronously which means the text will always be set later after row is initialized and will generate an update row event.
// To make sure not every cell update will render the viewport (which is an expensive operation), the update is buffered and done as soon as all promises resolve.
this.table.updateBuffer.pushPromise(promise);
}
setCellText(row, text, cell) {
if (!cell) {
cell = this.cell(row);
}
if (cell.text === text) {
// Don't trigger row update if text has not changed
return;
}
cell.setText(text);
// Don't update row while initializing (it is either added to the table later, or being added / updated right now)
// The check for "this.table" is necessary, because the column could already have been destroyed (method is called
// asynchronously by setCellTextDeferred).
if (row.initialized && this.table) {
this.table.updateRow(row);
}
}
setCellErrorStatus(row, errorStatus, cell) {
if (!cell) {
cell = this.cell(row);
}
cell.setErrorStatus(errorStatus);
}
setCellIconId(row, iconId) {
let cell = this.cell(row);
if (cell.iconId === iconId) {
return;
}
cell.setIconId(iconId);
if (row.initialized) {
this.table.updateRow(row);
}
}
setHorizontalAlignment(hAlign) {
if (this.horizontalAlignment === hAlign) {
return;
}
this.horizontalAlignment = hAlign;
this.table.rows.forEach(row => {
this.cell(row).setHorizontalAlignment(hAlign);
});
this.table.updateRows(this.table.rows);
if (this.table.header) {
this.table.header.updateHeader(this);
}
}
setEditable(editable) {
if (this.editable === editable) {
return;
}
this.editable = editable;
this.table.rows.forEach(row => {
this.cell(row).setEditable(editable);
});
this.table.updateRows(this.table.rows);
}
setMandatory(mandatory) {
if (this.mandatory === mandatory) {
return;
}
this.mandatory = mandatory;
this.table.rows.forEach(row => {
this.cell(row).setMandatory(mandatory);
});
this.table.updateRows(this.table.rows);
}
setCssClass(cssClass) {
if (this.cssClass === cssClass) {
return;
}
this.cssClass = cssClass;
this.table.rows.forEach(function(row) {
this.cell(row).setCssClass(cssClass);
}, this);
this.table.updateRows(this.table.rows);
}
setWidth(width) {
if (this.width === width) {
return;
}
this.table.resizeColumn(this, width);
}
createAggrGroupCell(row) {
let cell = this.cell(row);
return this.initCell(scout.create('Cell', {
// value necessary for value based columns (e.g. checkbox column)
value: cell.value,
text: this.cellTextForGrouping(row),
iconId: cell.iconId,
horizontalAlignment: this.horizontalAlignment,
cssClass: 'table-aggregate-cell' + (cell.cssClass ? ' ' + cell.cssClass : '')
}));
}
createAggrValueCell(value) {
return this.createAggrEmptyCell();
}
createAggrEmptyCell() {
return this.initCell(scout.create('Cell', {
empty: true,
cssClass: 'table-aggregate-cell'
}));
}
calculateOptimalWidth() {
return this.optimalWidthMeasurer.measure();
}
/**
* Returns a type specific column user-filter. The default impl. returns a ColumnUserFilter.
* Sub-classes that must return another type, must simply change the value of the 'filterType' property.
*/
createFilter(model) {
return scout.create(this.filterType, {
session: this.session,
table: this.table,
column: this
});
}
/**
* Returns a table header menu. Sub-classes can override this method to create a column specific table header menu.
*/
createTableHeaderMenu(tableHeader) {
let $header = this.$header;
return scout.create('TableHeaderMenu', {
parent: tableHeader,
column: $header.data('column'),
tableHeader: tableHeader,
$anchor: $header
});
}
/**
* @returns a field instance used as editor when a cell of this column is in edit mode.
*/
createEditor(row) {
let field = this._createEditor(row);
let cell = this.cell(row);
this._initEditorField(field, cell);
field.setLabelVisible(false);
field.setFieldStyle(FormField.FieldStyle.CLASSIC);
let hints = new GridData(field.gridDataHints);
hints.horizontalAlignment = cell.horizontalAlignment;
field.setGridDataHints(hints);
return field;
}
/**
* Depending on the type of column the editor may need to be initialized differently.
* The default implementation either copies the value to the field if the field has no error or copies the text and error status if it has an error.
*/
_initEditorField(field, cell) {
if (cell.errorStatus) {
this._updateEditorFromInvalidCell(field, cell);
} else {
this._updateEditorFromValidCell(field, cell);
}
}
_updateEditorFromValidCell(field, cell) {
field.setValue(cell.value);
}
_updateEditorFromInvalidCell(field, cell) {
field.setErrorStatus(cell.errorStatus);
field.setDisplayText(cell.text);
}
_createEditor() {
return scout.create('StringField', {
parent: this.table,
maxLength: this.maxLength,
multilineText: this.table.multilineText,
wrapText: this.textWrap
});
}
updateCellFromEditor(row, field) {
if (field.errorStatus) {
this._updateCellFromInvalidEditor(row, field);
} else {
this._updateCellFromValidEditor(row, field);
}
}
_updateCellFromInvalidEditor(row, field) {
this.setCellErrorStatus(row, field.errorStatus);
this.setCellText(row, field.displayText);
}
_updateCellFromValidEditor(row, field) {
this.setCellErrorStatus(row, null);
this.setCellValue(row, field.value);
}
/**
* Override this function to install a specific compare function on a column instance.
* The default impl. installs a generic comparator working with less than and greater than.
*
* @returns whether or not it was possible to install a compare function. If not, client side sorting is disabled.
*/
installComparator() {
return this.comparator.install(this.session);
}
/**
* @returns {boolean} whether or not it is possible to sort this column.
* As a side effect a comparator is installed.
*/
isSortingPossible() {
// If installation fails sorting is still possible (in case of the text comparator just without a collator)
this.installComparator();
return true;
}
compare(row1, row2) {
let cell1 = this.table.cell(this, row1),
cell2 = this.table.cell(this, row2);
if (cell1.sortCode !== null || cell2.sortCode !== null) {
return comparators.NUMERIC.compare(cell1.sortCode, cell2.sortCode);
}
let valueA = this.cellValueOrText(row1);
let valueB = this.cellValueOrText(row2);
return this.comparator.compare(valueA, valueB);
}
isVisible() {
return this.displayable && this.visible && !this.compacted;
}
/**
*
* @param {boolean} visible
* @param {boolean} [redraw] true, to redraw the table immediately, false if not.
* When false is used, the redraw needs to be triggered manually using {@link Table.onColumnVisibilityChanged}. Default is true.
*/
setVisible(visible, redraw) {
if (this.visible === visible) {
return;
}
this._setVisible(visible, redraw);
}
_setVisible(visible, redraw) {
this.visible = visible;
if (scout.nvl(redraw, this.initialized)) {
this.table.onColumnVisibilityChanged();
}
}
/**
*
* @param {boolean} displayable
* @param {boolean} [redraw] true, to redraw the table immediately, false if not.
* When false is used, the redraw needs to be triggered manually using {@link Table.onColumnVisibilityChanged}. Default is true.
*/
setDisplayable(displayable, redraw) {
if (this.displayable === displayable) {
return;
}
this._setDisplayable(displayable, redraw);
}
_setDisplayable(displayable, redraw) {
this.displayable = displayable;
if (scout.nvl(redraw, this.initialized)) {
this.table.onColumnVisibilityChanged();
}
}
/**
*
* @param {boolean} compacted
* @param {boolean} [redraw] true, to redraw the table immediately, false if not.
* When false is used, the redraw needs to be triggered manually using {@link Table.onColumnVisibilityChanged}. Default is true.
*/
setCompacted(compacted, redraw) {
if (this.compacted === compacted) {
return;
}
this._setCompacted(compacted, redraw);
}
_setCompacted(compacted, redraw) {
this.compacted = compacted;
if (scout.nvl(redraw, this.initialized)) {
this.table.onColumnVisibilityChanged();
}
}
setAutoOptimizeWidth(autoOptimizeWidth) {
if (this.autoOptimizeWidth === autoOptimizeWidth) {
return;
}
this._setAutoOptimizeWidth(autoOptimizeWidth);
}
_setAutoOptimizeWidth(autoOptimizeWidth) {
this.autoOptimizeWidth = autoOptimizeWidth;
this.autoOptimizeWidthRequired = autoOptimizeWidth;
if (this.initialized) {
this.table.columnLayoutDirty = true;
this.table.invalidateLayoutTree();
}
}
setMaxLength(maxLength) {
this.maxLength = maxLength;
}
setText(text) {
if (this.text === text) {
return;
}
this.text = text;
if (this.table.header) {
this.table.header.updateHeader(this);
}
}
setHeaderIconId(headerIconId) {
if (this.headerIconId === headerIconId) {
return;
}
this.headerIconId = headerIconId;
if (this.table.header) {
this.table.header.updateHeader(this);
}
}
setHeaderCssClass(headerCssClass) {
if (this.headerCssClass === headerCssClass) {
return;
}
let oldState = $.extend({}, this);
this.headerCssClass = headerCssClass;
if (this.table.header) {
this.table.header.updateHeader(this, oldState);
}
}
setHeaderHtmlEnabled(headerHtmlEnabled) {
if (this.headerHtmlEnabled === headerHtmlEnabled) {
return;
}
this.headerHtmlEnabled = headerHtmlEnabled;
if (this.table.header) {
this.table.header.updateHeader(this);
}
}
setHeaderTooltipText(headerTooltipText) {
this.headerTooltipText = headerTooltipText;
}
setHeaderTooltipHtmlEnabled(headerTooltipHtmlEnabled) {
this.headerTooltipHtmlEnabled = headerTooltipHtmlEnabled;
}
setTextWrap(textWrap) {
if (this.textWrap === textWrap) {
return;
}
this.textWrap = textWrap;
if (this.table.rendered && this.table.multilineText) { // If multilineText is disabled toggling textWrap has no effect
// See also table._renderMultilineText(), requires similar operations
this.autoOptimizeWidthRequired = true;
this.table._redraw();
this.table.invalidateLayoutTree();
}
}
isContentValid(row) {
let cell = this.cell(row);
let validByErrorStatus = !cell.errorStatus || cell.errorStatus.severity !== Status.Severity.ERROR;
let validByMandatory = !cell.mandatory || this._hasCellValue(cell);
return {
valid: validByErrorStatus && validByMandatory,
validByErrorStatus: validByErrorStatus,
validByMandatory: validByMandatory
};
}
_hasCellValue(cell) {
return !!cell.value;
}
_onTableColumnsChanged(event) {
if (this.table.visibleColumns().indexOf(this) === 0) {
this.tableNodeLevel0CellPadding = 28;
this.expandableIconLevel0CellPadding = 13;
} else {
this.tableNodeLevel0CellPadding = 23;
this.expandableIconLevel0CellPadding = 8;
}
}
_realWidthIfAvailable() {
return this._realWidth || this.width;
}
// --- Event handling methods ---
_createEventSupport() {
return new EventSupport();
}
trigger(type, event) {
event = event || {};
event.source = this;
this.events.trigger(type, event);
}
one(type, func) {
this.events.one(type, func);
}
on(type, func) {
return this.events.on(type, func);
}
off(type, func) {
this.events.off(type, func);
}
addListener(listener) {
this.events.addListener(listener);
}
removeListener(listener) {
this.events.removeListener(listener);
}
/**
* Adds an event handler using {@link #one()} and returns a promise.
* The promise is resolved as soon as the event is triggered.
*/
when(type) {
return this.events.when(type);
}
}