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

Commit 9c80f62

Browse files
feat(core): add support for 1..n mediaQuery overrides during printing
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.
1 parent d57b293 commit 9c80f62

32 files changed

+1015
-329
lines changed

docs/documentation/BreakPoints.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {BREAKPOINT} from '@angular/flex-layout';
4242
const PRINT_BREAKPOINTS = [{
4343
alias: 'xs.print',
4444
suffix: 'XsPrint',
45-
mediaQuery: 'print and (max-width: 297px)',
45+
mediaQuery: 'screen and (max-width: 297px)',
4646
overlapping: false
4747
}];
4848

@@ -73,7 +73,7 @@ export class MyAppModule {
7373
}
7474
```
7575

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

@@ -157,4 +157,4 @@ export class CustomShowHideDirective extends ShowHideDirective {
157157
this._cacheInput("showXsPrint", negativeOf(val));
158158
}
159159
}
160-
```
160+
```

docs/documentation/fxHide-API.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ e.g.
2424
2525
### Using Responsive API
2626

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

3030
The *fallback* solution uses a **`largest_range-to-smallest_range`** search algorithm. Consider the following:

docs/documentation/fxShow-API.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ e.g.
2424
2525
### Using Responsive API
2626

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

3030
The *fallback* solution uses a **`largest_range-to-smallest_range`** search algorithm. Consider the following:

src/apps/demo-app/src/app/app.component.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ <h2>Layout Demos: </h2>
1313
</span>
1414
</div>
1515
<div fxLayout="row"
16-
fxLayoutAlign="start center"
1716
fxLayoutGap="20px"
17+
fxHide.print
1818
style="height:40px; min-height:40px;">
1919
<button mat-raised-button color="primary" [routerLink]="['']">
2020
Static
@@ -30,4 +30,5 @@ <h2>Layout Demos: </h2>
3030

3131
<div class="demo-content">
3232
<router-outlet></router-outlet>
33+
<watermark fxHide fxShow.print></watermark>
3334
</div>

src/apps/demo-app/src/app/app.module.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,31 @@ import {FlexLayoutModule, BREAKPOINT} from '@angular/flex-layout';
66
import {RoutingModule} from './routing.module';
77
import {AppComponent} from './app.component';
88
import {DemoMaterialModule} from './material.module';
9+
import {WatermarkComponent} from './watermark.component';
910

10-
const PRINT_BREAKPOINTS = [{
11-
alias: 'xs.print',
12-
suffix: 'XsPrint',
13-
mediaQuery: 'print and (max-width: 297px)',
11+
const EXTRA_BREAKPOINT = [{
12+
alias: 'xs.landscape',
13+
suffix: 'XsLandscape',
14+
mediaQuery: 'screen and (orientation: landscape) and (max-width: 559px)',
15+
priority: 1000,
1416
overlapping: false
1517
}];
1618

1719
@NgModule({
1820
declarations: [
19-
AppComponent,
21+
AppComponent, WatermarkComponent
2022
],
2123
imports: [
2224
BrowserModule.withServerTransition({ appId: 'serverApp' }),
2325
BrowserAnimationsModule,
2426
RoutingModule,
2527
DemoMaterialModule,
26-
FlexLayoutModule.withConfig({useColumnBasisZero: false}),
28+
FlexLayoutModule.withConfig({
29+
useColumnBasisZero: false,
30+
printWithBreakpoints: ['md', 'lt-lg', 'lt-xl', 'gt-sm', 'gt-xs']
31+
}),
2732
],
28-
providers: [{provide: BREAKPOINT, useValue: PRINT_BREAKPOINTS, multi: true}],
33+
providers: [{provide: BREAKPOINT, useValue: EXTRA_BREAKPOINT, multi: true}],
2934
bootstrap: [AppComponent]
3035
})
3136
export class AppModule { }

src/apps/demo-app/src/app/responsive/docs-responsive/docs-responsive.component.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {Component} from '@angular/core';
33
@Component({
44
selector: 'demo-docs-responsive',
55
template: `
6-
<demo-responsive-layout-direction class='small-demo'> </demo-responsive-layout-direction>
6+
<demo-responsive-layout-direction class='small-demo' fxHide.print>
7+
</demo-responsive-layout-direction>
78
<demo-responsive-row-column class='small-demo'> </demo-responsive-row-column>
89
<demo-responsive-flex-directive class='small-demo'> </demo-responsive-flex-directive>
910
<demo-responsive-flex-order class='small-demo'> </demo-responsive-flex-order>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
:host {
2+
display: block;
3+
position: absolute;
4+
5+
width: 100vw;
6+
min-height: 100vh;
7+
top: 0;
8+
left: 0;
9+
right: 0;
10+
bottom: 0;
11+
12+
div {
13+
width: 100vw;
14+
min-height: 100vh;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {Component, Input} from '@angular/core';
2+
import {DomSanitizer} from '@angular/platform-browser';
3+
4+
@Component({
5+
selector: 'watermark',
6+
styleUrls: ['watermark.component.scss'],
7+
template: `
8+
<div [style.background]="backgroundImage">
9+
</div>
10+
`,
11+
})
12+
export class WatermarkComponent {
13+
@Input() title = '@angular/layout';
14+
@Input() message = 'Layout with FlexBox + CSS Grid';
15+
16+
constructor(private _sanitizer: DomSanitizer) {
17+
}
18+
19+
/* tslint:disable:max-line-length */
20+
get backgroundImage() {
21+
const rawSVG = `
22+
<svg id="diagonalWatermark" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
23+
width="100%" height="100%">
24+
<style type="text/css">text { fill: gray; font-family: Avenir, Arial, Helvetica, sans-serif;
25+
opacity: 0.25; }
26+
</style>
27+
<defs>
28+
<pattern id="titlePattern" patternUnits="userSpaceOnUse" width="400" height="200">
29+
<text y="30" font-size="40" id="title">${this.title}</text>
30+
</pattern>
31+
<pattern xlink:href="#titlePattern">
32+
<text y="120" x="200" font-size="30" id="message">${this.message}</text>
33+
</pattern>
34+
<pattern id="combo" xlink:href="#titlePattern" patternTransform="rotate(-45)">
35+
<use xlink:href="#title"/>
36+
<use xlink:href="#message"/>
37+
</pattern>
38+
</defs>
39+
<rect width="100%" height="100%" fill="url(#combo)"/>
40+
</svg>
41+
`;
42+
const bkgrndImageUrl = `data:image/svg+xml;base64,${window.btoa(rawSVG)}`;
43+
44+
return this._sanitizer.bypassSecurityTrustStyle(`url('${bkgrndImageUrl}') repeat-y`);
45+
}
46+
}

src/apps/demo-app/src/styles.scss

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ body {
88
font-size: 16px;
99
line-height: 1.4;
1010
-webkit-font-smoothing: antialiased;
11+
-webkit-print-color-adjust: exact !important;
12+
}
13+
14+
@page {
15+
size: auto; /* auto is the initial value */
16+
margin: 2cm;
17+
}
18+
19+
@media print {
20+
body {
21+
background: white;
22+
}
1123
}
1224

1325
h3 {

src/lib/core/add-alias.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {extendObject} from '../utils/object-extend';
1414
* and suffix (if available).
1515
*/
1616
export function mergeAlias(dest: MediaChange, source: BreakPoint | null): MediaChange {
17-
return extendObject(dest, source ? {
17+
return extendObject(dest || {}, source ? {
1818
mqAlias: source.alias,
1919
suffix: source.suffix
2020
} : {});

src/lib/core/base/base2.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export abstract class BaseDirective2 implements OnChanges, OnDestroy {
3737
}
3838
set activatedValue(value: string) {
3939
this.marshal.setValue(this.nativeElement, this.DIRECTIVE_KEY, value,
40-
this.marshal.activatedBreakpoint);
40+
this.marshal.activatedAlias);
4141
}
4242

4343
/** Cache map for style computation */

src/lib/core/breakpoints/break-point-registry.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {BreakPoint} from './break-point';
1111
import {BREAKPOINTS} from './break-points-token';
1212
import {sortAscendingPriority} from './breakpoint-tools';
1313

14-
type OptionalBreakPoint = BreakPoint | null;
14+
export type OptionalBreakPoint = BreakPoint | null;
1515

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

3636
findByQuery(query: string): OptionalBreakPoint {

src/lib/core/breakpoints/breakpoint-tools.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {OptionalBreakPoint} from './break-point-registry';
910
import {BreakPoint} from './break-point';
1011
import {extendObject} from '../../utils/object-extend';
1112

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

6768
/** HOF to sort the breakpoints by priority */
68-
export function sortDescendingPriority(a: BreakPoint, b: BreakPoint): number {
69-
const priorityA = a.priority || 0;
70-
const priorityB = b.priority || 0;
69+
export function sortDescendingPriority(a: OptionalBreakPoint, b: OptionalBreakPoint): number {
70+
const priorityA = a ? a.priority || 0 : 0;
71+
const priorityB = b ? b.priority || 0 : 0;
7172
return priorityB - priorityA;
7273
}
7374

src/lib/core/breakpoints/data/orientation-break-points.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ export const ScreenTypes = {
3636
* Extended Breakpoints for handset/tablets with landscape or portrait orientations
3737
*/
3838
export const ORIENTATION_BREAKPOINTS : BreakPoint[] = [
39-
{'alias': 'handset', priority: 10000, 'mediaQuery': ScreenTypes.HANDSET},
40-
{'alias': 'handset.landscape', priority: 10000, 'mediaQuery': ScreenTypes.HANDSET_LANDSCAPE},
41-
{'alias': 'handset.portrait', priority: 10000, 'mediaQuery': ScreenTypes.HANDSET_PORTRAIT},
39+
{'alias': 'handset', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET},
40+
{'alias': 'handset.landscape', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_LANDSCAPE},
41+
{'alias': 'handset.portrait', priority: 2000, 'mediaQuery': ScreenTypes.HANDSET_PORTRAIT},
4242

43-
{'alias': 'tablet', priority: 8000, 'mediaQuery': ScreenTypes.TABLET},
44-
{'alias': 'tablet.landscape', priority: 8000, 'mediaQuery': ScreenTypes.TABLET},
45-
{'alias': 'tablet.portrait', priority: 8000, 'mediaQuery': ScreenTypes.TABLET_PORTRAIT},
43+
{'alias': 'tablet', priority: 2100, 'mediaQuery': ScreenTypes.TABLET},
44+
{'alias': 'tablet.landscape', priority: 2100, 'mediaQuery': ScreenTypes.TABLET},
45+
{'alias': 'tablet.portrait', priority: 2100, 'mediaQuery': ScreenTypes.TABLET_PORTRAIT},
4646

47-
{'alias': 'web', priority: 9000, 'mediaQuery': ScreenTypes.WEB, overlapping : true },
48-
{'alias': 'web.landscape', priority: 9000, 'mediaQuery': ScreenTypes.WEB_LANDSCAPE, overlapping : true },
49-
{'alias': 'web.portrait', priority: 9000, 'mediaQuery': ScreenTypes.WEB_PORTRAIT, overlapping : true }
47+
{'alias': 'web', priority: 2200, 'mediaQuery': ScreenTypes.WEB, overlapping : true },
48+
{'alias': 'web.landscape', priority: 2200, 'mediaQuery': ScreenTypes.WEB_LANDSCAPE, overlapping : true },
49+
{'alias': 'web.portrait', priority: 2200, 'mediaQuery': ScreenTypes.WEB_PORTRAIT, overlapping : true }
5050
];

src/lib/core/match-media/match-media.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ describe('match-media-observable', () => {
171171
});
172172

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

190190
matchMedia.activate(breakPoints.findByAlias('md')!.mediaQuery);
191191
matchMedia.activate(breakPoints.findByAlias('gt-md')!.mediaQuery);
192+
matchMedia.activate(breakPoints.findByAlias('lg')!.mediaQuery);
192193

193194
// 'all' mediaQuery is already active; total count should be (3)
194-
expect(activationCount).toEqual(3);
195+
expect(activationCount).toEqual(4);
195196
expect(deactivationCount).toEqual(0);
196197

197198
subscription.unsubscribe();

src/lib/core/match-media/mock/mock-match-media.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ describe('mock-match-media', () => {
181181
subscription.unsubscribe();
182182
});
183183

184-
it('can activate with either a mediaQuery or an alias', () => {
184+
it('can onMediaChange with either a mediaQuery or an alias', () => {
185185
let activates = 0;
186186
let bpGtSM = breakPoints.findByAlias('gt-sm'),
187187
bpLg = breakPoints.findByAlias('lg');

src/lib/core/match-media/mock/mock-match-media.ts

+6-11
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class MockMatchMedia extends MatchMedia {
6868
}
6969

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

95-
// Simulate activate of overlapping gt-<xxxx> mediaQuery ranges
95+
// Simulate onMediaChange of overlapping gt-<xxxx> mediaQuery ranges
9696
switch (alias) {
9797
case 'xl' :
9898
this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs');
@@ -137,15 +137,10 @@ export class MockMatchMedia extends MatchMedia {
137137
return this.hasActivated;
138138
}
139139

140-
/** Deactivate all current Mock MQLs */
140+
/** Deactivate all current MQLs and reset the buffer */
141141
private _deactivateAll() {
142-
if (this._actives.length) {
143-
// Deactivate all current MQLs and reset the buffer
144-
for (const it of this._actives) {
145-
it.deactivate();
146-
}
147-
this._actives = [];
148-
}
142+
this._actives.forEach(it => it.deactivate());
143+
this._actives = [];
149144
return this;
150145
}
151146

@@ -224,7 +219,7 @@ export class MockMediaQueryList implements MediaQueryList {
224219
return this;
225220
}
226221

227-
/** Add a listener to our internal list to activate later */
222+
/** Add a listener to our internal list to onMediaChange later */
228223
addListener(listener: MediaQueryListListener) {
229224
if (this._listeners.indexOf(listener) === -1) {
230225
this._listeners.push(listener);

src/lib/core/match-media/server-match-media.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class ServerMediaQueryList implements MediaQueryList {
6363
return this;
6464
}
6565

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

0 commit comments

Comments
 (0)