UNPKG

@progress/kendo-angular-grid

Version:

Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.

293 lines (292 loc) 11.8 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Component, Input, ContentChild, ContentChildren, ElementRef, NgZone, QueryList, Optional } from '@angular/core'; import { delay, take } from 'rxjs/operators'; import { SuspendService } from '../scrolling/suspend.service'; import { PDFService } from './pdf.service'; import { PDFMarginComponent } from './pdf-margin.component'; import { PDFTemplateDirective } from './pdf-template.directive'; import { exportElement, cloneNode } from './export-element'; import { GridQuery } from './grid-query'; import { ColumnBase } from '../columns/column-base'; import { PDFExportComponent } from '@progress/kendo-angular-pdf-export'; import { isDocumentAvailable } from '@progress/kendo-angular-common'; import { ContextService } from '../common/provider.service'; import { GridConfigurationErrorMessages } from '../common/error-messages'; import * as i0 from "@angular/core"; import * as i1 from "./pdf.service"; import * as i2 from "../scrolling/suspend.service"; import * as i3 from "../common/provider.service"; const createElement = (tagName, className) => { const element = document.createElement(tagName); if (className) { element.className = className; } return element; }; const createDiv = (className) => { return createElement('div', className); }; /** * Configures the settings for the export of Grid in PDF ([see example]({% slug pdfexport_grid %})). */ export class PDFComponent extends PDFExportComponent { pdfService; suspendService; ngZone; ctx; /** * Exports all Grid pages, starting from the first one. */ allPages; /** * The delay in milliseconds before exporting the Grid content. * Useful for scenarios which involve exporting complex components used in the Grid * templates such as charts or data-bound components with asynchronous data retrieval ([see example]({% slug pdfexport_grid %}#toc-exporting-all-pages)). * * @default 0 */ delay = 0; columns = new QueryList(); /** * @hidden */ marginComponent; /** * @hidden */ pageTemplateDirective; progress; component; container; skip; pageSize; originalHeight; originalOverflow; saveSubscription; drawSubscription; renderAllPages; originalColumns; constructor(pdfService, suspendService, ngZone, element, ctx) { super(element); this.pdfService = pdfService; this.suspendService = suspendService; this.ngZone = ngZone; this.ctx = ctx; this.saveSubscription = pdfService.savePDF.subscribe(this.savePDF.bind(this)); this.drawSubscription = pdfService.drawPDF.subscribe(this.drawPDF.bind(this)); this.reset = this.reset.bind(this); this.draw = this.draw.bind(this); } ngOnDestroy() { this.saveSubscription.unsubscribe(); this.drawSubscription.unsubscribe(); this.reset(); } /** * @hidden */ saveAs() { throw new Error(GridConfigurationErrorMessages.unsupportedMethod('saveAs', 'GridComponent.saveAsPDF')); } /** * @hidden */ export() { throw new Error(GridConfigurationErrorMessages.unsupportedMethod('export', 'GridComponent.drawPDF')); } savePDF(component) { this.createPDF(component, this.draw); } drawPDF({ component, promise }) { this.createPDF(component, () => { this.createExportGroup(promise); }); } createPDF(component, callback) { const pageSize = component.pageSize; const total = component.view.total; const columns = this.columns.toArray(); if (columns.length) { this.originalColumns = component.columns.toArray(); } this.component = component; this.suspendService.scroll = true; this.pdfService.exporting = true; this.initProgress(); this.renderAllPages = this.allPages && pageSize < total; if (this.renderAllPages) { this.skip = component.skip; this.pageSize = pageSize; this.changePage(0, total, callback, columns); } else if (columns.length || component.virtualColumns) { this.changeColumns(columns, callback); } else { callback(); } } initProgress() { if (!isDocumentAvailable()) { return; } const wrapperElement = this.component.wrapper.nativeElement; const progress = this.progress = createDiv('k-loading-pdf-mask'); const overlay = cloneNode(wrapperElement); progress.appendChild(overlay); progress.appendChild(createDiv('k-loading-color')); progress.appendChild(createElement('span', 'k-i-loading k-icon')); this.originalHeight = wrapperElement.style.height; this.originalOverflow = wrapperElement.style.overflow; wrapperElement.style.height = wrapperElement.offsetHeight + 'px'; wrapperElement.style.overflow = 'hidden'; wrapperElement.appendChild(progress); this.applyScroll(overlay); } applyScroll(overlay) { const query = new GridQuery(this.component.wrapper.nativeElement); const content = query.content(); if (content) { const overlayQuery = new GridQuery(overlay); const overlayContent = overlayQuery.content(); overlayContent.scrollTop = content.scrollTop; overlayContent.scrollLeft = content.scrollLeft; overlayQuery.header().scrollLeft = query.header().scrollLeft; const footer = query.footer(); if (footer) { overlayQuery.footer().scrollLeft = footer.scrollLeft; } const lockedContent = query.content(true); if (lockedContent) { const overlayLockedContent = overlayQuery.content(true); overlayLockedContent.scrollTop = lockedContent.scrollTop; overlayLockedContent.scrollLeft = lockedContent.scrollLeft; } } } draw() { this.createExportElement((element) => { this.save(element, this.fileName); }); } createExportGroup(promise) { this.createExportElement((element) => { this.exportElement(element).then(group => promise.resolve(group)); }); } createExportElement(callback) { this.ngZone.runOutsideAngular(() => { const container = this.container = createDiv('k-grid-pdf-export-element'); const element = exportElement(this.component.wrapper.nativeElement, this.ctx?.grid.size || 'medium'); container.appendChild(element); document.body.appendChild(container); callback(element); }); } drawOptions() { const options = super.drawOptions(); options._destructive = true; return options; } cleanup() { super.cleanup(); this.pdfService.exporting = false; if (this.component) { const originalColumns = this.originalColumns; delete this.originalColumns; if (this.renderAllPages) { this.changePage(this.skip, this.pageSize, this.reset, originalColumns); } else if (originalColumns || this.component.virtualColumns) { this.changeColumns(originalColumns, this.reset); } else { this.reset(); } } else { this.reset(); } this.removeContainer(); } removeContainer() { if (this.container) { document.body.removeChild(this.container); delete this.container; } } changePage(skip, _take, callback, columns) { this.ngZone.run(() => { this.pdfService.dataChanged.pipe(take(1)).subscribe(() => { if ((columns && columns.length) || this.component.virtualColumns) { this.changeColumns(columns, callback); } else { this.onStable(callback); } }); this.component.notifyPageChange('pdf', { skip: skip, take: _take }); }); } changeColumns(columns, callback) { this.ngZone.run(() => { this.onStable(callback); if (columns && columns.length) { this.component.columns.reset(columns); } }); } reset() { this.suspendService.scroll = false; this.renderAllPages = false; if (!this.component) { return; } const wrapperElement = this.component.wrapper.nativeElement; wrapperElement.removeChild(this.progress); wrapperElement.style.height = this.originalHeight; wrapperElement.style.overflow = this.originalOverflow; delete this.progress; delete this.component; } onStable(callback) { // not sure if it is an actual scenario. occurs in the tests. // onStable is triggered in the same pass without the change detection. // thus, the callback is called before the changes are applied without the timeout. setTimeout(() => { let onStable = this.ngZone.onStable.asObservable().pipe(take(1)); if (this.delay > 0) { onStable = onStable.pipe(delay(this.delay)); } onStable.subscribe(callback); }, 0); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PDFComponent, deps: [{ token: i1.PDFService }, { token: i2.SuspendService }, { token: i0.NgZone }, { token: i0.ElementRef }, { token: i3.ContextService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: PDFComponent, isStandalone: true, selector: "kendo-grid-pdf", inputs: { allPages: "allPages", delay: "delay" }, queries: [{ propertyName: "marginComponent", first: true, predicate: PDFMarginComponent, descendants: true }, { propertyName: "pageTemplateDirective", first: true, predicate: PDFTemplateDirective, descendants: true }, { propertyName: "columns", predicate: ColumnBase }], usesInheritance: true, ngImport: i0, template: '', isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PDFComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-grid-pdf', template: '', standalone: true }] }], ctorParameters: function () { return [{ type: i1.PDFService }, { type: i2.SuspendService }, { type: i0.NgZone }, { type: i0.ElementRef }, { type: i3.ContextService, decorators: [{ type: Optional }] }]; }, propDecorators: { allPages: [{ type: Input }], delay: [{ type: Input }], columns: [{ type: ContentChildren, args: [ColumnBase] }], marginComponent: [{ type: ContentChild, args: [PDFMarginComponent, { static: false }] }], pageTemplateDirective: [{ type: ContentChild, args: [PDFTemplateDirective, { static: false }] }] } });