Skip to content

Commit

Permalink
fix(lib, media-query): support angular/universal (#353)
Browse files Browse the repository at this point in the history
Use getDom() for platform-server/universal fixes
Now gets document object from platform-browser by DI instead of global.

> Thx to @ardatan for PR #346.

Fixes #187, #354. Closes #346.
  • Loading branch information
ThomasBurleson authored and tinayuangao committed Aug 4, 2017
1 parent 40defac commit 0f13b14
Show file tree
Hide file tree
Showing 26 changed files with 653 additions and 81 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
include:
- env: "MODE=lint"
- env: "MODE=aot"
- env: "MODE=prerender"
- env: "MODE=closure-compiler"
- env: "MODE=saucelabs_required"
- env: "MODE=browserstack_required"
Expand Down
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
"url": "git+https://github.com/angular/flex-layout.git"
},
"scripts": {
"api": "gulp api-docs",
"build": "gulp :publish:build-releases",
"closure": "./scripts/closure-compiler/build-devapp-bundle.sh",
"demo-app": "gulp serve:devapp",
"test": "gulp test",
"tslint": "gulp lint",
"stylelint": "gulp lint",
"e2e": "gulp e2e",
"docs": "gulp docs",
"deploy": "gulp deploy:devapp",
"stage": "gulp stage-deploy:devapp",
"stylelint": "gulp lint",
"test": "gulp test",
"tslint": "gulp lint",
"webdriver-manager": "webdriver-manager",
"docs": "gulp docs",
"api": "gulp api-docs"
"universal": "gulp universal:build",
"universal:test": "gulp ci:prerender"
},
"version": "2.0.0-beta.8",
"license": "MIT",
Expand Down
2 changes: 2 additions & 0 deletions scripts/ci/travis-testing.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ elif is_aot; then
$(npm bin)/gulp ci:aot
elif is_unit; then
$(npm bin)/gulp ci:test
elif is_prerender; then
$(npm bin)/gulp ci:prerender
elif is_closure_compiler; then
./scripts/closure-compiler/build-devapp-bundle.sh
fi
Expand Down
2 changes: 1 addition & 1 deletion scripts/closure-compiler/build-devapp-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ set -e -o pipefail
cd $(dirname $0)/../..


# Build a release of material and of the CDK package.
# Build a release of Flex-Layout library
$(npm bin)/gulp flex-layout:build-release:clean

# Build demo-app with ES2015 modules. Closure compiler is then able to parse imports.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {Directive, ElementRef, Output} from '@angular/core';
import {Directive, ElementRef, Inject, Output} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';

import {Observable} from 'rxjs/Observable';

import 'rxjs/add/observable/fromEvent';
Expand All @@ -17,14 +19,13 @@ export class SplitHandleDirective {

@Output() drag: Observable<{ x: number, y: number }>;

constructor(ref: ElementRef) {
constructor(ref: ElementRef, @Inject(DOCUMENT) _document: any) {
const fromEvent = Observable.fromEvent;
const getMouseEventPosition = (event: MouseEvent) => ({x: event.movementX, y: event.movementY});

const mouseup$ = Observable.fromEvent(document, 'mouseup');
const mousemove$ = Observable.fromEvent(document, 'mousemove')
.map(getMouseEventPosition);
const mousedown$ = Observable.fromEvent(ref.nativeElement, 'mousedown')
.map(getMouseEventPosition);
const mousedown$ = fromEvent(ref.nativeElement, 'mousedown').map(getMouseEventPosition);
const mousemove$ = fromEvent(_document, 'mousemove').map(getMouseEventPosition);
const mouseup$ = fromEvent(_document, 'mouseup').map(getMouseEventPosition);

this.drag = mousedown$.switchMap(_ => mousemove$.takeUntil(mouseup$));
}
Expand Down
44 changes: 29 additions & 15 deletions src/lib/flexbox/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ElementRef, OnDestroy, SimpleChanges, OnChanges,
SimpleChange, Renderer
} from '@angular/core';
import {ɵgetDOM as getDom} from '@angular/platform-browser';

import {applyCssPrefixes} from '../../utils/auto-prefixer';
import {buildLayoutCSS} from '../../utils/layout-validator';
Expand Down Expand Up @@ -117,27 +118,40 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
*/
protected _getDisplayStyle(source?: HTMLElement): string {
let element: HTMLElement = source || this._elementRef.nativeElement;
let value = (element.style as any)['display'] || getComputedStyle(element)['display'];
return value ? value.trim() : 'block';
let value = this._lookupStyle(element, 'display');

return value ? value.trim() : ((element.nodeType === 1) ? 'block' : 'inline-block');
}

protected _getFlowDirection(target: any, addIfMissing = false): string {
let value = '';
let value = 'row';

if (target) {
let directionKeys = Object.keys(applyCssPrefixes({'flex-direction': ''}));
let findDirection = (styles) => directionKeys.reduce((direction, key) => {
return direction || styles[key];
}, null);

let immediateValue = findDirection(target.style);
value = immediateValue || findDirection(getComputedStyle(target as Element));
if (!immediateValue && addIfMissing) {
value = value || 'row';
value = this._lookupStyle(target, 'flex-direction') || 'row';

let hasInlineValue = getDom().getStyle(target, 'flex-direction');
if (!hasInlineValue && addIfMissing) {
this._applyStyleToElements(buildLayoutCSS(value), [target]);
}
}

return value ? value.trim() : 'row';
return value.trim();
}

/**
* Determine the inline or inherited CSS style
*/
protected _lookupStyle(element: HTMLElement, styleName: string): any {
let value = '';
try {
if (element) {
let immediateValue = getDom().getStyle(element, styleName);
value = immediateValue || getDom().getComputedStyle(element).display;
}
} catch (e) {
// TODO: platform-server throws an exception for getComputedStyle
}
return value;
}

/**
Expand Down Expand Up @@ -220,11 +234,11 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
* Special accessor to query for all child 'element' nodes regardless of type, class, etc.
*/
protected get childrenNodes() {
const obj = this._elementRef.nativeElement.childNodes;
const obj = this._elementRef.nativeElement.children;
const buffer = [];

// iterate backwards ensuring that length is an UInt32
for ( let i = obj.length; i--; ) {
for (let i = obj.length; i--; ) {
buffer[i] = obj[i];
}
return buffer;
Expand Down
42 changes: 23 additions & 19 deletions src/lib/flexbox/api/layout-gap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import {LayoutDirective} from './layout';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';
import {LAYOUT_VALUES} from '../../utils/layout-validator';

/**
* 'layout-padding' styling directive
* Defines padding of child elements in a layout container
*/
@Directive({selector: `
@Directive({
selector: `
[fxLayoutGap],
[fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl],
[fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl],
Expand All @@ -42,22 +44,22 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
protected _observer: MutationObserver;

/* tslint:disable */
@Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); }
@Input('fxLayoutGap.xs') set gapXs(val) { this._cacheInput('gapXs', val); }
@Input('fxLayoutGap.sm') set gapSm(val) { this._cacheInput('gapSm', val); };
@Input('fxLayoutGap.md') set gapMd(val) { this._cacheInput('gapMd', val); };
@Input('fxLayoutGap.lg') set gapLg(val) { this._cacheInput('gapLg', val); };
@Input('fxLayoutGap.xl') set gapXl(val) { this._cacheInput('gapXl', val); };

@Input('fxLayoutGap.gt-xs') set gapGtXs(val) { this._cacheInput('gapGtXs', val); };
@Input('fxLayoutGap.gt-sm') set gapGtSm(val) { this._cacheInput('gapGtSm', val); };
@Input('fxLayoutGap.gt-md') set gapGtMd(val) { this._cacheInput('gapGtMd', val); };
@Input('fxLayoutGap.gt-lg') set gapGtLg(val) { this._cacheInput('gapGtLg', val); };

@Input('fxLayoutGap.lt-sm') set gapLtSm(val) { this._cacheInput('gapLtSm', val); };
@Input('fxLayoutGap.lt-md') set gapLtMd(val) { this._cacheInput('gapLtMd', val); };
@Input('fxLayoutGap.lt-lg') set gapLtLg(val) { this._cacheInput('gapLtLg', val); };
@Input('fxLayoutGap.lt-xl') set gapLtXl(val) { this._cacheInput('gapLtXl', val); };
@Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); }
@Input('fxLayoutGap.xs') set gapXs(val) { this._cacheInput('gapXs', val); }
@Input('fxLayoutGap.sm') set gapSm(val) { this._cacheInput('gapSm', val); };
@Input('fxLayoutGap.md') set gapMd(val) { this._cacheInput('gapMd', val); };
@Input('fxLayoutGap.lg') set gapLg(val) { this._cacheInput('gapLg', val); };
@Input('fxLayoutGap.xl') set gapXl(val) { this._cacheInput('gapXl', val); };

@Input('fxLayoutGap.gt-xs') set gapGtXs(val) { this._cacheInput('gapGtXs', val); };
@Input('fxLayoutGap.gt-sm') set gapGtSm(val) { this._cacheInput('gapGtSm', val); };
@Input('fxLayoutGap.gt-md') set gapGtMd(val) { this._cacheInput('gapGtMd', val); };
@Input('fxLayoutGap.gt-lg') set gapGtLg(val) { this._cacheInput('gapGtLg', val); };

@Input('fxLayoutGap.lt-sm') set gapLtSm(val) { this._cacheInput('gapLtSm', val); };
@Input('fxLayoutGap.lt-md') set gapLtMd(val) { this._cacheInput('gapLtMd', val); };
@Input('fxLayoutGap.lt-lg') set gapLtLg(val) { this._cacheInput('gapLtLg', val); };
@Input('fxLayoutGap.lt-xl') set gapLtXl(val) { this._cacheInput('gapLtXl', val); };

/* tslint:enable */
constructor(monitor: MediaMonitor,
Expand Down Expand Up @@ -124,8 +126,10 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
}
};

this._observer = new MutationObserver(onMutationCallback);
this._observer.observe(this._elementRef.nativeElement, {childList: true});
if (typeof MutationObserver !== 'undefined') {
this._observer = new MutationObserver(onMutationCallback);
this._observer.observe(this._elementRef.nativeElement, {childList: true});
}
}

/**
Expand Down
37 changes: 24 additions & 13 deletions src/lib/media-query/match-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* 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 {Injectable, NgZone} from '@angular/core';

import {Inject, Injectable, NgZone} from '@angular/core';
import {ɵgetDOM as getDom, DOCUMENT} from '@angular/platform-browser';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {filter} from 'rxjs/operator/filter';
Expand All @@ -27,7 +27,9 @@ export interface MediaQueryListListener {
export interface MediaQueryList {
readonly matches: boolean;
readonly media: string;

addListener(listener: MediaQueryListListener): void;

removeListener(listener: MediaQueryListListener): void;
}

Expand All @@ -45,7 +47,7 @@ export class MatchMedia {
protected _source: BehaviorSubject<MediaChange>;
protected _observable$: Observable<MediaChange>;

constructor(protected _zone: NgZone) {
constructor(protected _zone: NgZone, @Inject(DOCUMENT) protected _document: any) {
this._registry = new Map<string, MediaQueryList>();
this._source = new BehaviorSubject<MediaChange>(new MediaChange(true));
this._observable$ = this._source.asObservable();
Expand Down Expand Up @@ -86,7 +88,7 @@ export class MatchMedia {
let list = normalizeQuery(mediaQuery);

if (list.length > 0) {
prepareQueryCSS(list);
prepareQueryCSS(list, this._document);

list.forEach(query => {
let mql = this._registry.get(query);
Expand Down Expand Up @@ -114,8 +116,9 @@ export class MatchMedia {
* Call window.matchMedia() to build a MediaQueryList; which
* supports 0..n listeners for activation/deactivation
*/
protected _buildMQL(query: string): MediaQueryList {
let canListen = !!(<any>window).matchMedia('all').addListener;
protected _buildMQL(query: string): MediaQueryList {
let canListen = isBrowser() && !!(<any>window).matchMedia('all').addListener;

return canListen ? (<any>window).matchMedia(query) : <MediaQueryList>{
matches: query === 'all' || query === '',
media: query,
Expand All @@ -127,6 +130,13 @@ export class MatchMedia {
}
}

/**
* Determine if SSR or Browser rendering.
*/
export function isBrowser() {
return getDom().supportsDOMEvents();
}

/**
* Private global registry for all dynamically-created, injected style tags
* @see prepare(query)
Expand All @@ -140,27 +150,28 @@ const ALL_STYLES = {};
* @param query string The mediaQuery used to create a faux CSS selector
*
*/
function prepareQueryCSS(mediaQueries: string[]) {
function prepareQueryCSS(mediaQueries: string[], _document: any) {
let list = mediaQueries.filter(it => !ALL_STYLES[it]);
if (list.length > 0) {
let query = list.join(', ');

try {
let style = document.createElement('style');
let styleEl = getDom().createElement('style');

style.setAttribute('type', 'text/css');
if (!style['styleSheet']) {
getDom().setAttribute(styleEl, 'type', 'text/css');
if (!styleEl['styleSheet']) {
let cssText = `/*
@angular/flex-layout - workaround for possible browser quirk with mediaQuery listeners
see http://bit.ly/2sd4HMP
*/
@media ${query} {.fx-query-test{ }}`;
style.appendChild(document.createTextNode(cssText));
getDom().appendChild(styleEl, getDom().createTextNode(cssText));
}

document.getElementsByTagName('head')[0].appendChild(style);
getDom().appendChild(_document.head, styleEl);

// Store in private global registry
list.forEach(mq => ALL_STYLES[mq] = style);
list.forEach(mq => ALL_STYLES[mq] = styleEl);

} catch (e) {
console.error(e);
Expand Down
44 changes: 32 additions & 12 deletions src/lib/media-query/mock/mock-match-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* 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 {Injectable, NgZone} from '@angular/core';
import {Inject, Injectable, NgZone} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';

import {MatchMedia} from '../match-media';
import {BreakPointRegistry} from '../breakpoints/break-point-registry';

Expand All @@ -28,8 +30,10 @@ export class MockMatchMedia extends MatchMedia {
*/
public useOverlaps = false;

constructor(_zone: NgZone, private _breakpoints: BreakPointRegistry) {
super(_zone);
constructor(_zone: NgZone,
@Inject(DOCUMENT) _document: any,
private _breakpoints: BreakPointRegistry) {
super(_zone, _document);
this._actives = [];
}

Expand Down Expand Up @@ -83,18 +87,34 @@ export class MockMatchMedia extends MatchMedia {

// Simulate activation of overlapping lt-<XXX> ranges
switch (alias) {
case 'lg' : this._activateByAlias('lt-xl'); break;
case 'md' : this._activateByAlias('lt-xl, lt-lg'); break;
case 'sm' : this._activateByAlias('lt-xl, lt-lg, lt-md'); break;
case 'xs' : this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm'); break;
case 'lg' :
this._activateByAlias('lt-xl');
break;
case 'md' :
this._activateByAlias('lt-xl, lt-lg');
break;
case 'sm' :
this._activateByAlias('lt-xl, lt-lg, lt-md');
break;
case 'xs' :
this._activateByAlias('lt-xl, lt-lg, lt-md, lt-sm');
break;
}

// Simulate activate of overlapping gt-<xxxx> mediaQuery ranges
switch (alias) {
case 'xl' : this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs'); break;
case 'lg' : this._activateByAlias('gt-md, gt-sm, gt-xs'); break;
case 'md' : this._activateByAlias('gt-sm, gt-xs'); break;
case 'sm' : this._activateByAlias('gt-xs'); break;
case 'xl' :
this._activateByAlias('gt-lg, gt-md, gt-sm, gt-xs');
break;
case 'lg' :
this._activateByAlias('gt-md, gt-sm, gt-xs');
break;
case 'md' :
this._activateByAlias('gt-sm, gt-xs');
break;
case 'sm' :
this._activateByAlias('gt-xs');
break;
}
}
// Activate last since the responsiveActivation is watching *this* mediaQuery
Expand Down Expand Up @@ -195,7 +215,7 @@ export class MockMediaQueryList implements MediaQueryList {
* Notify all listeners that 'matches === TRUE'
*/
activate(): MockMediaQueryList {
if ( !this._isActive ) {
if (!this._isActive) {
this._isActive = true;
this._listeners.forEach((callback) => {
callback(this);
Expand Down
Loading

0 comments on commit 0f13b14

Please sign in to comment.