Skip to content

Commit

Permalink
feat(core): add support for 1..n mediaQuery overrides during printing
Browse files Browse the repository at this point in the history
When printing developers can now configure how layouts should render
by specifying 1..n mediaQuery aliases. This feature allows totally different
print outputs without modifying the currently layout.

Implement PrintHook service to intercept print mediaQuery activation events.
  * add fxShow.print and fxHide.print support to show/hide elements during printing
  * suspend activation changes in MediaMarshaller while print-mode is active
  * trigger MediaObserver to notify subscribers with print mqAlias(es)
  * use PrintHook to intercept activation changes in MediaMarshaller while print-mode is active
  * trigger MediaObserver to notify subscribers with print mqAlias(es)

Using the new `printWithBreakpoint` allows developers to specify a breakpoint that should be used to render layouts only during printing. With the configuration below, the breakpoint associated with the **`md`** alias will be used.

```ts
    FlexLayoutModule.withConfig({
      useColumnBasisZero: false,
      printWithBreakpoints: ['md', 'gt-sm', 'gt-xs']
    })
```
Shown below is the print layout rendered in floating dialog over the normal layout using 'lg' breakpoints.

![angular-layout-printing](https://user-images.githubusercontent.com/210413/50407211-2e04ca00-0798-11e9-8f35-b4e9e2fca864.jpg)

Fixes #603.
  • Loading branch information
ThomasBurleson committed Dec 27, 2018
1 parent 350e753 commit bff6040
Show file tree
Hide file tree
Showing 29 changed files with 859 additions and 245 deletions.
6 changes: 3 additions & 3 deletions docs/documentation/BreakPoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {BREAKPOINT} from '@angular/flex-layout';
const PRINT_BREAKPOINTS = [{
alias: 'xs.print',
suffix: 'XsPrint',
mediaQuery: 'print and (max-width: 297px)',
mediaQuery: 'screen and (max-width: 297px)',
overlapping: false
}];

Expand Down Expand Up @@ -73,7 +73,7 @@ export class MyAppModule {
}
```

With the above changes, when printing on mobile-sized viewports the **`xs.print`** mediaQuery will activate. Please note
With the above changes, when printing on mobile-sized viewports the **`xs.print`** mediaQuery will onMediaChange. Please note
that the provider is a **multi-provider**, meaning it can be provided multiple times and in a variety of
presentations. The type signature of `BREAKPOINT` is the following:

Expand Down Expand Up @@ -157,4 +157,4 @@ export class CustomShowHideDirective extends ShowHideDirective {
this._cacheInput("showXsPrint", negativeOf(val));
}
}
```
```
2 changes: 1 addition & 1 deletion docs/documentation/fxHide-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ e.g.
### Using Responsive API

When a mediaQuery range activates, the directive instances will be notified. If the current activate mediaQuery range
When a mediaQuery range activates, the directive instances will be notified. If the current activated mediaQuery range
(and its associated alias) are not used, then the static API value is restored as the fallback value.

The *fallback* solution uses a **`largest_range-to-smallest_range`** search algorithm. Consider the following:
Expand Down
2 changes: 1 addition & 1 deletion docs/documentation/fxShow-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ e.g.
### Using Responsive API

When a mediaQuery range activates, the directive instances will be notified. If the current activate mediaQuery range
When a mediaQuery range activates, the directive instances will be notified. If the current activated mediaQuery range
(and its associated alias) are not used, then the static API value is restored as the fallback value.

The *fallback* solution uses a **`largest_range-to-smallest_range`** search algorithm. Consider the following:
Expand Down
3 changes: 2 additions & 1 deletion src/apps/demo-app/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ <h2>Layout Demos: </h2>
</span>
</div>
<div fxLayout="row"
fxLayoutAlign="start center"
fxLayoutGap="20px"
fxHide.print
style="height:40px; min-height:40px;">
<button mat-raised-button color="primary" [routerLink]="['']">
Static
Expand All @@ -30,4 +30,5 @@ <h2>Layout Demos: </h2>

<div class="demo-content">
<router-outlet></router-outlet>
<watermark fxHide fxShow.print></watermark>
</div>
18 changes: 7 additions & 11 deletions src/apps/demo-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
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
}];
import {WatermarkComponent} from './watermark.component';

@NgModule({
declarations: [
AppComponent,
AppComponent, WatermarkComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
BrowserAnimationsModule,
RoutingModule,
DemoMaterialModule,
FlexLayoutModule.withConfig({useColumnBasisZero: false}),
FlexLayoutModule.withConfig({
useColumnBasisZero: false,
printWithBreakpoints: ['md', 'gt-sm', 'gt-xs']
}),
],
providers: [{provide: BREAKPOINT, useValue: PRINT_BREAKPOINTS, multi: true}],
bootstrap: [AppComponent]
})
export class AppModule { }
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {Component} from '@angular/core';
@Component({
selector: 'demo-docs-responsive',
template: `
<demo-responsive-layout-direction class='small-demo'> </demo-responsive-layout-direction>
<demo-responsive-layout-direction class='small-demo' fxHide.print>
</demo-responsive-layout-direction>
<demo-responsive-row-column class='small-demo'> </demo-responsive-row-column>
<demo-responsive-flex-directive class='small-demo'> </demo-responsive-flex-directive>
<demo-responsive-flex-order class='small-demo'> </demo-responsive-flex-order>
Expand Down
16 changes: 16 additions & 0 deletions src/apps/demo-app/src/app/watermark.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
:host {
display: block;
position: absolute;

width: 100vw;
min-height: 100vh;
top: 0;
left: 0;
right: 0;
bottom: 0;

div {
width: 100vw;
min-height: 100vh;
}
}
46 changes: 46 additions & 0 deletions src/apps/demo-app/src/app/watermark.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {Component, Input} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';

@Component({
selector: 'watermark',
styleUrls: ['watermark.component.scss'],
template: `
<div [style.background]="backgroundImage">
</div>
`,
})
export class WatermarkComponent {
@Input() title = '@angular/layout';
@Input() message = 'Layout with FlexBox + CSS Grid';

constructor(private _sanitizer: DomSanitizer) {
}

/* tslint:disable:max-line-length */
get backgroundImage() {
const rawSVG = `
<svg id="diagonalWatermark" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%">
<style type="text/css">text { fill: gray; font-family: Avenir, Arial, Helvetica, sans-serif;
opacity: 0.25; }
</style>
<defs>
<pattern id="titlePattern" patternUnits="userSpaceOnUse" width="400" height="200">
<text y="30" font-size="40" id="title">${this.title}</text>
</pattern>
<pattern xlink:href="#titlePattern">
<text y="120" x="200" font-size="30" id="message">${this.message}</text>
</pattern>
<pattern id="combo" xlink:href="#titlePattern" patternTransform="rotate(-45)">
<use xlink:href="#title"/>
<use xlink:href="#message"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#combo)"/>
</svg>
`;
const bkgrndImageUrl = `data:image/svg+xml;base64,${window.btoa(rawSVG)}`;

return this._sanitizer.bypassSecurityTrustStyle(`url('${bkgrndImageUrl}') repeat-y`);
}
}
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
2 changes: 1 addition & 1 deletion src/lib/core/add-alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {extendObject} from '../utils/object-extend';
* and suffix (if available).
*/
export function mergeAlias(dest: MediaChange, source: BreakPoint | null): MediaChange {
return extendObject(dest, source ? {
return extendObject(dest || {}, source ? {
mqAlias: source.alias,
suffix: source.suffix
} : {});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/base/base2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export abstract class BaseDirective2 implements OnChanges, OnDestroy {
}
set activatedValue(value: string) {
this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, value,
this.marshal.activatedBreakpoint);
this.marshal.activatedAlias);
}

/** Cache map for style computation */
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
7 changes: 4 additions & 3 deletions src/lib/core/breakpoints/breakpoint-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {OptionalBreakPoint} from './break-point-registry';
import {BreakPoint} from './break-point';
import {extendObject} from '../../utils/object-extend';

Expand Down Expand Up @@ -65,9 +66,9 @@ export function mergeByAlias(defaults: BreakPoint[], custom: BreakPoint[] = []):
}

/** HOF to sort the breakpoints by priority */
export function sortDescendingPriority(a: BreakPoint, b: BreakPoint): number {
const priorityA = a.priority || 0;
const priorityB = b.priority || 0;
export function sortDescendingPriority(a: OptionalBreakPoint, b: OptionalBreakPoint): number {
const priorityA = a ? a.priority || 0 : 0;
const priorityB = b ? b.priority || 0 : 0;
return priorityB - priorityA;
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/match-media/match-media.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('match-media', () => {

// Single async inject to save references; which are used in all tests below
beforeEach(async(inject([MatchMedia], (service: MockMatchMedia) => {
matchMedia = service; // inject only to manually activate mediaQuery ranges
matchMedia = service; // inject only to manually onMediaChange mediaQuery ranges
})));
afterEach(() => {
matchMedia.clearAll();
Expand Down Expand Up @@ -121,7 +121,7 @@ describe('match-media-observable', () => {
[MediaObserver, MatchMedia, BreakPointRegistry],
(_mediaObserver: MediaObserver, _matchMedia: MockMatchMedia,
_breakPoints: BreakPointRegistry) => {
matchMedia = _matchMedia; // inject only to manually activate mediaQuery ranges
matchMedia = _matchMedia; // inject only to manually onMediaChange mediaQuery ranges
breakPoints = _breakPoints;
mediaObserver = _mediaObserver;
})));
Expand Down
5 changes: 3 additions & 2 deletions src/lib/core/match-media/match-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ export class MatchMedia {
const matches: Array<MediaChange> = this.registerQuery(mqList);
if (matches.length) {
const lastChange = matches.pop()!;

matches.forEach(observer.next);
matches.forEach((e: MediaChange) => {
observer.next(e);
});
this._source.next(lastChange); // last match is cached
}
observer.complete();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/match-media/mock/mock-match-media.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe('mock-match-media', () => {
subscription.unsubscribe();
});

it('can activate with either a mediaQuery or an alias', () => {
it('can onMediaChange with either a mediaQuery or an alias', () => {
let activates = 0;
let bpGtSM = breakPoints.findByAlias('gt-sm'),
bpLg = breakPoints.findByAlias('lg');
Expand Down
6 changes: 3 additions & 3 deletions src/lib/core/match-media/mock/mock-match-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class MockMatchMedia extends MatchMedia {
}

/**
* Manually activate any overlapping mediaQueries to simulate
* Manually onMediaChange any overlapping mediaQueries to simulate
* similar functionality in the window.matchMedia()
*/
private _activateWithOverlaps(mediaQuery: string, useOverlaps: boolean): boolean {
Expand All @@ -92,7 +92,7 @@ export class MockMatchMedia extends MatchMedia {
break;
}

// Simulate activate of overlapping gt-<xxxx> mediaQuery ranges
// Simulate onMediaChange of overlapping gt-<xxxx> mediaQuery ranges
switch (alias) {
case 'xl' :
this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs');
Expand Down Expand Up @@ -223,7 +223,7 @@ export class MockMediaQueryList implements MediaQueryList {
return this;
}

/** Add a listener to our internal list to activate later */
/** Add a listener to our internal list to onMediaChange later */
addListener(listener: MediaQueryListListener) {
if (this._listeners.indexOf(listener) === -1) {
this._listeners.push(listener);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/match-media/server-match-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ServerMediaQueryList implements MediaQueryList {
return this;
}

/** Add a listener to our internal list to activate later */
/** Add a listener to our internal list to onMediaChange later */
addListener(listener: MediaQueryListListener) {
if (this._listeners.indexOf(listener) === -1) {
this._listeners.push(listener);
Expand Down Expand Up @@ -109,7 +109,7 @@ export class ServerMediaQueryList implements MediaQueryList {
* Special server-only implementation of MatchMedia that uses the above
* ServerMediaQueryList as its internal representation
*
* Also contains methods to activate and deactivate breakpoints
* Also contains methods to onMediaChange and deactivate breakpoints
*/
@Injectable()
export class ServerMatchMedia extends MatchMedia {
Expand Down
Loading

0 comments on commit bff6040

Please sign in to comment.