UNPKG

ngx-print

Version:

Plug n' Play Angular (2++) directive to print your stuff

528 lines (519 loc) 19.1 kB
import * as i0 from '@angular/core'; import { inject, CSP_NONCE, Injectable, output, HostListener, Input, Directive, NgModule } from '@angular/core'; import { Subject, take } from 'rxjs'; class PrintBase { nonce = inject(CSP_NONCE, { optional: true }); _iframeElement; _printStyle = []; _styleSheetFile = ''; printComplete = new Subject(); //#region Getters and Setters /** * Sets the print styles based on the provided values. * * @param {Object} values - Key-value pairs representing print styles. * @protected */ setPrintStyle(values) { this._printStyle = []; for (const key of Object.keys(values)) { this._printStyle.push((key + JSON.stringify(values[key])).replace(/['"]+/g, '')); } } /** * * * @returns the string that create the stylesheet which will be injected * later within <style></style> tag. * * -join/replace to transform an array objects to css-styled string */ returnStyleValues() { const styleNonce = this.nonce ? ` nonce="${this.nonce}"` : ''; return `<style${styleNonce}> ${this._printStyle.join(' ').replace(/,/g, ';')} </style>`; } /** * @returns string which contains the link tags containing the css which will * be injected later within <head></head> tag. * */ returnStyleSheetLinkTags() { return this._styleSheetFile; } /** * Sets the style sheet file based on the provided CSS list. * * @param {string} cssList - CSS file or list of CSS files. * @protected */ setStyleSheetFile(cssList) { const linkTagFn = (cssFileName) => { return `<link rel="stylesheet" type="text/css" href="${cssFileName}">`; }; if (cssList.indexOf(',') !== -1) { const valueArr = cssList.split(','); this._styleSheetFile = valueArr.map(val => linkTagFn(val)).join(''); } else { this._styleSheetFile = linkTagFn(cssList); } } //#endregion //#region Private methods used by PrintBase /** * Updates the default values for input elements. * * @param {HTMLCollectionOf<HTMLInputElement>} elements - Collection of input elements. * @private */ updateInputDefaults(elements) { for (const element of Array.from(elements)) { element['defaultValue'] = element.value; if (element['checked']) element['defaultChecked'] = true; } } /** * Updates the default values for select elements. * * @param {HTMLCollectionOf<HTMLSelectElement>} elements - Collection of select elements. * @private */ updateSelectDefaults(elements) { for (const element of Array.from(elements)) { const selectedIdx = element.selectedIndex; if (selectedIdx < 0 || selectedIdx >= element.options.length) continue; const selectedOption = element.options[selectedIdx]; selectedOption.defaultSelected = true; } } /** * Updates the default values for textarea elements. * * @param {HTMLCollectionOf<HTMLTextAreaElement>} elements - Collection of textarea elements. * @private */ updateTextAreaDefaults(elements) { for (const element of Array.from(elements)) { element['defaultValue'] = element.value; } } /** * Converts a canvas element to an image and returns its HTML string. * * @param {HTMLCanvasElement} element - The canvas element to convert. * @returns {string} - HTML string of the image. * @private */ canvasToImageHtml(element) { const dataUrl = element.toDataURL(); return `<img src="${dataUrl}" style="max-width: 100%;">`; } /** * Includes canvas contents in the print section via img tags. * * @param {HTMLCollectionOf<HTMLCanvasElement>} elements - Collection of canvas elements. * @private */ updateCanvasToImage(elements) { for (const canvasElement of Array.from(elements)) { const imgHtml = this.canvasToImageHtml(canvasElement); canvasElement.insertAdjacentHTML('afterend', imgHtml); canvasElement.remove(); } } /** * Retrieves the HTML content of a specified printing section. * * @param {string} printSectionId - Id of the printing section. * @returns {string | null} - HTML content of the printing section, or null if not found. * @private */ getHtmlContents(printSectionId) { const printContents = document.getElementById(printSectionId); if (!printContents) return null; const inputEls = printContents.getElementsByTagName('input'); const selectEls = printContents.getElementsByTagName('select'); const textAreaEls = printContents.getElementsByTagName('textarea'); const canvasEls = printContents.getElementsByTagName('canvas'); this.updateInputDefaults(inputEls); this.updateSelectDefaults(selectEls); this.updateTextAreaDefaults(textAreaEls); this.updateCanvasToImage(canvasEls); return printContents.innerHTML; } /** * Retrieves the HTML content of elements with the specified tag. * * @param {keyof HTMLElementTagNameMap} tag - HTML tag name. * @returns {string} - Concatenated outerHTML of elements with the specified tag. * @private */ getElementTag(tag) { const html = []; const elements = document.getElementsByTagName(tag); for (let index = 0; index < elements.length; index++) { html.push(elements[index].outerHTML); } return html.join('\r\n'); } //#endregion notifyPrintComplete() { this.printComplete.next(); } /** * Prints the specified content using the provided print options. * * @param {PrintOptions} printOptions - Options for printing. * @public */ print(printOptions) { if (printOptions.printMethod === 'iframe') { this.printWithIframe(printOptions); } else { this.printWithWindow(printOptions); } } printWithWindow(printOptions) { // If the openNewTab option is set to true, then set the popOut option to an empty string // This will cause the print dialog to open in a new tab. const popOut = printOptions.printMethod === 'tab' ? '' : 'top=0,left=0,height=auto,width=auto'; const popupWin = window.open('', '_blank', popOut); if (!popupWin) { // the popup window could not be opened. console.error('Could not open print window.'); return; } popupWin.document.open(); // Create the HTML structure this.buildPrintDocument(popupWin.document, printOptions); popupWin.document.close(); // Listen for the window closing const checkClosedInterval = setInterval(() => { if (popupWin.closed) { clearInterval(checkClosedInterval); this.notifyPrintComplete(); } }, 500); popupWin.addEventListener('load', () => { if (!printOptions.previewOnly) { setTimeout(() => { popupWin.print(); if (printOptions.closeWindow) popupWin.close(); }, printOptions.printDelay || 0); } }); } printWithIframe(printOptions) { if (this._iframeElement) { this._iframeElement.remove(); } this._iframeElement = document.createElement('iframe'); const iframe = this._iframeElement; iframe.id = 'print-iframe-' + new Date().getTime(); iframe.style.position = 'absolute'; iframe.style.left = '-9999px'; iframe.style.top = '-9999px'; iframe.style.width = '0px'; iframe.style.height = '0px'; document.body.appendChild(iframe); const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (!iframeDoc) { console.error('Could not access iframe document.'); document.body.removeChild(iframe); return; } iframeDoc.open(); const success = this.buildPrintDocument(iframeDoc, printOptions); if (!success) { iframeDoc.close(); document.body.removeChild(iframe); return; } iframeDoc.close(); iframe.onload = () => { const printWindow = iframe.contentWindow; if (!printWindow) { console.error('Could not access iframe window.'); document.body.removeChild(iframe); return; } setTimeout(() => { if (printOptions.previewOnly) { return; } printWindow.focus(); printWindow.print(); const mediaQueryList = printWindow.matchMedia('print'); const listener = (mql) => { if (!mql.matches) { this.notifyPrintComplete(); mediaQueryList.removeEventListener('change', listener); } }; mediaQueryList.addEventListener('change', listener); }, printOptions.printDelay || 0); }; } prepareDocumentComponents(printOptions) { let styles = ''; let links = ''; const baseTag = this.getElementTag('base'); if (printOptions.useExistingCss) { styles = this.getElementTag('style'); links = this.getElementTag('link'); } const printContents = this.getHtmlContents(printOptions.printSectionId); return { styles, links, baseTag, printContents }; } buildPrintDocument(doc, printOptions) { const components = this.prepareDocumentComponents(printOptions); if (!components.printContents) { console.error(`Print section with id ${printOptions.printSectionId} not found.`); return false; } const html = doc.createElement('html'); const head = doc.createElement('head'); const body = doc.createElement('body'); // Set title const title = doc.createElement('title'); title.textContent = printOptions.printTitle || ''; head.appendChild(title); // Add all head content if (components.baseTag) head.innerHTML += components.baseTag; head.innerHTML += this.returnStyleValues(); head.innerHTML += this.returnStyleSheetLinkTags(); head.innerHTML += components.styles; head.innerHTML += components.links; // Set body class and content if (printOptions.bodyClass) body.className = printOptions.bodyClass; body.innerHTML += components.printContents; // Assemble document html.appendChild(head); html.appendChild(body); doc.appendChild(html); return true; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: PrintBase, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: PrintBase, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: PrintBase, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class PrintOptions { printSectionId = ''; printTitle = ''; useExistingCss = false; bodyClass = ''; printMethod = 'window'; previewOnly = false; closeWindow = true; printDelay = 0; constructor(options) { if (options) { Object.assign(this, options); } } } class NgxPrintDirective extends PrintBase { printOptions = new PrintOptions(); /** * Prevents the print dialog from opening on the window * * @memberof NgxPrintDirective */ set previewOnly(value) { this.printOptions = { ...this.printOptions, previewOnly: value }; } /** * * * @memberof NgxPrintDirective */ set printSectionId(value) { this.printOptions = { ...this.printOptions, printSectionId: value }; } /** * * * @memberof NgxPrintDirective */ set printTitle(value) { this.printOptions = { ...this.printOptions, printTitle: value }; } /** * * * @memberof NgxPrintDirective */ set useExistingCss(value) { this.printOptions = { ...this.printOptions, useExistingCss: value }; } /** * A delay in milliseconds to force the print dialog to wait before opened. Default: 0 * * @memberof NgxPrintDirective */ set printDelay(value) { this.printOptions = { ...this.printOptions, printDelay: value }; } /** * Whether to close the window after print() returns. * */ set closeWindow(value) { this.printOptions = { ...this.printOptions, closeWindow: value }; } /** * Class attribute to apply to the body element. * */ set bodyClass(value) { this.printOptions = { ...this.printOptions, bodyClass: value }; } /** * Which PrintMethod (iframe/window/tab) to use. * */ set printMethod(value) { this.printOptions = { ...this.printOptions, printMethod: value }; } /** * * * @memberof NgxPrintDirective */ set printStyle(values) { super.setPrintStyle(values); } /** * @memberof NgxPrintDirective * @param cssList */ set styleSheetFile(cssList) { super.setStyleSheetFile(cssList); } /** * * * @memberof NgxPrintDirective */ print() { super.print(this.printOptions); this.printComplete.pipe(take(1)).subscribe(() => { this.printCompleted.emit(undefined); }); } printCompleted = output(); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.8", type: NgxPrintDirective, isStandalone: true, selector: "[ngxPrint]", inputs: { previewOnly: "previewOnly", printSectionId: "printSectionId", printTitle: "printTitle", useExistingCss: "useExistingCss", printDelay: "printDelay", closeWindow: "closeWindow", bodyClass: "bodyClass", printMethod: "printMethod", printStyle: "printStyle", styleSheetFile: "styleSheetFile" }, outputs: { printCompleted: "printCompleted" }, host: { listeners: { "click": "print()" } }, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintDirective, decorators: [{ type: Directive, args: [{ selector: '[ngxPrint]', standalone: true, }] }], propDecorators: { previewOnly: [{ type: Input }], printSectionId: [{ type: Input }], printTitle: [{ type: Input }], useExistingCss: [{ type: Input }], printDelay: [{ type: Input }], closeWindow: [{ type: Input }], bodyClass: [{ type: Input }], printMethod: [{ type: Input }], printStyle: [{ type: Input }], styleSheetFile: [{ type: Input }], print: [{ type: HostListener, args: ['click'] }], printCompleted: [{ type: i0.Output, args: ["printCompleted"] }] } }); class NgxPrintModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintModule, imports: [NgxPrintDirective], exports: [NgxPrintDirective] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintModule }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintModule, decorators: [{ type: NgModule, args: [{ imports: [NgxPrintDirective], exports: [NgxPrintDirective], }] }] }); /** * Service for handling printing functionality in Angular applications. * Extends the base printing class (PrintBase). * * @export * @class NgxPrintService * @extends {PrintBase} */ class NgxPrintService extends PrintBase { printComplete$ = this.printComplete.asObservable(); /** * Initiates the printing process using the provided print options. * * @param {PrintOptions} printOptions - Options for configuring the printing process. * @memberof NgxPrintService * @returns {void} */ print(printOptions) { // Call the print method in the parent class super.print(printOptions); } /** * Sets the print style for the printing process. * * @param {{ [key: string]: { [key: string]: string } }} values - A dictionary representing the print styles. * @memberof NgxPrintService * @setter */ set printStyle(values) { super.setPrintStyle(values); } /** * Sets the stylesheet file for the printing process. * * @param {string} cssList - A string representing the path to the stylesheet file. * @memberof NgxPrintService * @setter */ set styleSheetFile(cssList) { super.setStyleSheetFile(cssList); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: NgxPrintService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /* * Public API Surface of ngx-print */ /** * Generated bundle index. Do not edit. */ export { NgxPrintDirective, NgxPrintModule, NgxPrintService, PrintOptions }; //# sourceMappingURL=ngx-print.mjs.map