devexpress-reporting
Version:
DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.
572 lines (571 loc) • 26.6 kB
JavaScript
/**
* DevExpress HTML/JS Reporting (designer\controls\xrCrossTabCell.js)
* Version: 24.2.6
* Build date: Mar 18, 2025
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* License: https://www.devexpress.com/Support/EULAs/universal.xml
*/
import { Rectangle } from '@devexpress/analytics-core/analytics-elements';
import { checkModelReady, formatUnicorn, getFullPath, getLocalization } from '@devexpress/analytics-core/analytics-internal';
import { ModelSerializer, PathRequest } from '@devexpress/analytics-core/analytics-utils';
import { createExpressionEditorCollectionToolOptions } from '@devexpress/analytics-core/analytics-widgets-internal';
import * as ko from 'knockout';
import { stylesProperties } from '../helpers/_styleHelper';
import { controlsFactory } from '../utils/settings';
import { CellKind, DataFieldLayout, TotalsPosition } from './crossTab/enums';
import { crossTabDataFieldInfoBase, crossTabGroupFieldInfoBase } from './metadata/crosstab/fields';
import { stylesInfo } from './metadata/properties/style';
import { XRControlSurface, XRControlViewModel } from './xrControl';
import { ReportViewModel } from './xrReport';
import { XRTextControlSurfaceBase } from './xrTextControl';
export function kindToString(kind) {
switch (kind) {
case CellKind.None:
return 'NO';
case CellKind.Corner:
return 'CRN';
case CellKind.RowHeader:
return 'RH';
case CellKind.RowTotalHeader:
return 'RTH';
case CellKind.RowTotal:
return 'RT';
case CellKind.ColumnHeader:
return 'CH';
case CellKind.ColumnTotalHeader:
return 'CTH';
case CellKind.ColumnTotal:
return 'CT';
case CellKind.Data:
return 'DAT';
case CellKind.DataHeader:
return 'DH';
case CellKind.GrandTotal:
return 'GDT';
case CellKind.Empty:
return 'Emtpy';
case CellKind.EmptyHeader:
return 'EmtpyH';
}
}
export class XRCrossTabCellViewModel extends XRControlViewModel {
get namePrefix() {
let cellType = '';
for (const type in XRCrossTabCellViewModel.cellKinds) {
if (XRCrossTabCellViewModel.cellKinds[type].indexOf(this.kind()) != -1)
cellType = type;
}
return 'crossTab' + cellType + 'Cell';
}
get _width() {
const columns = this.parent._columnDefinitions();
let result = 0;
for (let i = this._columnIndex(); i < this._columnIndex() + this._columnSpan(); i++) {
result += columns[i].width();
}
return result;
}
get _height() {
const rows = this.parent._rowDefinitions();
let result = 0;
for (let i = this._rowIndex(); i < this._rowIndex() + this._rowSpan(); i++) {
result += rows[i].height();
}
return result;
}
get _left() {
const columns = this.parent._columnDefinitions();
let result = 0;
for (let i = 0; i < this._columnIndex(); i++) {
result += columns[i].width();
}
return result;
}
get _top() {
const rows = this.parent._rowDefinitions();
let result = 0;
for (let i = 0; i < this._rowIndex(); i++) {
result += rows[i].height();
}
return result;
}
_getDefaultName(kind) {
switch (kind) {
case CellKind.ColumnHeader:
return getLocalization('Columns', 'ReportStringId.CrossTab_ColumnAreaName');
case CellKind.RowHeader:
return getLocalization('Rows', 'ReportStringId.CrossTab_RowAreaName');
case CellKind.Data:
return getLocalization('Data', 'ReportStringId.CrossTab_DataAreaName');
}
}
_testFieldName(fieldName, path) {
const report = this.root;
if (!report || !(report instanceof ReportViewModel))
return;
const dataBindingsProvider = report.dataBindingsProvider && report.dataBindingsProvider();
if (dataBindingsProvider) {
dataBindingsProvider.getItems(path).done(result => {
this.fieldNameAreValid(result.some(x => x.name === fieldName));
});
}
}
_createParametersExpressionCategory() {
const crossTabParameters = this.parent.controlParameters();
crossTabParameters.forEach(x => x.initDataMemberInfo());
return createExpressionEditorCollectionToolOptions(crossTabParameters.map(x => {
return {
text: x.name,
val: '?' + x.name,
description: (x.dataType || x.specifics || 'object').toLowerCase() + ' ' + x.name
};
}), 'Parameters', 'ReportStringId.CatParameters', true);
}
constructor(model, parent, serializer = new ModelSerializer()) {
super(model, parent, serializer);
this.parent = parent;
this._showCellCode = ko.observable(false);
this._oldFieldName = null;
this.getPath = (propertyName) => {
if (this.field && this.field())
return this.field().getPath(propertyName);
return getFullPath(this.parent.getPath('dataMember'), this.parent['dataMember']());
};
this.kind = ko.observable(CellKind.None);
this.fieldNameAreValid = ko.observable(true);
this._disposables.push(ko.computed(() => {
if (checkModelReady(this.parent)) {
this.size.width(this._width);
}
}));
this._disposables.push(ko.computed(() => {
if (checkModelReady(this.parent)) {
this.size.height(this._height);
}
}));
this._disposables.push(ko.computed(() => {
if (checkModelReady(this.parent)) {
this.location.x(this._left);
}
}));
this._disposables.push(ko.computed(() => {
if (checkModelReady(this.parent)) {
this.location.y(this._top);
}
}));
this._disposables.push(this.location.x.subscribe(newVal => {
if (checkModelReady(this.parent)) {
const delta = newVal - this._left;
if (this._columnIndex() != 0) {
const targetColumn = parent._columnDefinitions()[this._columnIndex() - 1];
targetColumn.width(targetColumn.width() + delta);
}
else {
parent.location.x(parent.location.x() + delta);
}
}
}));
this._disposables.push(this.location.y.subscribe(newVal => {
if (checkModelReady(this.parent)) {
const delta = newVal - this._top;
if (this._rowIndex() != 0) {
const targetRow = parent._rowDefinitions()[this._rowIndex() - 1];
targetRow.height(targetRow.height() + delta);
}
else {
parent.location.y(parent.location.y() + delta);
}
}
}));
this._disposables.push(this.size.height.subscribe(newHeight => {
if (checkModelReady(this.parent)) {
const currentHeight = this._height;
const rows = parent._rowDefinitions();
for (let i = this._rowIndex(); i < this._rowIndex() + this._rowSpan(); i++) {
rows[i].height(rows[i].height() + (newHeight - currentHeight) * (rows[i].height() / currentHeight));
}
}
}));
this._disposables.push(this.size.width.subscribe(newWidth => {
if (checkModelReady(this.parent)) {
const currentWidth = this._width;
const columns = parent._columnDefinitions();
for (let i = this._columnIndex(); i < this._columnIndex() + this._columnSpan(); i++) {
columns[i].width(columns[i].width() + (newWidth - currentWidth) * (columns[i].width() / currentWidth));
}
}
}));
this._text = ko.observable(this.text());
this.field = ko.observable(null);
this._disposables.push(this.field.subscribe(field => {
if (field) {
crossTabGroupFieldInfoBase.concat(crossTabDataFieldInfoBase).forEach(info => {
if (field[info.propertyName])
this[info.propertyName] = field[info.propertyName];
});
}
}));
this._disposables.push(this.fieldName = ko.pureComputed({
read: () => this.field() && this.field().fieldName() || '',
write: (newVal) => {
if (this.field())
this.field().fieldName(newVal);
else
this.createAndAssignNewField(newVal, true);
}
}));
this._disposables.push(ko.computed(() => {
this._testFieldName(this.fieldName(), new PathRequest(getFullPath(this.parent.getPath(''), this.parent['dataMember']())));
}));
this._disposables.push(this.kind.subscribe((kind) => {
if (this._textFormatString() === '' && this.field() && (kind == CellKind.RowTotalHeader
|| kind == CellKind.ColumnTotalHeader))
this.textFormatString('Total {0}');
}));
this._disposables.push(ko.computed(() => {
if (this._text() === '' && this.isIndependant() && this.kind() != CellKind.Corner) {
this.text('Grand Total');
}
if ((this.kind() == CellKind.Corner || this.kind() == CellKind.DataHeader) && this.fieldName() && this.fieldName() != this._oldFieldName) {
(this._oldFieldName == this.text() || this.text() === '') && this.text(this.fieldName());
this._oldFieldName = this.fieldName();
}
}));
this._disposables.push(this.text = ko.pureComputed({
read: () => {
if (this._showCellCode()) {
const kind = kindToString(this.kind());
const rowSpan = this._rowSpan() != 1 ? '(' + this._rowSpan() + ')' : '';
const columnSpan = this._columnSpan() != 1 ? '(' + this._columnSpan() + ')' : '';
return kind + ' X' + this._columnIndex() + columnSpan + ' Y' + this._rowIndex() + rowSpan;
}
if (this._text())
return this._text();
const fieldName = this.fieldName() ? '[' + this.fieldName() + ']' : '';
if (this.kind() == CellKind.RowTotalHeader || this.kind() == CellKind.ColumnTotalHeader) {
return this.textFormatString() ? formatUnicorn(this.textFormatString(), fieldName) : fieldName;
}
if (this.isBindable())
return fieldName || this._getDefaultName(this.kind());
},
write: (newVal) => this._text(newVal)
}));
this._disposables.push(ko.computed(() => {
switch (this.kind()) {
case CellKind.Corner:
case CellKind.ColumnHeader:
case CellKind.ColumnTotalHeader:
case CellKind.DataHeader:
case CellKind.RowHeader:
case CellKind.RowTotalHeader:
case CellKind.EmptyHeader:
case CellKind.Empty:
this.styleName(this.parent['headerAreaStyleName']());
break;
case CellKind.ColumnTotal:
case CellKind.RowTotal:
case CellKind.GrandTotal:
this.styleName(this.parent['totalAreaStyleName']());
break;
case CellKind.Data:
this.styleName(this.parent['dataAreaStyleName']());
break;
default:
this.styleName(this.parent['generalStyleName']());
}
stylesProperties.forEach(property => {
if (this['_' + property] && this['_' + property]())
this[property](this['_' + property]());
});
}));
this._disposables.push(this.rowVisible = ko.pureComputed({
read: () => {
const rows = parent._rowDefinitions().slice(this._rowIndex(), this._rowIndex() + this._rowSpan());
return !rows.every(x => !x.visible());
},
write: (value) => {
for (let i = 0; i < this._rowSpan(); i++) {
parent._rowDefinitions()[this._rowIndex() + i].visible(value);
}
}
}));
this._disposables.push(this.columnVisible = ko.pureComputed({
read: () => {
const columns = parent._columnDefinitions().slice(this._columnIndex(), this._columnIndex() + this._columnSpan());
return !columns.every(x => !x.visible());
},
write: (value) => {
for (let i = 0; i < this._columnSpan(); i++) {
parent._columnDefinitions()[this._columnIndex() + i].visible(value);
}
}
}));
this.rowAutoHeightMode = parent._rowDefinitions()[this._rowIndex()].autoHeightMode;
this.columnAutoWidthMode = parent._columnDefinitions()[this._columnIndex()].autoWidthMode;
this.crossTabSortBySummaryInfo.getPath = (propertyName) => this.getPath(propertyName);
}
reset() {
this.dataLevel = undefined;
this.columnLevel = undefined;
this.rowLevel = undefined;
}
canRemove() {
return this.kind() === CellKind.Data && this.parent.dataFields().length > 0
|| this.kind() === CellKind.RowHeader && this.parent.rowFields().length > 0
|| this.kind() === CellKind.ColumnHeader && this.parent.columnFields().length > 0;
}
canDropDown() {
return this.fieldName() && (this.kind() === CellKind.Data && (this.parent.dataFields().length < 2
|| this.parent.layoutOptions.dataFieldLayout() === DataFieldLayout[DataFieldLayout.InColumn])
|| this.kind() === CellKind.ColumnHeader);
}
canDropRight() {
return this.fieldName() && (this.kind() === CellKind.Data && (this.parent.dataFields().length < 2
|| this.parent.layoutOptions.dataFieldLayout() === DataFieldLayout[DataFieldLayout.InRow])
|| this.kind() === CellKind.RowHeader);
}
canDropUp() {
return this.fieldName() && this.kind() === CellKind.ColumnHeader;
}
canDropLeft() {
return this.fieldName() && this.kind() === CellKind.RowHeader;
}
getExpressionBinding(property, event) {
if (this.isBindable()) {
return this.text();
}
return super.getExpressionBinding(property, event);
}
isPropertyVisible(name, isPopularProperty = false) {
if (name == 'textFormatString')
return !this.isIndependant();
if (name == 'visible' || name == 'location' || name == 'canPublish')
return false;
if (name == 'fieldName')
return this.isBindable();
if (name == 'summaryType' || name == 'summaryDisplayType')
return this.kind() === CellKind.Data;
if (name == 'text' && isPopularProperty)
return !this.isPropertyDisabled(name);
if (stylesInfo.some(x => x.propertyName === name))
return false;
if (name == 'sortOrder' ||
name == 'crossTabGroupInterval' ||
name == 'crossTabGroupIntervalNumericRange' ||
name == 'crossTabSortBySummaryInfo')
return this.kind() === CellKind.RowHeader || this.kind() === CellKind.ColumnHeader;
return super.isPropertyVisible(name);
}
isPropertyModified(name) {
if (name === 'columnIndex' || name === 'rowIndex')
return false;
return super.isPropertyModified(name);
}
isPropertyDisabled(name) {
if (name === 'text')
return !this.isEditable();
if (name === 'crossTabGroupInterval' ||
name === 'sortOrder' ||
name === 'summaryType' ||
name === 'summaryDisplayType' ||
name === 'crossTabSortBySummaryInfo')
return !this.fieldName();
if (name === 'crossTabGroupIntervalNumericRange')
return !this.fieldName() || !this.crossTabGroupInterval() || (this.crossTabGroupInterval() != 'Numeric' &&
this.crossTabGroupInterval() != 'DayAge' &&
this.crossTabGroupInterval() != 'WeekAge' &&
this.crossTabGroupInterval() != 'MonthAge' &&
this.crossTabGroupInterval() != 'YearAge');
return super.isPropertyDisabled(name);
}
isBindable() {
return this.kind() === CellKind.RowHeader || this.kind() === CellKind.ColumnHeader || this.kind() === CellKind.Data;
}
isIndependant() {
return this.kind() == CellKind.Corner ||
this.kind() == CellKind.ColumnTotalHeader && this.columnLevel == undefined ||
this.kind() == CellKind.RowTotalHeader && this.rowLevel == undefined;
}
isEditable() {
return this.kind() == CellKind.Corner || this.kind() == CellKind.DataHeader
|| this.kind() == CellKind.ColumnTotalHeader || this.kind() == CellKind.RowTotalHeader
|| this.kind() == CellKind.EmptyHeader || this.kind() == CellKind.Empty;
}
createAndAssignNewField(fieldName, insertBefore, dataFieldLayout = DataFieldLayout.InRow) {
const increment = insertBefore ? 0 : 1;
switch (this.kind()) {
case CellKind.ColumnHeader:
this.parent.insertNewField('columnFields', this.columnLevel + increment, fieldName);
break;
case CellKind.RowHeader:
this.parent.insertNewField('rowFields', this.rowLevel + increment, fieldName);
break;
case CellKind.Data:
this.parent.insertNewField('dataFields', this.dataLevel + increment, fieldName, dataFieldLayout);
}
}
customizeExpressionCategories(tools, categories) {
const crossTabParameters = this.parent.controlParameters();
const crossTabFields = this.parent.getFields();
const fieldsCategory = categories.filter(item => item.content.name == 'dx-expressioneditor-fields')[0];
if (fieldsCategory) {
delete fieldsCategory.content.data.parameters;
const fields = fieldsCategory.content.data.fields();
if (fields) {
fields.treeListController.customFilter = (path) => {
return crossTabFields.some(x => getFullPath(x.getPath(''), x.fieldName()) === path);
};
}
else {
categories.splice(categories.indexOf(fieldsCategory), 1);
}
}
if (crossTabParameters.length > 0) {
categories.push(this._createParametersExpressionCategory());
}
}
}
XRCrossTabCellViewModel.cellKinds = {
'Header': [CellKind.ColumnHeader, CellKind.ColumnTotalHeader, CellKind.DataHeader, CellKind.RowHeader,
CellKind.RowTotalHeader, CellKind.Corner],
'Total': [CellKind.RowTotal, CellKind.GrandTotal, CellKind.ColumnTotal],
'Data': [CellKind.Data]
};
export class XRCellsurface extends XRTextControlSurfaceBase {
constructor(control, context) {
super(control, context);
this.controls = null;
this.contenttemplate = 'dxrd-crosstab-control-content';
this.dropRect = new Rectangle();
this.isDropTarget = ko.observable(false);
this.dragCss = ko.observable('dxrd-drag-helper-item-allowed');
this.selectiontemplate = 'dxrd-crosstab-cell';
this._disposables.push(this.showDropSurface = ko.computed(() => {
if (!this.underCursor().isOver) {
this.isDropTarget(false);
}
return this.isDropTarget() && this.underCursor().isOver;
}));
}
checkParent(surfaceParent) {
return this.parent === surfaceParent;
}
selectLine(selection, event = { ctrlKey: false, metaKey: false }, isRow = false) {
this.parent.selectLine(selection, this.getControlModel(), event.ctrlKey || event.metaKey, isRow);
}
cellClick() {
if (controlsFactory && controlsFactory()) {
const metadata = controlsFactory().controlsMap['XRCrossTabCell'];
const isBindable = this._control.canRemove();
if (isBindable) {
metadata.isDeleteDeny = false;
}
else if (!isBindable) {
metadata.isDeleteDeny = true;
}
}
}
isEditable() {
const control = this._control;
return control.isEditable();
}
_getDropCallback(insertBefore = false, dataFieldLayout = DataFieldLayout.InRow) {
return (item) => {
const control = this._control;
control.createAndAssignNewField(item.path, insertBefore, dataFieldLayout);
};
}
_canSetFieldName(fullPath) {
const crossTab = this._control.parentModel();
const path = getFullPath(crossTab.getPath(''), crossTab['dataMember']());
const pathLength = path.split('.').length;
return fullPath.indexOf(path) == 0
&& (pathLength < 2 || fullPath.split('.').length - pathLength == 1);
}
getAdornTemplate() {
let result = super.getAdornTemplate();
if (this._context['validationMode'] && this._context['validationMode']())
result = XRControlSurface._appendValue(result, 'dxrd-image-surface-bounded-notvalid', this._control['isBindable']() && this._control['fieldName']() && !this._control['fieldNameAreValid']());
result = XRControlSurface._appendValue(result, 'dxrd-image-surface-bounded', this._control['isBindable']() && this._control['fieldName']() && this._control['fieldNameAreValid']);
result = XRControlSurface._appendValue(result, 'dxrd-surface-hidden', !this._control['columnVisible']() || !this._control['rowVisible']());
return result;
}
dragCallback(item) {
const control = this._control;
const crossTab = control.parentModel();
const rect = this.dropRect;
this.isDropTarget(true);
rect.left(-1);
rect.top(-1);
rect.width(this._width() - 3);
rect.height(this._height() - 3);
this.dropCallback = () => { };
if ((control.kind() == CellKind.ColumnHeader || control.kind() == CellKind.RowHeader || control.kind() == CellKind.Data) && this._canSetFieldName(item.path)) {
this.dragCss('dxrd-drag-helper-item-allowed');
const cursor = { x: this.underCursor().x, y: this.underCursor().y };
if (rect.height() - cursor.y < 7 && control.canDropDown()) {
rect.top(rect.height() - 3);
rect.height(7);
this.dropCallback = this._getDropCallback(false, DataFieldLayout.InColumn);
}
else if (rect.width() - cursor.x < 7 && control.canDropRight()) {
rect.left(rect.width() - 3);
rect.width(7);
this.dropCallback = this._getDropCallback(false);
}
else if (cursor.x < 7 && control.canDropLeft()) {
if (control._columnIndex() != 0) {
rect.height(crossTab.cells().filter((x) => x._columnIndex() == control._columnIndex() - 1 && x.kind() == control.kind())[0].surface._height() - 3);
if (crossTab.layoutOptions.rowTotalsPosition() === TotalsPosition[TotalsPosition.BeforeData]) {
rect.top(rect.top() - rect.height() + this._height() - 4);
}
}
else {
const grandTotal = crossTab.cells().filter((x) => x.kind() === CellKind.RowTotalHeader && x.rowLevel === undefined)[0];
rect.height(grandTotal.surface._y() + grandTotal.surface._height() - this._y() - 3);
if (crossTab.layoutOptions.rowTotalsPosition() === TotalsPosition[TotalsPosition.BeforeData]) {
rect.top(rect.top() - (this._y() - grandTotal.surface._y()));
rect.height(this._y() - grandTotal.surface._y() + this._height() - 4);
}
}
this.dropCallback = this._getDropCallback(true);
rect.left(-6);
rect.width(7);
}
else if (cursor.y < 7 && control.canDropUp()) {
if (control._rowIndex() != 0) {
rect.width(crossTab.cells().filter((x) => x._rowIndex() == control._rowIndex() - 1 && x.kind() == control.kind())[0].surface._width() - 3);
if (crossTab.layoutOptions.columnTotalsPosition() === TotalsPosition[TotalsPosition.BeforeData]) {
rect.left(rect.left() - rect.width() + this._width() - 4);
}
}
else {
const grandTotal = crossTab.cells().filter((x) => x.kind() === CellKind.ColumnTotalHeader && x.columnLevel === undefined)[0];
rect.width(grandTotal.surface._x() + grandTotal.surface._width() - this._x() - 3);
if (crossTab.layoutOptions.columnTotalsPosition() === TotalsPosition[TotalsPosition.BeforeData]) {
rect.left(rect.left() - (this._x() - grandTotal.surface._x()));
rect.width(this._x() - grandTotal.surface._x() + this._width() - 4);
}
}
this.dropCallback = this._getDropCallback(true);
rect.top(-6);
rect.height(7);
}
else {
this.dropCallback = (item) => {
if (!control.field()) {
this._getDropCallback(true)(item);
return;
}
control.field()['setFieldName'](item.path);
};
}
}
else {
this.dragCss('dxrd-drag-helper-item-forbidden');
}
}
findNextSelection() {
return this.parent;
}
}