Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

Commit

Permalink
feat(core): add print support with mediaQuery override
Browse files Browse the repository at this point in the history
Implement PrintHook service to intercept print mediaQuery activation events.
  * suspend activation changes in MediaMarshaller while print-mode is active
  * trigger MediaObserver to notify subscribers with print mqAlias
  * suspend activation changes in MediaMarshaller while print-mode is active
  * trigger MediaObserver to notify subscribers with print mqAlias
  • Loading branch information
ThomasBurleson committed Dec 22, 2018
1 parent 350e753 commit 72998b2
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 37 deletions.
15 changes: 5 additions & 10 deletions src/apps/demo-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FlexLayoutModule, BREAKPOINT} from '@angular/flex-layout';
import {FlexLayoutModule} from '@angular/flex-layout';

import {RoutingModule} from './routing.module';
import {AppComponent} from './app.component';
import {DemoMaterialModule} from './material.module';

const PRINT_BREAKPOINTS = [{
alias: 'xs.print',
suffix: 'XsPrint',
mediaQuery: 'print and (max-width: 297px)',
overlapping: false
}];

@NgModule({
declarations: [
AppComponent,
Expand All @@ -23,9 +16,11 @@ const PRINT_BREAKPOINTS = [{
BrowserAnimationsModule,
RoutingModule,
DemoMaterialModule,
FlexLayoutModule.withConfig({useColumnBasisZero: false}),
FlexLayoutModule.withConfig({
useColumnBasisZero: false,
printWithBreakpoint: 'md'
}),
],
providers: [{provide: BREAKPOINT, useValue: PRINT_BREAKPOINTS, multi: true}],
bootstrap: [AppComponent]
})
export class AppModule { }
6 changes: 6 additions & 0 deletions src/apps/demo-app/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ body {
font-size: 16px;
line-height: 1.4;
-webkit-font-smoothing: antialiased;
-webkit-print-color-adjust: exact !important;
}

@page {
size: auto; /* auto is the initial value */
margin: 2cm;
}

h3 {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/breakpoints/break-point-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {BreakPoint} from './break-point';
import {BREAKPOINTS} from './break-points-token';
import {sortAscendingPriority} from './breakpoint-tools';

type OptionalBreakPoint = BreakPoint | null;
export type OptionalBreakPoint = BreakPoint | null;

/**
* Registry of 1..n MediaQuery breakpoint ranges
Expand All @@ -30,7 +30,7 @@ export class BreakPointRegistry {
* Search breakpoints by alias (e.g. gt-xs)
*/
findByAlias(alias: string): OptionalBreakPoint {
return this.findWithPredicate(alias, (bp) => bp.alias == alias);
return !alias ? null : this.findWithPredicate(alias, (bp) => bp.alias == alias);
}

findByQuery(query: string): OptionalBreakPoint {
Expand Down
39 changes: 27 additions & 12 deletions src/lib/core/media-marshaller/media-marshaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';


import {merge, Observable, Subject, Subscription} from 'rxjs';
import {filter} from 'rxjs/operators';

Expand All @@ -15,6 +17,8 @@ import {BreakPointRegistry} from '../breakpoints/break-point-registry';
import {MatchMedia} from '../match-media/match-media';
import {MediaChange} from '../media-change';

import {PrintHook, HookTarget} from './print-hook';

type ClearCallback = () => void;
type UpdateCallback = (val: any) => void;
type Builder = UpdateCallback | ClearCallback;
Expand Down Expand Up @@ -42,18 +46,19 @@ export class MediaMarshaller {
private activatedBreakpoints: BreakPoint[] = [];
private elementMap: ElementMap = new Map();
private elementKeyMap: ElementKeyMap = new WeakMap();
// registry of special triggers to update elements
private watcherMap: WatcherMap = new WeakMap();
private builderMap: BuilderMap = new WeakMap();
private clearBuilderMap: BuilderMap = new WeakMap();
private watcherMap: WatcherMap = new WeakMap(); // special triggers to update elements
private updateMap: BuilderMap = new WeakMap(); // callback functions to update styles
private clearMap: BuilderMap = new WeakMap(); // callback functions to clear styles

private subject: Subject<ElementMatcher> = new Subject();

get activatedBreakpoint(): string {
return this.activatedBreakpoints[0] ? this.activatedBreakpoints[0].alias : '';
}

constructor(protected matchMedia: MatchMedia,
protected breakpoints: BreakPointRegistry) {
protected breakpoints: BreakPointRegistry,
protected hook: PrintHook) {
this.observeActivations();
}

Expand All @@ -63,6 +68,7 @@ export class MediaMarshaller {
*/
activate(mc: MediaChange) {
const bp: BreakPoint | null = this.findByQuery(mc.mediaQuery);

if (bp) {
if (mc.matches && this.activatedBreakpoints.indexOf(bp) === -1) {
this.activatedBreakpoints.push(bp);
Expand All @@ -89,9 +95,11 @@ export class MediaMarshaller {
updateFn?: UpdateCallback,
clearFn?: ClearCallback,
extraTriggers: Observable<any>[] = []): void {

initBuilderMap(this.updateMap, element, key, updateFn);
initBuilderMap(this.clearMap, element, key, clearFn);

this.buildElementKeyMap(element, key);
initBuilderMap(this.builderMap, element, key, updateFn);
initBuilderMap(this.clearBuilderMap, element, key, clearFn);
this.watchExtraTriggers(element, key, extraTriggers);
}

Expand Down Expand Up @@ -151,8 +159,9 @@ export class MediaMarshaller {

/** Track element value changes for a specific key */
trackValue(element: HTMLElement, key: string): Observable<ElementMatcher> {
return this.subject.asObservable()
.pipe(filter(v => v.element === element && v.key === key));
return this.subject
.asObservable()
.pipe(filter(v => v.element === element && v.key === key));
}

/** update all styles for all elements on the current breakpoint */
Expand Down Expand Up @@ -184,7 +193,7 @@ export class MediaMarshaller {
* @param key
*/
clearElement(element: HTMLElement, key: string): void {
const builders = this.clearBuilderMap.get(element);
const builders = this.clearMap.get(element);
if (builders) {
const clearFn: ClearCallback = builders.get(key) as ClearCallback;
if (!!clearFn) {
Expand All @@ -201,7 +210,7 @@ export class MediaMarshaller {
* @param value
*/
updateElement(element: HTMLElement, key: string, value: any): void {
const builders = this.builderMap.get(element);
const builders = this.updateMap.get(element);
if (builders) {
const updateFn: UpdateCallback = builders.get(key) as UpdateCallback;
if (!!updateFn) {
Expand Down Expand Up @@ -291,11 +300,15 @@ export class MediaMarshaller {
* Watch for mediaQuery breakpoint activations
*/
private observeActivations() {
const target = this as unknown as HookTarget;
const queries = this.breakpoints.items.map(bp => bp.mediaQuery);

this.matchMedia
.observe(queries)
.observe(this.hook.withPrintListener(queries))
.pipe(filter(this.hook.interceptEvents(target)))
.subscribe(this.activate.bind(this));
}

}

function initBuilderMap(map: BuilderMap,
Expand All @@ -311,3 +324,5 @@ function initBuilderMap(map: BuilderMap,
oldMap.set(key, input);
}
}


111 changes: 111 additions & 0 deletions src/lib/core/media-marshaller/print-hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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 {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';

/**
* Interface to apply PrintHook to call anonymous `target.updateStyles()`
*/
export interface HookTarget {
activatedBreakpoints: BreakPoint[];

updateStyles(): void;
}

/**
* PrintHook - Use to intercept print MediaQuery activations and force
* layouts to render with the specified print alias/breakpoint
*
* Used in MediaMarshaller and MediaObserver
*/
@Injectable({providedIn: 'root'})
export class PrintHook {
protected offlineActivations: BreakPoint[] | null = null;

constructor(
protected breakpoints: BreakPointRegistry,
@Inject(LAYOUT_CONFIG) protected layoutConfig: LayoutConfigOptions) {
}

/**
* Add 'print' mediaQuery: to listen for matchMedia activations
*/
withPrintListener(queries: string[]): string[] {
if (!!this.printAlias) {
queries.push('print');
}
return queries;
}

/**
* Is this service currently in Print-mode ?
*/
get isPrinting(): boolean {
return !!this.offlineActivations;
}

/**
* What is the desired mqAlias to use while printing?
*/
get printAlias(): string {
return this.layoutConfig.printWithBreakpoint || '';
}

/**
* Lookup breakpoint associated with print alias.
*/
get printBreakPoint(): OptionalBreakPoint {
return this.breakpoints.findByAlias(this.printAlias!);
}

/**
* Prepare RxJs filter operator with partial application
* @return pipeable filter predicate
*/
interceptEvents(target: HookTarget) {
return (change: MediaChange): boolean => {
if (change.mediaQuery == 'print') {

if (change.matches && !this.isPrinting) {
this.enablePrintMode(target, this.printBreakPoint);
target.updateStyles();
} else if (!change.matches && this.isPrinting) {
this.disablePrintMode(target);
target.updateStyles();
}
}

return !this.isPrinting;
};
}

/**
* Save current activateBreakpoints (for later restore)
* and substitute only the printAlias breakpoint
*/
protected enablePrintMode(target: HookTarget, bp: OptionalBreakPoint) {
if (!!bp) {
this.offlineActivations = target.activatedBreakpoints;
target.activatedBreakpoints = [bp];
}
}

/**
* Restore cached activatedBreakpoints and clear isPrinting
* state
*/
protected disablePrintMode(target: HookTarget) {
target.activatedBreakpoints = this.offlineActivations!;
this.offlineActivations = null;
}

}
33 changes: 22 additions & 11 deletions src/lib/core/media-observer/media-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {filter, map} from 'rxjs/operators';

import {BreakPointRegistry} from '../breakpoints/break-point-registry';
import {mergeAlias} from '../add-alias';
import {MediaChange} from '../media-change';
import {MatchMedia} from '../match-media/match-media';
import {mergeAlias} from '../add-alias';
import {PrintHook} from '../media-marshaller/print-hook';
import {BreakPointRegistry} from '../breakpoints/break-point-registry';

/**
* Class internalizes a MatchMedia service and exposes an Observable interface.
Expand Down Expand Up @@ -64,7 +65,10 @@ export class MediaObserver {
filterOverlaps = true;
readonly media$: Observable<MediaChange>;

constructor(private breakpoints: BreakPointRegistry, private mediaWatcher: MatchMedia) {
constructor(
protected breakpoints: BreakPointRegistry,
protected mediaWatcher: MatchMedia,
protected hook: PrintHook) {
this.media$ = this.watchActivations();
}

Expand Down Expand Up @@ -108,14 +112,21 @@ export class MediaObserver {
* Inject associated (if any) alias information into the MediaChange event
* Exclude mediaQuery activations for overlapping mQs. List bounded mQ ranges only
*/
return this.mediaWatcher.observe(mqList)
.pipe(
filter(change => change.matches),
filter(excludeOverlaps),
map((change: MediaChange) =>
mergeAlias(change, locator.findByQuery(change.mediaQuery))
)
);
return this.mediaWatcher.observe(this.hook.withPrintListener(mqList))
.pipe(
filter(change => change.matches),
filter(excludeOverlaps),
map((change: MediaChange) => {
const bp = (change.mediaQuery === 'print')
? locator.findByAlias(this.hook.printAlias)
: locator.findByQuery(change.mediaQuery);
if (bp) {
change.mediaQuery = bp.mediaQuery;
}

return mergeAlias(change, bp);
})
);
}


Expand Down
1 change: 1 addition & 0 deletions src/lib/core/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from './style-utils/style-utils';
export * from './style-builder/style-builder';
export * from './basis-validator/basis-validator';
export * from './media-marshaller/media-marshaller';
export * from './media-marshaller/print-hook';
2 changes: 2 additions & 0 deletions src/lib/core/tokens/library-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface LayoutConfigOptions {
disableVendorPrefixes?: boolean;
serverLoaded?: boolean;
useColumnBasisZero?: boolean;
printWithBreakpoint?: string;
}

export const DEFAULT_CONFIG: LayoutConfigOptions = {
Expand All @@ -24,6 +25,7 @@ export const DEFAULT_CONFIG: LayoutConfigOptions = {
disableVendorPrefixes: false,
serverLoaded: false,
useColumnBasisZero: true,
printWithBreakpoint: ''
};

export const LAYOUT_CONFIG = new InjectionToken<LayoutConfigOptions>(
Expand Down
6 changes: 4 additions & 2 deletions src/lib/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import {
PLATFORM_ID,
} from '@angular/core';
import {isPlatformServer} from '@angular/common';

import {
SERVER_TOKEN,
LayoutConfigOptions,
LAYOUT_CONFIG,
DEFAULT_CONFIG,
BreakPoint,
BREAKPOINT,
} from '@angular/flex-layout/core';
Expand Down Expand Up @@ -46,11 +48,11 @@ export class FlexLayoutModule {
ngModule: FlexLayoutModule,
providers: configOptions.serverLoaded ?
[
{provide: LAYOUT_CONFIG, useValue: configOptions},
{provide: LAYOUT_CONFIG, useValue: {...DEFAULT_CONFIG, ...configOptions}},
{provide: BREAKPOINT, useValue: breakpoints, multi: true},
{provide: SERVER_TOKEN, useValue: true},
] : [
{provide: LAYOUT_CONFIG, useValue: configOptions},
{provide: LAYOUT_CONFIG, useValue: {...DEFAULT_CONFIG, ...configOptions}},
{provide: BREAKPOINT, useValue: breakpoints, multi: true},
]
};
Expand Down

0 comments on commit 72998b2

Please sign in to comment.