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 30, 2018
1 parent d57b293 commit 9c80f62
Show file tree
Hide file tree
Showing 32 changed files with 1,015 additions and 329 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>
19 changes: 12 additions & 7 deletions src/apps/demo-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,31 @@ import {FlexLayoutModule, BREAKPOINT} from '@angular/flex-layout';
import {RoutingModule} from './routing.module';
import {AppComponent} from './app.component';
import {DemoMaterialModule} from './material.module';
import {WatermarkComponent} from './watermark.component';

const PRINT_BREAKPOINTS = [{
alias: 'xs.print',
suffix: 'XsPrint',
mediaQuery: 'print and (max-width: 297px)',
const EXTRA_BREAKPOINT = [{
alias: 'xs.landscape',
suffix: 'XsLandscape',
mediaQuery: 'screen and (orientation: landscape) and (max-width: 559px)',
priority: 1000,
overlapping: false
}];

@NgModule({
declarations: [
AppComponent,
AppComponent, WatermarkComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
BrowserAnimationsModule,
RoutingModule,
DemoMaterialModule,
FlexLayoutModule.withConfig({useColumnBasisZero: false}),
FlexLayoutModule.withConfig({
useColumnBasisZero: false,
printWithBreakpoints: ['md', 'lt-lg', 'lt-xl', 'gt-sm', 'gt-xs']
}),
],
providers: [{provide: BREAKPOINT, useValue: PRINT_BREAKPOINTS, multi: true}],
providers: [{provide: BREAKPOINT, useValue: EXTRA_BREAKPOINT, 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`);
}
}
12 changes: 12 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,18 @@ 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;
}

@media print {
body {
background: white;
}
}

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
18 changes: 9 additions & 9 deletions src/lib/core/breakpoints/data/orientation-break-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ export const ScreenTypes = {
* Extended Breakpoints for handset/tablets with landscape or portrait orientations
*/
export const ORIENTATION_BREAKPOINTS : BreakPoint[] = [
{'alias': 'handset', priority: 10000, 'mediaQuery': ScreenTypes.HANDSET},
{'alias': 'handset.landscape', priority: 10000, 'mediaQuery': ScreenTypes.HANDSET_LANDSCAPE},
{'alias': 'handset.portrait', priority: 10000, 'mediaQuery': ScreenTypes.HANDSET_PORTRAIT},
{'alias': 'handset', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET},
{'alias': 'handset.landscape', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_LANDSCAPE},
{'alias': 'handset.portrait', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_PORTRAIT},

{'alias': 'tablet', priority: 8000, 'mediaQuery': ScreenTypes.TABLET},
{'alias': 'tablet.landscape', priority: 8000, 'mediaQuery': ScreenTypes.TABLET},
{'alias': 'tablet.portrait', priority: 8000, 'mediaQuery': ScreenTypes.TABLET_PORTRAIT},
{'alias': 'tablet', priority: 2100, 'mediaQuery': ScreenTypes.TABLET},
{'alias': 'tablet.landscape', priority: 2100, 'mediaQuery': ScreenTypes.TABLET},
{'alias': 'tablet.portrait', priority: 2100, 'mediaQuery': ScreenTypes.TABLET_PORTRAIT},

{'alias': 'web', priority: 9000, 'mediaQuery': ScreenTypes.WEB, overlapping : true },
{'alias': 'web.landscape', priority: 9000, 'mediaQuery': ScreenTypes.WEB_LANDSCAPE, overlapping : true },
{'alias': 'web.portrait', priority: 9000, 'mediaQuery': ScreenTypes.WEB_PORTRAIT, overlapping : true }
{'alias': 'web', priority: 2200, 'mediaQuery': ScreenTypes.WEB, overlapping : true },
{'alias': 'web.landscape', priority: 2200, 'mediaQuery': ScreenTypes.WEB_LANDSCAPE, overlapping : true },
{'alias': 'web.portrait', priority: 2200, 'mediaQuery': ScreenTypes.WEB_PORTRAIT, overlapping : true }
];
5 changes: 3 additions & 2 deletions src/lib/core/match-media/match-media.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe('match-media-observable', () => {
});

/**
* Only the ObservableMedia ignores de-activations;
* Only the MediaObserver ignores de-activations;
* MediaMonitor and MatchMedia report both activations and de-activations!
*/
it('ignores mediaQuery de-activations', () => {
Expand All @@ -189,9 +189,10 @@ describe('match-media-observable', () => {

matchMedia.activate(breakPoints.findByAlias('md')!.mediaQuery);
matchMedia.activate(breakPoints.findByAlias('gt-md')!.mediaQuery);
matchMedia.activate(breakPoints.findByAlias('lg')!.mediaQuery);

// 'all' mediaQuery is already active; total count should be (3)
expect(activationCount).toEqual(3);
expect(activationCount).toEqual(4);
expect(deactivationCount).toEqual(0);

subscription.unsubscribe();
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 @@ -181,7 +181,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
17 changes: 6 additions & 11 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 @@ -137,15 +137,10 @@ export class MockMatchMedia extends MatchMedia {
return this.hasActivated;
}

/** Deactivate all current Mock MQLs */
/** Deactivate all current MQLs and reset the buffer */
private _deactivateAll() {
if (this._actives.length) {
// Deactivate all current MQLs and reset the buffer
for (const it of this._actives) {
it.deactivate();
}
this._actives = [];
}
this._actives.forEach(it => it.deactivate());
this._actives = [];
return this;
}

Expand Down Expand Up @@ -224,7 +219,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 9c80f62

Please sign in to comment.