Skip to content

Commit

Permalink
update(lib): improve usage of selectors and support for future dot no…
Browse files Browse the repository at this point in the history
…tations

*  buildSelectors dynamically: add all responsive variations to base selector
*  Support future template selector-dot-notations features
  *  Support full-property selector usages
  *  Support object assignment of @input key
  *  Support property chain notations to updated parts of a nested chain
  *  Use Input map to manage all input key/value pairs
  *  Modify all @input to be setters
*  Move ResponsiveActivation construction to BaseDirective
*  Modify ResponsiveActivation to query the input map for values
*  Enhance `fx-flex` to support shorthand notations `fx-flex="1 1 100%"`

Fixes #34, Refs angular/angular#13352, Refs angular/angular#13355
  • Loading branch information
ThomasBurleson committed Dec 12, 2016
1 parent d3ea8ad commit 4938a44
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 275 deletions.
84 changes: 79 additions & 5 deletions src/lib/flexbox/api/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {ElementRef, Renderer} from '@angular/core';
import {ElementRef, Renderer, OnDestroy} from '@angular/core';
import {applyCssPrefixes} from '../../utils/auto-prefixer';

import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation';
import {MediaMonitor} from '../../media-query/media-monitor';
import {MediaQuerySubscriber} from '../../media-query/media-change';

/**
* Definition of a css style. Either a property name (e.g. "flex-basis") or an object
Expand All @@ -9,16 +12,62 @@ import {MediaMonitor} from '../../media-query/media-monitor';
export type StyleDefinition = string|{[property: string]: string|number};

/** Abstract base class for the Layout API styling directives. */
export abstract class BaseFxDirective {
constructor(private _mediaMonitor : MediaMonitor, private _elementRef: ElementRef, private _renderer: Renderer) {}
export abstract class BaseFxDirective implements OnDestroy {
/**
* MediaQuery Activation Tracker
*/
protected _mqActivation: ResponsiveActivation;

/**
* Dictionary of input keys with associated values
*/
protected _inputMap: Map<string, any>;

/**
*
*/
constructor(private _mediaMonitor: MediaMonitor, private _elementRef: ElementRef, private _renderer: Renderer) {
this._inputMap = new Map<string, any>();
}

// *********************************************
// Accessor Methods
// *********************************************

/**
* Accessor used by the ResponsiveActivation to subscribe to mediaQuery change notifications
*/
get mediaMonitor() : MediaMonitor {
get mediaMonitor(): MediaMonitor {
return this._mediaMonitor;
}
/** Applies styles given via string pair or object map to the directive element. */

/**
* Access the current value (if any) of the @Input property.
*/
protected _queryInput(key) {
return this._inputMap.get(key);
}


// *********************************************
// Lifecycle Methods
// *********************************************

/**
*
*/
ngOnDestroy() {
this._mqActivation.destroy();
this._mediaMonitor = null;
}

// *********************************************
// Protected Methods
// *********************************************

/**
* Applies styles given via string pair or object map to the directive element.
*/
protected _applyStyleToElement(style: StyleDefinition, value?: string|number) {
let styles = {};
let element = this._elementRef.nativeElement;
Expand All @@ -35,4 +84,29 @@ export abstract class BaseFxDirective {
this._renderer.setElementStyle(element, key, styles[key]);
}
}

/**
* Save the property value; which may be a complex object.
* Complex objects support property chains
*/
protected _cacheInput(key, source) {
if (typeof source === 'object') {
for (let prop in source) {
this._inputMap.set(prop, source[prop]);
}
} else {
this._inputMap.set(key, source);
}
}

/**
* Build a ResponsiveActivation object used to manage subscriptions to mediaChange notifications
* and intelligent lookup of the directive's property value that corresponds to that mediaQuery
* (or closest match).
*/
protected _listenForMediaQueryChanges(key: string, defaultVal: any,
onMediaQueryChange: MediaQuerySubscriber): ResponsiveActivation {
let keyOptions = new KeyOptions(key, defaultVal, this._inputMap);
return this._mqActivation = new ResponsiveActivation(this, keyOptions, onMediaQueryChange);
}
}
43 changes: 14 additions & 29 deletions src/lib/flexbox/api/flex-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,26 @@ import {
import {BaseFxDirective} from './base';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';
import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation';
import {addResponsiveAliases} from '../../utils/add-alias';

/**
* 'flex-align' flexbox styling directive
* Allows element-specific overrides for cross-axis alignments in a layout container
* @see https://css-tricks.com/almanac/properties/a/align-self/
*/
@Directive({selector: '[fx-flex-align]'})
@Directive({selector: addResponsiveAliases('fx-flex-align')})
export class FlexAlignDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
/**
* MediaQuery Activation Tracker
*/
private _mqActivation: ResponsiveActivation;

@Input('fx-flex-align') align: string = 'stretch'; // default

// *******************************************************
// Optional input variations to support mediaQuery triggers
// *******************************************************

@Input('fx-flex-align.xs') alignXs;
@Input('fx-flex-align.gt-xs') alignGtXs;
@Input('fx-flex-align.sm') alignSm;
@Input('fx-flex-align.gt-sm') alignGtSm;
@Input('fx-flex-align.md') alignMd;
@Input('fx-flex-align.gt-md') alignGtMd;
@Input('fx-flex-align.lg') alignLg;
@Input('fx-flex-align.gt-lg') alignGtLg;
@Input('fx-flex-align.xl') alignXl;

@Input('fx-flex-align') set align(val) { this._cacheInput('align', val); }
@Input('fx-flex-align.xs') set alignXs(val) { this._cacheInput('alignXs', val); }
@Input('fx-flex-align.gt-xs') set alignGtXs(val) { this._cacheInput('alignGtXs', val); };
@Input('fx-flex-align.sm') set alignSm(val) { this._cacheInput('alignSm', val); };
@Input('fx-flex-align.gt-sm') set alignGtSm(val) { this._cacheInput('alignGtSm', val); };
@Input('fx-flex-align.md') set alignMd(val) { this._cacheInput('alignMd', val); };
@Input('fx-flex-align.gt-md') set alignGtMd(val) { this._cacheInput('alignGtMd', val); };
@Input('fx-flex-align.lg') set alignLg(val) { this._cacheInput('alignLg', val); };
@Input('fx-flex-align.gt-lg') set alignGtLg(val) { this._cacheInput('alignGtLg', val); };
@Input('fx-flex-align.xl') set alignXl(val) { this._cacheInput('alignXl', val); };

constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) {
super(monitor, elRef, renderer);
Expand All @@ -66,23 +56,18 @@ export class FlexAlignDirective extends BaseFxDirective implements OnInit, OnCha
* mql change events to onMediaQueryChange handlers
*/
ngOnInit() {
let keyOptions = new KeyOptions('align', 'stretch');
this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{
this._listenForMediaQueryChanges('align', 'stretch', (changes: MediaChange) =>{
this._updateWithValue(changes.value);
});
this._updateWithValue();
}

ngOnDestroy() {
this._mqActivation.destroy();
}

// *********************************************
// Protected methods
// *********************************************

_updateWithValue(value?: string|number) {
value = value || this.align || 'stretch';
value = value || this._queryInput("align") || 'stretch';
if (this._mqActivation) {
value = this._mqActivation.activatedInput;
}
Expand Down
7 changes: 4 additions & 3 deletions src/lib/flexbox/api/flex-fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Directive, ElementRef, Renderer} from '@angular/core';

import {MediaMonitor} from '../../media-query/media-monitor';
import {BaseFxDirective} from './base';
import {addResponsiveAliases} from '../../utils/add-alias';

const FLEX_FILL_CSS = {
'margin': 0,
Expand All @@ -12,12 +13,12 @@ const FLEX_FILL_CSS = {
};

/**
* 'fx-flex-fill' flexbox styling directive
* 'fx-fill' flexbox styling directive
* Maximizes width and height of element in a layout container
*
* NOTE: [fx-flexFill] is NOT responsive fx-flex
* NOTE: fx-fill is NOT responsive API!!
*/
@Directive({selector: '[fx-flex-fill]'})
@Directive({selector: "[fx-fill], " + addResponsiveAliases('fx-flex-fill')})
export class FlexFillDirective extends BaseFxDirective {
constructor(monitor : MediaMonitor, public elRef: ElementRef, public renderer: Renderer) {
super(monitor, elRef, renderer);
Expand Down
42 changes: 14 additions & 28 deletions src/lib/flexbox/api/flex-offset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,26 @@ import {
import {BaseFxDirective} from './base';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';
import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation';
import {addResponsiveAliases} from '../../utils/add-alias';


/**
* 'flex-offset' flexbox styling directive
* Configures the 'margin-left' of the element in a layout container
*/
@Directive({selector: '[fx-flex-offset]'})
@Directive({selector: addResponsiveAliases('fx-flex-offset')})
export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
/**
* MediaQuery Activation Tracker
*/
private _mqActivation: ResponsiveActivation;

@Input('fx-flex-offset') offset: string|number;

// *******************************************************
// Optional input variations to support mediaQuery triggers
// *******************************************************

@Input('fx-flex-offset.xs') offsetXs: string|number;
@Input('fx-flex-offset.gt-xs') offsetGtXs: string|number;
@Input('fx-flex-offset.sm') offsetSm: string|number;
@Input('fx-flex-offset.gt-sm') offsetGtSm: string|number;
@Input('fx-flex-offset.md') offsetMd: string|number;
@Input('fx-flex-offset.gt-md') offsetGtMd: string|number;
@Input('fx-flex-offset.lg') offsetLg: string|number;
@Input('fx-flex-offset.gt-lg') offsetGtLg: string|number;
@Input('fx-flex-offset.xl') offsetXl: string|number;
@Input('fx-flex-offset') set offset(val) { this._cacheInput('offset', val); }
@Input('fx-flex-offset.xs') set offsetXs(val) { this._cacheInput('offsetXs', val); }
@Input('fx-flex-offset.gt-xs') set offsetGtXs(val) { this._cacheInput('offsetGtXs', val); };
@Input('fx-flex-offset.sm') set offsetSm(val) { this._cacheInput('offsetSm', val); };
@Input('fx-flex-offset.gt-sm') set offsetGtSm(val) { this._cacheInput('offsetGtSm', val); };
@Input('fx-flex-offset.md') set offsetMd(val) { this._cacheInput('offsetMd', val); };
@Input('fx-flex-offset.gt-md') set offsetGtMd(val) { this._cacheInput('offsetGtMd', val); };
@Input('fx-flex-offset.lg') set offsetLg(val) { this._cacheInput('offsetLg', val); };
@Input('fx-flex-offset.gt-lg') set offsetGtLg(val) { this._cacheInput('offsetGtLg', val); };
@Input('fx-flex-offset.xl') set offsetXl(val) { this._cacheInput('offsetXl', val); };

constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) {
super(monitor, elRef, renderer);
Expand All @@ -65,23 +56,18 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh
* mql change events to onMediaQueryChange handlers
*/
ngOnInit() {
let keyOptions = new KeyOptions('offset', 0 );
this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{
this._listenForMediaQueryChanges('offset', 0 , (changes: MediaChange) =>{
this._updateWithValue(changes.value);
});
}

ngOnDestroy() {
this._mqActivation.destroy();
}

// *********************************************
// Protected methods
// *********************************************


_updateWithValue(value?: string|number) {
value = value || this.offset || 0;
value = value || this._queryInput("offset") || 0;
if (this._mqActivation) {
value = this._mqActivation.activatedInput;
}
Expand Down
42 changes: 14 additions & 28 deletions src/lib/flexbox/api/flex-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,26 @@ import {
import {BaseFxDirective} from './base';
import {MediaChange} from '../../media-query/media-change';
import {MediaMonitor} from '../../media-query/media-monitor';
import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation';
import {addResponsiveAliases} from '../../utils/add-alias';

/**
* 'flex-order' flexbox styling directive
* Configures the positional ordering of the element in a sorted layout container
* @see https://css-tricks.com/almanac/properties/o/order/
*/
@Directive({selector: '[fx-flex-order]'})
@Directive({selector: addResponsiveAliases('fx-flex-order')})
export class FlexOrderDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy {
/**
* MediaQuery Activation Tracker
*/
private _mqActivation: ResponsiveActivation;

@Input('fx-flex-order') order;

// *******************************************************
// Optional input variations to support mediaQuery triggers
// *******************************************************

@Input('fx-flex-order.xs') orderXs;
@Input('fx-flex-order.gt-xs') orderGtXs;
@Input('fx-flex-order.sm') orderSm;
@Input('fx-flex-order.gt-sm') orderGtSm;
@Input('fx-flex-order.md') orderMd;
@Input('fx-flex-order.gt-md') orderGtMd;
@Input('fx-flex-order.lg') orderLg;
@Input('fx-flex-order.gt-lg') orderGtLg;
@Input('fx-flex-order.xl') orderXl;
@Input('fx-flex-order') set order(val) { this._cacheInput('order', val); }
@Input('fx-flex-order.xs') set orderXs(val) { this._cacheInput('orderXs', val); }
@Input('fx-flex-order.gt-xs') set orderGtXs(val) { this._cacheInput('orderGtXs', val); };
@Input('fx-flex-order.sm') set orderSm(val) { this._cacheInput('orderSm', val); };
@Input('fx-flex-order.gt-sm') set orderGtSm(val) { this._cacheInput('orderGtSm', val); };
@Input('fx-flex-order.md') set orderMd(val) { this._cacheInput('orderMd', val); };
@Input('fx-flex-order.gt-md') set orderGtMd(val) { this._cacheInput('orderGtMd', val); };
@Input('fx-flex-order.lg') set orderLg(val) { this._cacheInput('orderLg', val); };
@Input('fx-flex-order.gt-lg') set orderGtLg(val) { this._cacheInput('orderGtLg', val); };
@Input('fx-flex-order.xl') set orderXl(val) { this._cacheInput('orderXl', val); };

constructor(monitor : MediaMonitor, elRef: ElementRef, renderer: Renderer) {
super(monitor, elRef, renderer);
Expand All @@ -64,23 +55,18 @@ export class FlexOrderDirective extends BaseFxDirective implements OnInit, OnCha
* mql change events to onMediaQueryChange handlers
*/
ngOnInit() {
let keyOptions = new KeyOptions('order', '1');
this._mqActivation = new ResponsiveActivation(this, keyOptions, (changes: MediaChange) =>{
this._listenForMediaQueryChanges('order', '1', (changes: MediaChange) =>{
this._updateWithValue(changes.value);
});
this._updateWithValue();
}

ngOnDestroy() {
this._mqActivation.destroy();
}

// *********************************************
// Protected methods
// *********************************************

_updateWithValue(value?: string) {
value = value || this.order || '1';
value = value || this._queryInput("order") || '1';
if (this._mqActivation) {
value = this._mqActivation.activatedInput;
}
Expand Down
Loading

0 comments on commit 4938a44

Please sign in to comment.