ngx-print
Version:
Plug n' Play Angular (2++) directive to print your stuff
528 lines (519 loc) • 19.1 kB
JavaScript
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