devexpress-reporting
Version:
DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.
456 lines (455 loc) • 21.6 kB
JavaScript
/**
* DevExpress HTML/JS Reporting (designer\bands\xrBand.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 { Point, SurfaceElementBase } from '@devexpress/analytics-core/analytics-elements';
import { checkModelReady, deserializeChildArray, DragDropHandler, getFullPath, HoverInfo, roundingXDecimals, unitsToPixel } from '@devexpress/analytics-core/analytics-internal';
import * as ko from 'knockout';
import { stylesInfo, stylesObj } from '../controls/metadata/properties/style';
import { Anchoring } from '../controls/properties/anchoring';
import { isVerticalBand } from '../controls/utils/_controlTypes';
import { isHeaderOrFooterBandType } from '../controls/utils/_headOrFooterBandType';
import { getExistTableOfContents } from '../controls/utils/_tocUtils';
import { XRReportElementViewModel } from '../controls/xrReportelement';
import { createObjectFromInfo } from '../internal/_createObjectFromInfo';
import { controlsFactory } from '../utils/settings';
import { bandSurfaceCollapsedHeight } from './bandSurfaceCollapsedHeight';
import { pageBreak, printAcrossBands, repeatEveryPage } from './metadata/bandsMetadata';
import { BandsHolder } from './_bandHolder';
import { generateArray, initLevels, insertBand, sortBands, _getUnitAbsoluteRect } from './_bandUtils';
import { PrintAcrossBandsPlaceHolder } from './_printAcrossBandsPlaceHolder';
export class BandViewModel extends XRReportElementViewModel {
dispose() {
super.dispose();
this.disposeObservableArray(this.bands);
this.disposeObservableArray(this.controls);
this.resetObservableArray(this.bands);
this.resetObservableArray(this.controls);
}
createChildsArray(band, serializer) {
const subBands = [];
if (band.SubBands) {
Object.keys(band.SubBands).forEach((key) => {
subBands.push(this.getControlFactory().createControl({
'@ControlType': 'SubBand',
...band.SubBands[key]
}, this, serializer));
});
}
if (subBands) {
initLevels(subBands);
subBands.sort(sortBands);
}
this.bands = ko.observableArray(subBands);
this.controls = deserializeChildArray(band.Controls, this, (control) => { return this.createControl(control, serializer); });
}
initHeight() {
let _heightFromControls = 0;
this._disposables.push(this.heightFromControls = ko.pureComputed(() => {
_heightFromControls = 0;
if (checkModelReady(this.root)) {
for (let i = 0; i < this.controls().length; i++) {
if (!this.controls()[i].update()) {
const controlY = this.controls()[i].anchorVertical && this.controls()[i].anchorVertical() === 'Bottom' && this.controls()[i].vertAnchoring.state !== Anchoring.states.fromControls ? 0 : this.controls()[i].location.y(), controlHeight = this.controls()[i].anchorVertical && this.controls()[i].anchorVertical() === 'Both' && this.controls()[i].vertAnchoring.state !== Anchoring.states.fromControls ? 1 : this.controls()[i].size.height(), controlBottom = controlY + controlHeight;
if (controlBottom > _heightFromControls) {
_heightFromControls = controlBottom;
}
}
}
_heightFromControls = roundingXDecimals(_heightFromControls);
this.height(Math.max(_heightFromControls, this.height()));
_heightFromControls = _heightFromControls > 0 ? _heightFromControls : 0;
}
return _heightFromControls;
}));
}
preInit(band, parent, serializer) {
}
_getMaxLevel() {
const getSiblingBandsCount = (controlType) => this.parentModel().bands().filter(x => {
return x.controlType === controlType;
}).length;
if (this.controlType === 'GroupHeaderBand' || this.controlType === 'GroupFooterBand') {
return Math.max(getSiblingBandsCount('GroupHeaderBand'), getSiblingBandsCount('GroupFooterBand')) - 1;
}
return getSiblingBandsCount(this.controlType) - 1;
}
constructor(band, parent, serializer) {
super(band, parent, serializer);
this.preInit(band, parent, serializer);
this.createChildsArray(band, serializer);
this.initHeight();
this.size.height = this.height;
if (this.level) {
this._disposables.push(this.maxLevel = ko.pureComputed(() => this._getMaxLevel()));
this._level = ko.observable(this.level.peek());
this._disposables.push(this.level = ko.pureComputed({
read: () => { return this._level(); },
write: (newVal) => {
newVal > this.maxLevel() && (newVal = this.maxLevel());
const parentModel = this.parentModel();
const parentBands = parentModel.bands;
const groupArray = generateArray(parentBands(), this.controlType, newVal);
groupArray.splice(newVal, 0, groupArray.splice(this._level(), 1)[0]);
this._level(newVal);
for (let i = newVal + 1, level = newVal + 1; i < groupArray.length; i++) {
groupArray[i] && groupArray[i]._level(level++);
}
for (let i = newVal - 1, level = newVal - 1; i >= 0; i--) {
groupArray[i] && groupArray[i]._level(level--);
}
parentBands.sort((left, right) => {
if (left.controlType === this.controlType && right.controlType === this.controlType) {
return this.controlType === 'GroupHeaderBand' ? right.level() - left.level() : left.level() - right.level();
}
return 0;
});
}
}));
}
const stylesObject = createObjectFromInfo(this, stylesInfo);
if (stylesObject) {
this[stylesObj.propertyName] = stylesObject;
}
}
addChild(control) {
if (control instanceof BandViewModel && control.isAllowedParent(this)) {
insertBand(this.bands, control);
return;
}
if (control.controlType === 'XRTableOfContents' && isHeaderOrFooterBandType(this)) {
const tocAlreadyExists = !!getExistTableOfContents(this);
if (tocAlreadyExists) {
throw new Error('Only one TOC can be added!!!');
}
}
super.addChild(control);
}
getPath(propertyName) {
if (propertyName === 'dataMember') {
return this.dsHelperProvider() && this.dsHelperProvider().getDataSourcePath(this['dataSource']());
}
else if (propertyName === 'groupFields') {
return getFullPath(this.parentModel().getPath('dataMember'), this.parentModel()['dataMember']());
}
return super.getPath(propertyName);
}
initSize() {
this.size.height = this.height;
this._disposables.push(this.size.width = ko.computed({
read: () => {
return this.root.size.width() - (this.root['margins'] ? ((this.root['margins'].left && this.root['margins'].left()) + (this.root['margins'].right && this.root['margins'].right())) : 0);
}, write: (newVal) => void 0
}));
this.size.isPropertyDisabled = (name) => { return name === 'width' || name === 'height' && ko.unwrap(controlsFactory().getPropertyInfo('DetailBand', 'height').disabled); };
this.size.isPropertyVisible = (name) => { return name !== 'height' || ko.unwrap(controlsFactory().getPropertyInfo('DetailBand', 'height').visible) !== false; };
}
initialize() {
super.initialize();
this.initSize();
}
removeChild(control) {
if (control instanceof BandViewModel) {
if (this.bands().indexOf(control) !== -1) {
this.bands.splice(this.bands().indexOf(control), 1);
}
}
else {
super.removeChild(control);
}
}
static isReorderingBand(control) {
return ['GroupHeaderBand', 'GroupFooterBand', 'DetailReportBand', 'SubBand'].indexOf(control.controlType) > -1;
}
isAllowedParent(target) {
return false;
}
_isHeaderBandTypeOrThemSubBands(band) {
const _isHeader = (band) => band.controlType === 'PageHeaderBand' || band.controlType === 'GroupHeaderBand';
return _isHeader(band) || (this.controlType === 'SubBand' && _isHeader(band.parentModel()));
}
isPropertyVisible(name) {
if (name === printAcrossBands.propertyName) {
return this._isHeaderBandTypeOrThemSubBands(this);
}
else if (name === pageBreak.propertyName) {
return this.controlType === 'SubBand' || !this._isHeaderBandTypeOrThemSubBands(this) || this[printAcrossBands.propertyName];
}
else {
return super.isPropertyVisible(name);
}
}
isPropertyDisabled(name) {
if (name === 'dataMember' && this['dataSource']) {
return this['dataSource']() === null;
}
else if (name === repeatEveryPage.propertyName) {
return this[printAcrossBands.propertyName] && this[printAcrossBands.propertyName]();
}
else if (name === printAcrossBands.propertyName) {
return !!this[repeatEveryPage.propertyName] && this[repeatEveryPage.propertyName]() ||
!!this.parentModel().bands().filter(x => isVerticalBand(x.controlType)).length ||
(!!this[pageBreak.propertyName] && (this[pageBreak.propertyName]() === 'AfterBand' || this[pageBreak.propertyName]() === 'AfterBandExceptLastEntry'));
}
else {
return super.isPropertyDisabled(name);
}
}
}
BandViewModel.unitProperties = ['height'];
export class BandSurface extends SurfaceElementBase {
_getMarginWidth(margins, rtl, isFarMargin = true) {
const marginWidht = margins ? (isFarMargin && this._context.rtl() ? margins.left && margins.left() : margins.right && margins.right()) || 0 : 0;
return unitsToPixel(marginWidht, this._context.measureUnit(), this._context.zoom());
}
dispose() {
super.dispose();
this.disposeObservableArray(this.controls);
this.resetObservableArray(this.controls);
}
_isHeaderBandTypeOrThemSubBands() {
const band = this.getControlModel();
const _isHeader = (band) => band.controlType === 'PageHeaderBand' || band.controlType === 'GroupHeaderBand';
return _isHeader(band) || (band.controlType === 'SubBand' && _isHeader(band.parentModel()));
}
_getUnitPositionInParent() {
let isVerticalBandTakenIntoAccount = false;
const neighbors = this._control.parentModel().bands();
const absoluteY = neighbors
.slice(0, neighbors.indexOf(this._control))
.reduce((sum, currentBandModel) => {
if (isVerticalBand(currentBandModel.controlType) && isVerticalBandTakenIntoAccount)
return sum;
else if (isVerticalBand(currentBandModel.controlType))
isVerticalBandTakenIntoAccount = true;
return sum + currentBandModel.size.height();
}, 0);
return new Point(0, absoluteY);
}
get _unitAbsoluteRect() {
return _getUnitAbsoluteRect(this, () => this._getUnitPositionInParent());
}
_getGrayArea() {
if (this.multiColumn && this.multiColumn()) {
return this.multiColumn().grayAreaWidth() + (this.multiColumn().columnSpacing() || 0);
}
return 0;
}
createChildCollection(band) {
this._disposables.push(this.bandsHolder = new BandsHolder(this));
this.bandsHolder.initialize(band.bands);
}
createUnderCursor() {
this.underCursor = ko.observable(new HoverInfo());
}
getTotalHeight() {
return this._height() + this.bandsHolder.getTotalHeight();
}
getHeight() {
if (this.collapsed())
return bandSurfaceCollapsedHeight;
else
return this._height() + this.subBandsHeight();
}
getHasOwnRuler() {
return true;
}
getBackgroundRect() {
let top = 0, bottom, height = this._height();
const parent = this.parent;
if (!parent) {
return { top, bottom, height };
}
const parentBands = ko.unwrap(parent.bandsHolder.bands);
const parentBackgroundRect = ko.unwrap(parent.backgroundRect);
if (parentBackgroundRect) {
top += parentBackgroundRect.top;
bottom = parentBackgroundRect.bottom;
}
else {
const pageHeight = parent.pageHeight();
const bottomMargin = parent.margins.bottom();
const footer = parentBands.filter(function (x) { return x._control.controlType === 'PageFooterBand'; })[0];
bottom = pageHeight - bottomMargin;
if (footer)
bottom -= footer._totalHeight();
}
const bandIndex = parentBands.indexOf(this);
if (parent.bandsHolder.verticalBandsContainer.visible && parent.bandsHolder.verticalBandsContainer.bandPosition() <= bandIndex) {
top += parent.bandsHolder.verticalBandsContainer._height();
}
for (let i = 0; i < bandIndex; i++) {
top += parentBands[i]._totalHeight();
}
if (top > bottom)
height = 0;
else if (top + height > bottom)
height = bottom - top;
return { top, bottom, height };
}
_initMultiColumn() {
this._disposables.push(this.multiColumn = ko.computed(() => {
const currentMultiColumn = this.parent && this.parent.bandsHolder.multiColumn();
const parentMultiColumn = this.parent && !(this.parent['_control'].controlType === 'DevExpress.XtraReports.UI.XtraReport') && this.parent.parent.bandsHolder.multiColumn();
if (parentMultiColumn && parentMultiColumn.haveColumns())
return parentMultiColumn;
else if (currentMultiColumn && currentMultiColumn.haveColumns()
&& (this.getControlModel().controlType === 'GroupHeaderBand' ||
this.getControlModel().controlType === 'GroupFooterBand' ||
this.getControlModel().controlType === 'DetailReportBand')) {
return currentMultiColumn;
}
}));
}
constructor(band, context, unitProperties = BandSurface._unitProperties) {
super(band, context, unitProperties);
this.isSomeParentCollapsed = ko.observable(false);
this._resize = (delta, oldDelta) => {
this._height(this._height() + delta - oldDelta);
return delta;
};
this.showMarker = true;
this.templateName = 'dxrd-band';
this.selectionTemplate = 'dxrd-band-selection';
this.vrulerTemplate = 'dxrd-band-vruler';
this.contentSelectionTemplate = 'dxrd-bandselection-content';
this.leftMarginTemplate = 'dxrd-band-coordinate-grid';
this.leftMarginSelectionTemplate = 'dxrd-band-coordinate-grid-selection';
this.allowMultiselect = false;
this.markerWidth = ko.observable(bandSurfaceCollapsedHeight);
this.collapsed = ko.observable(false);
this._disposables.push(ko.computed(() => {
this._width(context.pageWidth() - context.margins.left());
}));
this._disposables.push(this.collapsed = ko.pureComputed({
read: () => {
return !band.expanded();
},
write: (newVal) => {
band.expanded(!newVal);
}
}));
this._disposables.push(this._totalHeight = ko.pureComputed(() => this.getTotalHeight()));
this.name = band.name;
const subBandsHeight = 0;
this._disposables.push(this.subBandsHeight = ko.pureComputed(() => this.bandsHolder.getHeight()));
this._disposables.push(this.heightWithoutSubBands = ko.pureComputed(() => {
return this.height() - this.subBandsHeight();
}));
this._disposables.push(this.height = ko.pureComputed(() => this.getHeight()));
this._initMultiColumn();
this.createChildCollection(band);
this.createUnderCursor();
this._disposables.push(this.hasOwnRuler = ko.pureComputed(() => this.getHasOwnRuler()));
this._disposables.push(this.rulerHeight = ko.pureComputed(() => {
return this.collapsed() ? bandSurfaceCollapsedHeight : (this.heightWithoutSubBands());
}));
const root = this.getControlModel().root;
const nearMarginWidth = () => root.margins.right() + root.margins.left();
this.coordinateGridOptions = {
left: ko.pureComputed(() => {
return this.rtlLayout() ? this._context.margins.right() : 0;
}),
height: this.getControlModel().height,
snapGridSize: root.snapGridSize,
zoom: context.zoom,
measureUnit: context.measureUnit,
width: ko.pureComputed(() => root.pageWidth() - nearMarginWidth()),
flip: context.rtl
};
let oldDelta = 0;
this['resize'] = (params) => {
oldDelta = this._resize(params.delta.dh, oldDelta);
};
this['resizeTheBand'] = (params) => {
oldDelta = this._resize(params.delta.dh, oldDelta);
};
this['stopResize'] = () => {
oldDelta = 0;
};
this._disposables.push(this['markerClass'] = ko.pureComputed(() => {
let cssClass = 'dxrd-band-marker-body';
if (band.controlType.toLowerCase().indexOf('header') !== -1 || band.controlType === 'TopMarginBand') {
cssClass = 'dxrd-band-marker-header';
}
else if (band.controlType.toLowerCase().indexOf('footer') !== -1 || band.controlType === 'BottomMarginBand') {
cssClass = 'dxrd-band-marker-footer';
}
if (this.focused()) {
return cssClass += '-focused';
}
return cssClass;
}));
this._disposables.push(this['leftMargin'] = ko.pureComputed(() => {
return 0 - (context['margins'] && context.margins.left() || 0) + 10;
}));
this._disposables.push(this.canResize = ko.computed(() => {
return this.selected() && !this.locked && !this.collapsed() && !DragDropHandler.started();
}));
this._disposables.push(this.minHeight = ko.computed(() => {
const minHeight = (this.heightFromControls && this.heightFromControls() || 0) + this.subBandsHeight();
return minHeight || 1;
}));
this.getUsefulRect = () => {
let usefulWidth = this.rect().width;
const margins = this.getControlModel().root['margins'];
usefulWidth -= this._getMarginWidth(margins, this._context.rtl());
usefulWidth -= this._getGrayArea();
if (this.rtlLayout()) {
const nearMarginWidth = this._getMarginWidth(margins, this._context.rtl(), false);
const left = this.container().rect().width - usefulWidth - nearMarginWidth;
return { top: 0, left: left, right: usefulWidth + nearMarginWidth, bottom: this.height(), width: usefulWidth, height: this.height() };
}
else {
return { top: 0, left: 0, right: usefulWidth, bottom: this.height(), width: usefulWidth, height: this.height() };
}
};
this._disposables.push(this.backgroundRect = ko.pureComputed(() => this.getBackgroundRect()));
if (this._isHeaderBandTypeOrThemSubBands()) {
this.printAcrossBands = band['printAcrossBands'];
this._disposables.push(this.printAcrossBandsPlaceHolder = new PrintAcrossBandsPlaceHolder(this));
}
}
getAbsolutePositionY() {
return this.parent.bandsHolder.getBandAbsolutePositionY(this);
}
updateAbsolutePosition() {
if (!this.parent)
return;
const parent = this.parent;
this.absolutePosition.x(0);
if (ko.unwrap(parent['collapsed'])) {
this.absolutePosition.y(parent['absolutePosition'].y());
return;
}
this.absolutePosition.y(this.getAbsolutePositionY());
}
markerClick(selection, changeCollapsed = true) {
if (selection.expectClick) {
selection.expectClick = false;
return;
}
if (!this.focused() && !selection.disabled()) {
selection.initialize(this);
}
else {
if (changeCollapsed)
this.collapsed(!this.collapsed());
}
}
canDrop() { return super.canDrop() && !this.collapsed(); }
get parent() {
return this._getParent();
}
get zoom() { return this.getRoot().zoom; }
checkParent(surfaceParent) {
return false;
}
}
BandSurface._unitProperties = {
_height: (o) => { return o.height; },
heightFromControls: (o) => { return o.heightFromControls; }
};