UNPKG

devexpress-reporting

Version:

DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.

644 lines (643 loc) 32.3 kB
/** * DevExpress HTML/JS Reporting (designer\controls\xrControl.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 { Point, Size, SurfaceElementBase } from '@devexpress/analytics-core/analytics-elements'; import { deserializeChildArray, DxDeferred, getFirstItemByPropertyValue } from '@devexpress/analytics-core/analytics-internal'; import { getLocalization, PathRequest } from '@devexpress/analytics-core/analytics-utils'; import { FontModel } from '@devexpress/analytics-core/analytics-widgets-internal'; import * as ko from 'knockout'; import { BandSurface } from '../bands/xrBand'; import { VerticalBandSurface } from '../bands/xrVerticalBand'; import { createObjectFromInfo, findFirstParentWithPropertyName } from '../internal/_createObjectFromInfo'; import { DataBindingMode } from '../internal/_dataBindingMode'; import { getNearestBand } from './getNearestBand'; import { stylesInfo, stylesObj } from './metadata/properties/style'; import { HorizontalAnchoring, VerticalAcnhoring } from './properties/anchoring'; import { SortingOptions } from './properties/sortingOptions'; import { XRReportElementViewModel } from './xrReportelement'; import { ActionTypeBase, NavigateToReportAction } from './properties/action'; const patchCanPublish = (model) => { if (model['@CanPublish']) { model['@CanPublishOptions'] = model['@CanPublish'] === 'false' ? 'None' : 'All'; delete model['@CanPublish']; } return model; }; var FieldNameOptions; (function (FieldNameOptions) { FieldNameOptions[FieldNameOptions["DisplayName"] = 0] = "DisplayName"; FieldNameOptions[FieldNameOptions["RealName"] = 1] = "RealName"; })(FieldNameOptions || (FieldNameOptions = {})); export class XRControlViewModel extends XRReportElementViewModel { dispose() { super.dispose(); this.disposeObservableArray(this.controls); this.resetObservableArray(this.controls); } anchoring(parent) { if (parent) { this.vertAnchoring = new VerticalAcnhoring(parent.size.height, this, this.anchorVertical); this.horAnchoring = new HorizontalAnchoring(parent.size.width, this, this.anchorHorizontal); this._disposables.push(this.vertAnchoring); this._disposables.push(this.horAnchoring); } } beforeDeserialize(model, parent, serializer) { patchCanPublish(model); } getFieldName(value, fieldNameOption) { const reportModel = this.root; const dnp = reportModel?.['displayNameProvider']?.(); if (!dnp) { return null; } const startIndex = value.indexOf('['); const endIndex = value.indexOf(']'); const dataMember = value.substring(startIndex + 1, endIndex); const dataSourceInfo = reportModel.dataSource?.()?.['dataSourceInfo']; const dataSourceRef = dataSourceInfo?.ref || dataSourceInfo?.id; if (dataSourceRef != null) { switch (fieldNameOption) { case FieldNameOptions.DisplayName: return { dataMember, namePromise: dnp.getDisplayNameByPath(`${dataSourceRef}.${reportModel.dataMember()}`, dataMember) }; case FieldNameOptions.RealName: return { dataMember, namePromise: dnp.getRealName(`${dataSourceRef}.${reportModel.dataMember()}`, dataMember) }; } } } setTextValue(fieldNamePromise, originText, replacedText, propertyToSet) { if (!fieldNamePromise) propertyToSet(originText); fieldNamePromise.done((result) => { const newValue = originText.replace(replacedText, result); propertyToSet(newValue); }).fail(() => { propertyToSet(originText); }); } constructor(control, parent, serializer) { super(control, parent, serializer); if (this.text) { this._realText = this.text; this._textArea = this.text; this._textUI = ko.observable(''); this._disposables.push(this.text = ko.computed({ read: () => this._realText(), write: newTextValue => { const fieldName = this.getFieldName(newTextValue, FieldNameOptions.RealName); if (!fieldName) { this._realText(newTextValue); return; } this.setTextValue(fieldName.namePromise, newTextValue, fieldName.dataMember, this._realText); } })); this._disposables.push(this.textUI = ko.computed({ read: () => { const textValue = this.text(); if (!textValue) return this._textUI(); const fieldName = this.getFieldName(textValue, FieldNameOptions.DisplayName); if (!fieldName) return textValue; this.setTextValue(fieldName.namePromise, textValue, fieldName.dataMember, this._textUI); return this._textUI(); }, write: newTextValue => { this.text(newTextValue); } })); this._disposables.push(this.textArea = ko.computed({ read: () => this.textUI(), write: newTextValue => { this.text(newTextValue); } })); } this.controls = deserializeChildArray(control.Controls, this, (childControl) => { return this.createControl(childControl, serializer); }); if (this.anchorVertical || this.anchorHorizontal) { this.anchoring(parent); this._disposables.push(this.parentModel.subscribe((newParent) => { if (this.vertAnchoring && this.horAnchoring && newParent) { this.vertAnchoring.start(newParent.size.height, this); this.horAnchoring.start(newParent.size.width, this); } else { this.anchoring(newParent); } })); } if (this.interactiveSorting) { this.interactiveSorting = new SortingOptions(control['InteractiveSorting'], this.root, serializer); this._disposables.push(this.interactiveSorting); } this._disposables.push(this.hasBindings = ko.pureComputed(() => { const bindings = this.dataBindings && this.dataBindings(); return !!bindings && bindings.filter((dataBinding) => { return !dataBinding.isEmpty(); }).length > 0 || this.hasExpressionBindings(); })); const stylesObject = createObjectFromInfo(this, stylesInfo); if (stylesObject) { this[stylesObj.propertyName] = stylesObject; } if (this['Summary']) this['Summary'].isPropertyVisible = (propertyName) => { return propertyName === 'Func' ? this.isPropertyVisible('dataBindings') : true; }; if (this.textFormatString) { const binding = this['dataBindings'] && this['dataBindings']().filter(binding => binding.propertyName() === 'Text')[0]; let summaryFormatString = null; let bindingFormatString = null; this['_textFormatString'] = ko.observable(this.textFormatString.peek()); [summaryFormatString, bindingFormatString] = [this['Summary'], binding].map(obj => { if (obj && obj['formatString']) { obj['_formatString'] = ko.observable(obj['formatString'].peek()); this._disposables.push(obj['formatString'] = ko.computed({ read: () => this['_textFormatString']() || obj['_formatString'](), write: x => { this.textFormatString(x); } })); return obj['_formatString']; } }); this._disposables.push(this.textFormatString = ko.computed({ read: () => this['_textFormatString']() || summaryFormatString && summaryFormatString() || bindingFormatString && bindingFormatString(), write: x => { this['_textFormatString'](x); summaryFormatString && summaryFormatString(null); bindingFormatString && bindingFormatString(null); } })); } if (control.Action) { if (control.Action['@Name'].indexOf('NavigateToReport') !== -1) { this.action = ko.observable(new NavigateToReportAction(this.name(), control.Action, this, serializer, (report, serializer) => this.root.createReportViewModel(report, serializer))); } } else { this.action = ko.observable(new ActionTypeBase(this, this.name(), true)); } this.dataBindingsAreValid = ko.observable(true); this._disposables.push(ko.computed(() => { const bindings = this.dataBindings && this.dataBindings(); if (bindings) { if (bindings.length === 0) this.dataBindingsAreValid(true); else { const report = this.root; if (!report || !(report.controlType === 'DevExpress.XtraReports.UI.XtraReport')) return; const dsHelper = report.dsHelperProvider(); const parameters = report.parameters(); const fieldListProvider = report.getControlFactory().fieldListProvider(); if (!dsHelper || !parameters || !fieldListProvider) return; const defaultDataSourceInfo = dsHelper.findDataSourceInfo(ko.unwrap(findFirstParentWithPropertyName(this, 'dataSource').dataSource)); let reqFinished = 0; const reqCount = bindings.length; let reqResult = true; const deferred = new DxDeferred(); for (let i = 0; i < bindings.length; i++) { const binding = bindings[i], parameter = binding.parameter(), dataMember = binding.dataMember(), dataSource = binding.dataSource(); if (parameter) { if (dataSource) reqResult = false; else reqResult = reqResult && parameters.some(x => x.name === parameter.name); reqFinished++; } else if (dataMember) { let dsInfo = null; if (dataSource) { dsInfo = dataSource['dataSourceInfo']; if (dsHelper.usedDataSources().indexOf(dsInfo) < 0) { reqResult = false; reqFinished++; } } else { dsInfo = defaultDataSourceInfo; } if (dsInfo) { const lastPart = dataMember.slice(dataMember.lastIndexOf('.') + 1); fieldListProvider.getItems(new PathRequest([dsInfo.id || dsInfo.ref].concat(...dataMember.split('.').slice(0, -1)).join('.'))) .done(result => { if (result.every(x => x.isList || x.name !== lastPart)) reqResult = false; }) .fail(() => { reqResult = false; }) .always(() => { if (++reqFinished === reqCount) deferred.resolve(reqResult); }); } else { reqResult = false; reqFinished++; } } else if (dataSource) { reqResult = false; reqFinished++; } else reqFinished++; if (!reqResult) { reqFinished += reqCount - 1 - i; break; } } if (reqFinished === reqCount) deferred.resolve(reqResult); deferred.done(result => { this.dataBindingsAreValid(result); }); } } })); } isIntersectionEnabled() { return !this.root.designerOptions ? true : this.root.designerOptions?.showExportWarnings(); } getNearestParent(target) { if (['XRPageBreak', 'XRPivotGrid', 'XRSubreport', 'XRTableOfContents', 'XRPdfContent'].indexOf(this.controlType) !== -1) { return getNearestBand(target); } else { return super.getNearestParent(target); } } isPropertyDisabled(name) { if (super.isPropertyDisabled(name)) { return true; } if (name === 'textFitMode') { return this['canGrow']() || this['canShrink']() || (this.controlType === 'XRLabel' && this['autoWidth']()); } else if (name === 'processNullValues') { return this['Summary'] && ko.unwrap(this['Summary']['Running']) !== 'None'; } else if (name === 'allowMarkupText') { return this['textEditOptions'] && ko.unwrap(this['textEditOptions']['enabled']); } else if (name === 'textEditOptions' || name === 'angle') { return this['allowMarkupText'] && ko.unwrap(this['allowMarkupText']); } } isPropertyVisible(name) { if (this.multiline && this.multiline()) { if (name === 'text') return false; } else { if (name === 'textArea') return false; } return super.isPropertyVisible(name); } hasExpressionBindings() { return !!(this.expressionBindings && this.expressionBindings().filter(binding => !!binding.expression())[0]); } hasDataBindingByName(property = 'Text') { const bindings = this.dataBindings && this.dataBindings() && this.dataBindings().filter((dataBinding) => { return !dataBinding.isEmpty(); }); if (!!bindings && bindings.length > 0) { const binding = this.dataBindings()['findBinding'](property); return !!binding && !binding.isEmpty(); } return !!(this.expressionBindings && this.expressionBindings().filter(binding => binding.propertyName() === property).length > 0); } get hasDefaultBindingProperty() { return !!this.getControlInfo().defaultBindingName; } getExpressionBinding(property = 'Text', event = 'BeforePrint') { if (!this.expressionBindings) return null; const binding = this.expressionBindings().filter(binding => binding.propertyName() === property && binding.eventName() === event)[0]; return binding && binding.expression(); } setExpressionBinding(value, property = 'Text', event = 'BeforePrint') { if (!this.expressionBindings) return; const binding = this.expressionBindings().filter(binding => binding.propertyName() === property && binding.eventName() === event)[0]; binding && binding.expression(value); } getControlInfo() { return super.getControlInfo(); } getDefaultBinding() { const bindingName = this.getControlInfo().defaultBindingName; if (this.dataBindingMode !== DataBindingMode.Bindings) { return this.expressionObj.getExpression(bindingName, 'BeforePrint'); } else { return this.dataBindings().filter(x => x.propertyName() === bindingName)[0]; } } } export class XRControlSurfaceBase extends SurfaceElementBase { _isThereIntersectionWithUsefulArea(useFullWidth) { const absoluteRect = this._unitAbsoluteRect; return Math.max(absoluteRect.left, absoluteRect.right) - useFullWidth > this.delta; } static _appendValue(accumulator, value, needToAppend = true) { if (needToAppend) { accumulator += accumulator ? (' ' + value) : value; } return accumulator; } get _unitAbsoluteRect() { const parentAbsoluteRect = this.parent && this.parent['_unitAbsoluteRect']; if (parentAbsoluteRect) { return { top: parentAbsoluteRect.top + this._unitRect.top, left: parentAbsoluteRect.left + this._unitRect.left, right: parentAbsoluteRect.left + this._unitRect.left + this._unitRect.width, bottom: parentAbsoluteRect.top + this._unitRect.top + this._unitRect.height, width: this._unitRect.width, height: this._unitRect.height }; } else { return this._unitRect; } } get _unitRect() { const location = this._control['location'] || new Point(0, 0), size = this._control['size'] || new Size(0, 0); return { top: location.y(), left: location.x(), right: location.x() + size.width(), bottom: location.y() + size.height(), width: size.width(), height: size.height() }; } constructor(control, context, unitProperties) { super(control, context, unitProperties); this.delta = 0.0001; this.template = 'dxrd-control'; this.selectiontemplate = 'dxrd-control-selection'; this.contenttemplate = 'dxrd-control-content'; this.displayNameParameters = ko.pureComputed(() => { const control = this.getControlModel(); const parameters = { text: null, isExpression: true, dataSource: null, dataMember: null, dataMemberOffset: null, allowMarkupText: false, wordWrap: false, fontSize: 0, fontUnit: null }; if (control['controls'] && control['controls']().length !== 0) { parameters.text = ''; return parameters; } parameters.text = control['getExpressionBinding'] && control['getExpressionBinding'](); parameters.isExpression = !!parameters.text; parameters.allowMarkupText = control['allowMarkupText'] && control['allowMarkupText'](); parameters.wordWrap = control['wordWrap'] && control['wordWrap'](); if (control['font']) { const _font = new FontModel(control['font']); parameters.fontSize = _font.size(); parameters.fontUnit = _font.unit(); _font.dispose(); } if (parameters.isExpression) { parameters.dataMember = control['getPath'] && control['getPath']('expression') || ''; return parameters; } if (control['dataBindings'] && this.hasBindings) { const textBinding = getFirstItemByPropertyValue(control['dataBindings'](), 'propertyName', 'Text'); if (textBinding && textBinding.dataMember()) { const dataMember = textBinding.dataMember(); const dataSource = textBinding.dataSource(); const parentWithDS = findFirstParentWithPropertyName(control, 'dataSource'); const rootDataMember = parentWithDS['dataMember'] && parentWithDS['dataMember']() || ''; const rootDataSource = parentWithDS['dataSource'] && parentWithDS['dataSource']() || null; if ((!dataSource || dataSource === rootDataSource) && dataMember.indexOf(rootDataMember) === 0 && dataMember.charAt(rootDataMember.length) === '.') { parameters.dataMemberOffset = rootDataMember; parameters.dataMember = dataMember.substr(rootDataMember.length + 1); } else { parameters.dataMemberOffset = ''; parameters.dataMember = textBinding.dataMember(); } parameters.dataSource = dataSource || rootDataSource; return parameters; } } parameters.text = this.displayText(); return parameters; }); this.displayName = ko.pureComputed(() => { const parameters = this.displayNameParameters(); return parameters.dataMember ? ('[' + parameters.dataMember + ']') : (parameters.text || ''); }); this._disposables.push(this.contentSizes = ko.pureComputed(() => this.cssCalculator.contentSizeCss(this.rect().width, this.rect().height, this._context.zoom()))); this._disposables.push(this.contentHeightWithoutZoom = ko.pureComputed(() => this.contentSizes().height / this._context.zoom())); this._disposables.push(this.contentWidthWithoutZoom = ko.pureComputed(() => this.contentSizes().width / this._context.zoom())); this._disposables.push(this.borderCss = ko.pureComputed(() => { return (!control['borders'] || control['borders']() === 'None') ? { 'border': 'solid 1px Silver' } : this.cssCalculator.borderCss(this._context.zoom()); })); this._disposables.push(this.isIntersect = ko.pureComputed(() => { if (control instanceof XRControlViewModel && !control.isIntersectionEnabled()) { return false; } return this.isThereIntersectionWithUsefulArea() || this.isThereIntersectionWithCrossBandControls() || this.isThereIntersectionWithControls(); }).extend({ deferred: true })); this._disposables.push(this.adorntemplate = ko.computed(() => { return this.getAdornTemplate(); })); this._disposables.push(this.displayNameParameters); this._disposables.push(this.displayName); this.showDropBorders = ko.observable(false); } checkParent(surfaceParent) { const thisParent = this.parent instanceof BandSurface || this.parent && this.parent._control.controlType === 'DevExpress.XtraReports.UI.XtraReport' ? null : this.parent; const anotherParent = surfaceParent instanceof BandSurface || this.parent && this.parent._control.controlType === 'DevExpress.XtraReports.UI.XtraReport' ? null : surfaceParent; return thisParent === anotherParent; } isThereIntersection(rect1, rect2) { const rect1Right = rect1.right || rect1.left + rect1.width, rect2Right = rect2.right || rect2.left + rect2.width, rect1Bottom = rect1.bottom || rect1.top + rect1.height, rect2Bottom = rect2.bottom || rect2.top + rect2.height; return rect1Right > rect2.left && Math.abs(rect1Right - rect2.left) >= 0.0001 && rect2Right > rect1.left && Math.abs(rect2Right - rect1.left) >= 0.0001 && rect1Bottom > rect2.top && Math.abs(rect1Bottom - rect2.top) >= 0.0001 && rect2Bottom > rect1.top && Math.abs(rect2Bottom - rect1.top) >= 0.0001; } isThereIntersectionWithParent(parentRect, childRect) { const rectWidhtElement = childRect.right || childRect.left + childRect.width, rectHeightElement = childRect.bottom || childRect.top + childRect.height; return rectWidhtElement > parentRect.width && Math.abs(rectWidhtElement - parentRect.width) > this.delta || rectHeightElement > parentRect.height && Math.abs(rectHeightElement - parentRect.height) > this.delta; } isThereIntersectionWithUsefulArea() { const _container = this.container(); if (_container instanceof BandSurface && _container['_unitAbsoluteRect']) { const absoluteRect = this.container()['_unitAbsoluteRect']; return this._isThereIntersectionWithUsefulArea(absoluteRect.width); } else if (_container instanceof VerticalBandSurface) { return false; } else { const root = this.getRoot(), usefulPageWidth = root['_unitAbsoluteRect'].width; return this._isThereIntersectionWithUsefulArea(usefulPageWidth); } } isThereIntersectionWithCrossBandControls(currentRect = this._unitAbsoluteRect) { if (!currentRect) return false; let isThereIntersection = false; const crossBandControls = this.getRoot()['crossBandControls'](); if (this.isThereIntersectionWithNeighborsCollection(currentRect, crossBandControls.filter((control) => { return control.visible() && control.getControlModel().controlType === 'XRCrossBandLine'; }), '_unitAbsoluteRect')) { return true; } const crossBandBoxControls = crossBandControls.filter((control) => { return control.visible() && control.getControlModel().controlType === 'XRCrossBandBox'; }); for (let crossbandIndex = 0; crossbandIndex < crossBandBoxControls.length; crossbandIndex++) { const rects = crossBandBoxControls[crossbandIndex]._getCrossBandBoxSides(); for (let rectIndex = 0; rectIndex < rects.length; rectIndex++) { if (this !== crossBandBoxControls[crossbandIndex] && this.isThereIntersection(currentRect, rects[rectIndex])) { isThereIntersection = true; break; } } if (isThereIntersection) break; } return isThereIntersection; } isThereIntersectionWithControls() { const collectionControls = this.parent && this.parent.getChildrenCollection() && this.parent.getChildrenCollection()().filter((control) => { return !control.isIntersectionDeny; }) || []; return this.isThereIntersectionWithParentCollection(this._unitRect) || this.isThereIntersectionWithChildCollection() || this.isThereIntersectionWithNeighborsCollection(this._unitRect, collectionControls); } isThereIntersectionWithParentCollection(currentRect, controlRectProperty = '_unitRect') { return this.parent && this.parent instanceof XRControlSurfaceBase && this.parent[controlRectProperty] && this.isThereIntersectionWithParent(this.parent[controlRectProperty], currentRect); } isThereIntersectionWithChildCollection(controlRectProperty = '_unitRect') { return this['controls'] && this['controls']().length > 0 && this.isThereIntersectionWithChildControls(this['controls'](), controlRectProperty); } isThereIntersectionWithNeighborsCollection(currentRect, collectionControls, controlRectProperty = '_unitRect') { for (let i = 0; i < collectionControls.length; i++) { if (this !== collectionControls[i] && this.isThereIntersection(currentRect, collectionControls[i][controlRectProperty])) { return true; } } return false; } isThereIntersectionWithChildControls(collectionControls, controlRectProperty = '_unitRect') { const currentRect = this[controlRectProperty]; for (let i = 0; i < collectionControls.length; i++) { if (this !== collectionControls[i] && this.isThereIntersectionWithParent(currentRect, collectionControls[i][controlRectProperty])) { return true; } } return false; } canSnap() { return true; } getAdornTemplate() { let result = XRControlSurface._appendValue('', 'dxrd-intersect', this.isIntersect()); result = XRControlSurface._appendValue(result, 'dxrd-control-rtl', this._control.rtl()); result = XRControlSurface._appendValue(result, 'dxrd-uiselected', this.selected()); if (this.hasBindings) { if (this._context['validationMode'] && this._context['validationMode']()) { if (!this.bindingsIsValid) { result = XRControlSurface._appendValue(result, 'dxrd-image-surface-bounded-notvalid', true); } else if (this.bindingsHasWarning) { result = XRControlSurface._appendValue(result, 'dxrd-image-surface-bounded-warning', true); } else result = XRControlSurface._appendValue(result, 'dxrd-image-surface-bounded', true); } else result = XRControlSurface._appendValue(result, 'dxrd-image-surface-bounded', true); } if (this._control['visible']) { result = XRControlSurface._appendValue(result, 'dxrd-surface-hidden', !this._control['visible']()); } return result; } hasDataBindingByName(propertyName) { return !!(this._control['hasDataBindingByName'] && this._control['hasDataBindingByName'](propertyName)); } get hasBindings() { return !!(this._control['hasBindings'] && this._control['hasBindings']()); } get bindingsIsValid() { if (this._control['dataBindingMode'] !== 'Bindings') { if (!!this._control['expressionBindings']) { return this._control['expressionObj'].validateExpression(); } return true; } else { return this._control['dataBindingsAreValid'](); } } get bindingsHasWarning() { if (this._control['dataBindingMode'] !== 'Bindings') { if (!!this._control['expressionBindings']) { return this._control['expressionObj'].hasWarning(); } return false; } } displayText() { if (this._control.controlType == 'XRPanel') return getLocalization('Place controls here to keep them together', 'ReportStringId.PanelDesignMsg'); let text = ko.unwrap(this._control['textUI']) || ko.unwrap(this._control['text']) || ''; if (this._control['multiline'] && !this._control['multiline']()) { text = text.replace(/\r/g, '').replace(/\n/g, ''); } return text; } } export class XRControlSurface extends XRControlSurfaceBase { dispose() { super.dispose(); this.disposeObservableArray(this.controls); this.resetObservableArray(this.controls); } constructor(control, context) { super(control, context, XRControlSurface._unitProperties); this['multiline'] = control['multiline'] || false; this.getUsefulRect = () => { const borderWidth = ko.unwrap(control['borderWidth']), borderFlags = control['borders'](); const rect = { top: 0, left: 0, width: this.rect().width, height: this.rect().height }; if (borderWidth) { if (borderFlags === 'All') { rect.height -= 2 * borderWidth; rect.width -= 2 * borderWidth; } else { if (borderFlags.indexOf('Top') >= 0) rect.height -= borderWidth; if (borderFlags.indexOf('Right') >= 0) rect.width -= borderWidth; if (borderFlags.indexOf('Bottom') >= 0) rect.height -= borderWidth; if (borderFlags.indexOf('Left') >= 0) rect.width -= borderWidth; } } return rect; }; } } XRControlSurface._unitProperties = { _height: (o) => { return o.size.height; }, _width: (o) => { return o.size.width; }, _x: (o) => { return o.location.x; }, _y: (o) => { return o.location.y; } };