devexpress-reporting
Version:
DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.
410 lines (409 loc) • 19.2 kB
JavaScript
/**
* DevExpress HTML/JS Reporting (designer\controls\xrReportelement.js)
* Version: 25.1.3
* Build date: Jun 26, 2025
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* License: https://www.devexpress.com/Support/EULAs/universal.xml
*/
import { ElementViewModel, Size } from '@devexpress/analytics-core/analytics-elements';
import { formatUnicorn } from '@devexpress/analytics-core/analytics-internal';
import { deserializeArray, getLocalization } from '@devexpress/analytics-core/analytics-utils';
import * as ko from 'knockout';
import { stylesProperties } from '../helpers/_styleHelper';
import { findFirstParentWithPropertyName } from '../internal/_createObjectFromInfo';
import { DataBindingMode } from '../internal/_dataBindingMode';
import { createExpressionEditorAction } from '../internal/_expressionEditorAction';
import { controlsFactory } from '../utils/settings';
import { ReportExpressionEditorWrapper } from '../widgets/expressioneditor/reportExpressionEditorWrapper';
import { getNearestBand } from './getNearestBand';
import { stylePrioritySerializationInfo } from './metadata/properties/metadata';
import { FormattingRuleLink } from './properties/formattingrules';
import { isHeaderOrFooterBandType } from './utils/_headOrFooterBandType';
import { DefaultLocalizationProvider } from './utils/_localizationUtils';
import { createPaddingProperty } from './utils/_paddingUtils';
import { getUnitProperties } from './utils/_initUtils';
export class XRReportElementViewModel extends ElementViewModel {
get _localizationProvider() {
if (!this.__localizationProvider) {
this.__localizationProvider = this.createLocalizationProvider();
}
return this.__localizationProvider;
}
dispose() {
super.dispose();
this.disposeObservableArray(this.dataBindings);
this.resetObservableArray(this.formattingRuleLinks);
this.resetObservableArray(this.dataBindings);
this.__localizationProvider = null;
}
createLocalizationProvider() {
return new DefaultLocalizationProvider(this);
}
getLocalizationProperty(propertyName) {
return this._localizationProvider.getLocalizationProperty(propertyName);
}
getLocalizationProperties() {
return this._localizationProvider.getLocalizationProperties();
}
applyLocalization(propertyName, propertyValue) {
this._localizationProvider.applyLocalization(propertyName, propertyValue);
}
_resetProperty(propertyName) {
super._resetProperty(propertyName);
this._resetExpressions(propertyName);
}
_getControlPropertyName(propertyName) {
propertyName = propertyName === 'paddingObj' ? 'padding' : propertyName;
propertyName = propertyName === 'textArea' ? 'text' : propertyName;
return propertyName;
}
_getStylePriorityPropertyName(propertyName) {
propertyName = this._getControlPropertyName(propertyName);
return 'use' + propertyName.charAt(0).toUpperCase() + propertyName.substr(1);
}
_getStyle(root) {
const styleName = this.styleName && this.styleName(), style = styleName && root && root.findStyle && root.findStyle(styleName);
return style;
}
_checkStylePropertyModify(target, propertyName) {
const property = ko.unwrap(target && (target['_' + propertyName] || target[propertyName]));
return property != null && target.isPropertyModified(propertyName);
}
_getStyleProperty(propertyName, stylePriorityName, root) {
if (this.stylePriority && this.stylePriority[stylePriorityName] && this.stylePriority[stylePriorityName]() || !this._checkStylePropertyModify(this, propertyName)) {
const style = this._getStyle(root);
if (style && this._checkStylePropertyModify(style, propertyName)) {
return style[propertyName]();
}
}
if (this._checkStylePropertyModify(this, propertyName)) {
return this['_' + propertyName]();
}
const defaultValue = this.getPropertyDefaultValue(propertyName);
if (defaultValue && !(defaultValue instanceof Object)) {
return defaultValue;
}
const parent = this.parentModel();
if (parent) {
return parent.getStyleProperty(propertyName, stylePriorityName);
}
}
_zOrderChange(bringToFront) {
const parent = this.parentModel(), controlContainer = parent && parent[this.getControlContainerName()];
if (controlContainer) {
const itemIndex = controlContainer().indexOf(this);
const items = controlContainer();
items.splice(itemIndex, 1);
items.splice((bringToFront ? 0 : controlContainer().length), 0, this);
controlContainer.valueHasMutated();
}
}
_createPaddingDependencies() {
createPaddingProperty(this, this.root);
this.paddingObj['isPropertyHighlighted'] = (propertyName) => {
return this.isPropertyHighlighted(propertyName, 'padding');
};
}
constructor(model, parent, serializer) {
super(model, parent, serializer);
this._expressionActions = {};
this.initialize();
this._syncDpi();
this.formattingRuleLinks = deserializeArray(model.FormattingRuleLinks, (item) => { return new FormattingRuleLink(item, serializer); });
const _generateProperty = (propertyName, stylePriorityName) => {
this['_' + propertyName] = ko.observable(this[propertyName]());
this._disposables.push(this[propertyName] = ko.computed({
read: () => this._getStyleProperty(propertyName, stylePriorityName, this.root),
write: (val) => {
if (this._getStyleProperty(propertyName, stylePriorityName, this.root) !== val) {
if (this.stylePriority && this.stylePriority[stylePriorityName]) {
this.stylePriority[stylePriorityName](false);
}
this['_' + propertyName](val);
}
}
}));
};
this.dsHelperProvider = () => this.root['dataSourceHelper'] && this.root['dataSourceHelper']();
this.initBindings();
this.getStyleProperty = (propertyName, stylePriorityName) => this._getStyleProperty(propertyName, stylePriorityName, this.root);
for (let i = 0; i < stylesProperties.length; i++) {
if (this[stylesProperties[i]]) {
const stylePriorityName = this._getStylePriorityPropertyName(stylesProperties[i]);
_generateProperty(stylesProperties[i], stylePriorityName);
}
}
if (this.padding) {
this._createPaddingDependencies();
}
this.toggleUseStyle = (propertyName) => {
const styleName = this._getStylePriorityPropertyName(propertyName);
this.stylePriority[styleName](!this.stylePriority[styleName]());
};
this.actions.push({ action: this.toggleUseStyle, title: getLocalization('Style Priority', 'DevExpress.XtraReports.UI.XRControl.StylePriority'), visible: name => this.isStyleProperty(name) });
this.actionProviders.push({ getActions: (name) => this._getExpressionActions(name) });
this._disposables.push(this.lockedInUserDesigner = ko.computed({
read: () => {
const parent = this.parentModel(), parentLocked = (parent && parent['lockedInUserDesigner']) ? parent['lockedInUserDesigner']() : false;
return this._lockedInUserDesigner() || parentLocked;
},
write: newValue => this._lockedInUserDesigner(newValue)
}));
}
_syncDpi() {
this._syncDpiCore(this.parentModel());
this._disposables.push(this.parentModel.subscribe((parent) => this._syncDpiCore(parent)));
}
_syncDpiCore(parent) {
if (!parent?.dpi || parent.dpi() === this._innerDpi())
return;
getUnitProperties(this).reCalculateObject(parent.dpi() / this._innerDpi());
this._innerDpi(parent.dpi());
}
_getExpressionActions(name) {
if (!this._expressionActions[name]) {
this._expressionActions[name] = this._addExpressionActions(name);
}
return this._expressionActions[name];
}
_getExpressionEvents() {
const events = [
{ name: 'BeforePrint', localizationId: 'DevExpress.XtraReports.UI.XRControlEvents.OnBeforePrint', displayName: 'BeforePrint' }
];
if (this.dataBindingMode === DataBindingMode.ExpressionsAdvanced) {
events.push({ name: 'PrintOnPage', localizationId: 'DevExpress.XtraReports.UI.XRControlEvents.OnPrintOnPage', displayName: 'PrintOnPage' });
}
return events;
}
_addExpressionActions(propertyName) {
if (this.dataBindingMode === DataBindingMode.Bindings) {
return [];
}
const expressionName = this._getExpressionNameByPropertyName(propertyName);
if (!expressionName)
return [];
const events = this._getExpressionEvents();
const allExpressionsTreeItems = this.expressionObj.getExpressionsTreeItems(expressionName);
if (!allExpressionsTreeItems || !allExpressionsTreeItems.length)
return [];
const expressionForLocalizedString = getLocalization('{0} Expression', 'ReportStringId.UD_PropertyGrid_Menu_ItemExpression');
const convertToMenuAction = (item) => {
const hasInnerItems = item.innerItems && !!item.innerItems.length;
const expressionEditor = hasInnerItems ? null : new ReportExpressionEditorWrapper(ko.observable(this), ko.observable(item.expressionObj));
const currentEventInfo = events.filter(x => x.name === item.eventName)[0];
const eventLocalizedName = currentEventInfo ? getLocalization(currentEventInfo.displayName, currentEventInfo.localizationId) : item.eventName;
const expressionLocalizedName = !item.displayName && !item.localizationId ? item.expressionName : getLocalization(item.displayName, item.localizationId);
const menuAction = createExpressionEditorAction({
expressionEditor,
hasInnerItems,
title: item.eventName ? eventLocalizedName : formatUnicorn(expressionForLocalizedString, expressionLocalizedName),
hint: ko.computed(() => {
return item.expressionObj && item.expressionObj.value();
}),
});
menuAction.items = (item.innerItems || []).map(convertToMenuAction);
this._disposables.push(menuAction.hint);
return menuAction;
};
return allExpressionsTreeItems.map(convertToMenuAction);
}
getControlFactory() {
return controlsFactory();
}
addChild(control) {
if (control.controlType === 'XRTableOfContents' && !isHeaderOrFooterBandType(this)) {
const band = getNearestBand(this);
if (band) {
if (isHeaderOrFooterBandType(band)) {
band.addChild(control);
}
return;
}
else {
throw new Error('TOC can be added only to ReportHeaderBand or ReportFooterBand!!!');
}
}
super.addChild(control);
}
initDataBindingProperties() {
const bindingInfos = this.getInfo().filter(info => 'bindingName' in info);
bindingInfos.forEach(info => {
this[info.propertyName] = this.dataBindings()['findBinding'](info['bindingName']);
});
}
initExpressionProperties() {
if (!this.expressionBindings)
return;
const path = ko.pureComputed(() => {
return this.getPath('expression');
});
this._disposables.push(path);
this.expressionObj = this.getControlFactory()._createExpressionObject(this.controlType, this.expressionBindings, path, (name) => this['Summary'] && this['Summary']['Running'] && this.getControlInfo().defaultBindingName === name && ko.computed(() => {
return this['Summary']['Running']() != 'None';
}));
if (!this.expressionObj)
return;
this._disposables.push(this.expressionObj);
const expressionInfos = this.getInfo().filter(info => 'expressionName' in info);
expressionInfos.forEach(info => {
const expression = this.expressionObj.getExpression(info['expressionName'], 'BeforePrint');
if (expression)
this[info.propertyName] = expression;
});
}
_resetExpressions(propertyName) {
const modelName = this._getExpressionNameByPropertyName(propertyName);
if (!modelName)
return;
this._getExpressionEvents().forEach((event) => {
const expressionPropertyInfo = this.expressionObj.getExpression(modelName, event.name);
if (!expressionPropertyInfo)
return;
if (expressionPropertyInfo['getInfo']) {
(expressionPropertyInfo['getInfo']() || []).forEach(info => {
const value = expressionPropertyInfo[info.propertyName].value;
value && value('');
});
}
else {
expressionPropertyInfo.value && expressionPropertyInfo.value('');
}
});
}
_hasAnyExpressions(propertyName, predicateFunc = (value) => !!ko.unwrap(value)) {
const modelName = this._getExpressionNameByPropertyName(propertyName);
if (!modelName)
return false;
let returnValue = false;
const events = this._getExpressionEvents();
events.forEach((event) => {
const expressionPropertyInfo = this.expressionObj.getExpression(modelName, event.name);
if (!expressionPropertyInfo)
return;
const getInfoAction = expressionPropertyInfo['getInfo'];
let expressionExists = false;
if (getInfoAction) {
const info = getInfoAction() || [];
expressionExists = info.filter(info => {
return predicateFunc(expressionPropertyInfo[info.propertyName].value, info.propertyName);
}).length > 0;
}
else {
expressionExists = predicateFunc(expressionPropertyInfo.value);
}
returnValue = returnValue || expressionExists;
});
return returnValue;
}
_getExpressionNameByPropertyName(propertyName, info = this.getInfo()) {
if (this.dataBindingMode === DataBindingMode.Bindings) {
return '';
}
propertyName = this._getControlPropertyName(propertyName);
const propInfo = this.getInfo().filter(info => info.propertyName === propertyName)[0];
if (!propInfo || !propInfo.modelName) {
return '';
}
return propInfo.modelName.substring(propInfo.modelName.lastIndexOf('@') + 1);
}
initBindings() {
this.initDataBindingProperties();
this.initExpressionProperties();
}
isStyleProperty(propertyName) {
return this.stylePriority && stylePrioritySerializationInfo.some((info) => { return info.propertyName == this._getStylePriorityPropertyName(propertyName); });
}
isResettableProperty(propertyName) {
return super.isResettableProperty(propertyName) && propertyName !== 'dataBindings';
}
getActionClassName(propertyName) {
const result = {};
result['dxrd-editormenu-usestyle'] = this.isStyleProperty(propertyName) && this.stylePriority[this._getStylePriorityPropertyName(propertyName)]();
result['dxrd-editormenu-modified'] = this.isPropertyModified(propertyName);
const hasExpression = this._hasAnyExpressions(propertyName);
result['dxrd-editormenu-expressions'] = hasExpression;
result['dxd-icon-accented'] = hasExpression;
return result;
}
getMenuBoxTemplate(propertyName) {
if (this._hasAnyExpressions(propertyName))
return 'dxrd-svg-tabs-expressions';
return '';
}
className() {
return this.controlType.toLowerCase();
}
initialize() {
if (!this.size) {
this.size = new Size(0, 0);
}
}
getPath(propertyName) {
if (propertyName === 'expression' && this.dsHelperProvider()) {
const firstParentWithDS = findFirstParentWithPropertyName(this, 'dataSource');
const rootDataSourceName = this.dsHelperProvider().getDataSourcePath(ko.unwrap(firstParentWithDS['dataSource']));
const rootDataMember = ko.unwrap(firstParentWithDS['dataMember']) || '';
if (!!rootDataSourceName) {
return !!rootDataMember ? [rootDataSourceName, rootDataMember].join('.') : rootDataSourceName;
}
else {
return '';
}
}
return '';
}
isPropertyDisabled(name) {
return this.lockedInUserDesigner();
}
isPropertyVisible(name) {
if (this.dataBindingMode !== DataBindingMode.Bindings) {
return name !== 'dataBindings'
&& name !== 'formattingRuleLinks'
&& name !== 'formattingRuleSheet'
&& name.indexOf('popularDataBinding') !== 0;
}
else {
return name.indexOf('popularExpression') !== 0;
}
}
isPropertyHighlighted(propertyName, parentPropertyName) {
if (!parentPropertyName) {
return this._hasAnyExpressions(propertyName);
}
const parentExpressionName = this._getExpressionNameByPropertyName(parentPropertyName);
const subPropertyName = propertyName[0].toUpperCase() + propertyName.substr(1);
return parentExpressionName && this._hasAnyExpressions(parentPropertyName, (value, subExpressionName) => {
const unwrappedValue = !!ko.unwrap(value);
return subExpressionName === (parentExpressionName + '.' + subPropertyName) && unwrappedValue;
});
}
sendToBack() {
this._zOrderChange(false);
}
bringToFront() {
this._zOrderChange(true);
}
get root() {
return this._getRoot();
}
getControlContainerName() { return 'controls'; }
customizeExpressionCategories(sender, categories) { }
get dataBindingMode() {
return this.root !== this ? this.root.dataBindingMode : DataBindingMode.Expressions;
}
set dpi(value) {
this._innerDpi = value;
}
get dpi() {
return this.root !== this ? this.root.dpi : this._innerDpi;
}
rtl() {
const rtl = ko.unwrap(this['rightToLeft']);
if (rtl === 'Yes')
return true;
if (this.parentModel() && (!rtl || rtl === 'Inherit'))
return this.parentModel().rtl();
return false;
}
}
XRReportElementViewModel.unitProperties = ['size', 'location', 'paddingObj'];