Skip to content

Commit

Permalink
feat(core): support beforeprint and afterprint hooks
Browse files Browse the repository at this point in the history
Extends the PrintHook service to also register beforeprint and afterprint event handlers to synchronously update styles and prevent layout switching races.

Related angular#603
  • Loading branch information
epelc committed Jun 7, 2019
1 parent 31cb34e commit 6a370ae
Showing 1 changed file with 59 additions and 6 deletions.
65 changes: 59 additions & 6 deletions src/lib/core/media-marshaller/print-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Inject, Injectable} from '@angular/core';
import {Inject, Injectable, PLATFORM_ID} from '@angular/core';

import {mergeAlias} from '../add-alias';
import {MediaChange} from '../media-change';
import {BreakPoint} from '../breakpoints/break-point';
import {LAYOUT_CONFIG, LayoutConfigOptions} from '../tokens/library-config';
import {BreakPointRegistry, OptionalBreakPoint} from '../breakpoints/break-point-registry';
import {sortDescendingPriority} from '../utils/sort';
import { isPlatformBrowser } from '@angular/common';

/**
* Interface to apply PrintHook to call anonymous `target.updateStyles()`
Expand All @@ -39,7 +40,8 @@ export const BREAKPOINT_PRINT = {
export class PrintHook {
constructor(
protected breakpoints: BreakPointRegistry,
@Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions) {
@Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions,
@Inject(PLATFORM_ID) protected _platformId: Object) {
}

/** Add 'print' mediaQuery: to listen for matchMedia activations */
Expand Down Expand Up @@ -83,18 +85,65 @@ export class PrintHook {
return mergeAlias(event, bp);
}


// registeredBeforeAfterPrintHooks tracks if we registered the `beforeprint`
// and `afterprint` event listeners.
private registeredBeforeAfterPrintHooks: boolean = false;

// isPrintingBeforeAfterEvent is used to track if we are printing from within
// a `beforeprint` event handler. This prevents the typicall `stopPrinting`
// form `interceptEvents` so that printing is not stopped while the dialog
// is still open. This is an extension of the `isPrinting` property on
// browsers which support `beforeprint` and `afterprint` events.
private isPrintingBeforeAfterEvent: boolean = false;

// registerBeforeAfterPrintHooks registers a `beforeprint` event hook so we can
// trigger print styles synchronously and apply proper layout styles.
// It is a noop if the hooks have already been registered or the platform is
// not a browser(fallsback to mql print media queries).
private registerBeforeAfterPrintHooks(target: HookTarget) {
if (!isPlatformBrowser(this._platformId) || this.registeredBeforeAfterPrintHooks) {
return;
}

this.registeredBeforeAfterPrintHooks = true;

// Could we have teardown logic to remove if there are no print listeners being used?
(<Window>window).addEventListener('beforeprint', () => {
// If we aren't already printing, start printing and update the styles as
// if there was a regular print `MediaChange`(from matchMedia).
if (!this.isPrinting) {
this.isPrintingBeforeAfterEvent = true;
this.startPrinting(target, this.getEventBreakpoints(new MediaChange(true, PRINT)));
target.updateStyles();
}
});

(<Window>window).addEventListener('afterprint', () => {
// If we aren't already printing, start printing and update the styles as
// if there was a regular print `MediaChange`(from matchMedia).
this.isPrintingBeforeAfterEvent = false;
if (this.isPrinting) {
this.stopPrinting(target);
target.updateStyles();
}
});
}

/**
* Prepare RxJs filter operator with partial application
* @return pipeable filter predicate
*/
interceptEvents(target: HookTarget) {
this.registerBeforeAfterPrintHooks(target);

return (event: MediaChange) => {
if (this.isPrintEvent(event)) {
if (event.matches && !this.isPrinting) {
this.startPrinting(target, this.getEventBreakpoints(event));
target.updateStyles();

} else if (!event.matches && this.isPrinting) {
} else if (!event.matches && this.isPrinting && !this.isPrintingBeforeAfterEvent) {
this.stopPrinting(target);
target.updateStyles();
}
Expand Down Expand Up @@ -131,7 +180,8 @@ export class PrintHook {
/**
* To restore pre-Print Activations, we must capture the proper
* list of breakpoint activations BEFORE print starts. OnBeforePrint()
* is not supported; so 'print' mediaQuery activations must be used.
* is supported; so 'print' mediaQuery activations are used as a fallback
* in browsers without `beforeprint` support.
*
* > But activated breakpoints are deactivated BEFORE 'print' activation.
*
Expand All @@ -146,14 +196,17 @@ export class PrintHook {
* - restore as activatedTargets and clear when stop printing
*/
collectActivations(event: MediaChange) {
if (!this.isPrinting) {
if (!this.isPrinting || this.isPrintingBeforeAfterEvent) {
if (!event.matches) {
const bp = this.breakpoints.findByQuery(event.mediaQuery);
if (bp) { // Deactivating a breakpoint
this.deactivations.push(bp);
this.deactivations.sort(sortDescendingPriority);
}
} else {
} else if (!this.isPrintingBeforeAfterEvent) {
// Only clear deactivations if we aren't printing from a `beforeprint` event.
// Otherwise this will clear before `stopPrinting()` is called to restore
// the pre-Print Activations.
this.deactivations = [];
}
}
Expand Down

0 comments on commit 6a370ae

Please sign in to comment.