From 6482c1268c71ae2f634029c5a8099e58e380ce01 Mon Sep 17 00:00:00 2001 From: Thomas Burleson Date: Wed, 25 Jan 2017 11:14:55 +1300 Subject: [PATCH] fix(fxLayoutGap): add gaps to dynamic content (#124) * fxLayoutGap not applied to children inserted after initial render Fixes #95. --- src/lib/flexbox/api/layout-gap.spec.ts | 55 ++++++++++-- src/lib/flexbox/api/layout-gap.ts | 117 ++++++++++++++++++------- 2 files changed, 137 insertions(+), 35 deletions(-) diff --git a/src/lib/flexbox/api/layout-gap.spec.ts b/src/lib/flexbox/api/layout-gap.spec.ts index 34edf6651..9e3a5e5ac 100644 --- a/src/lib/flexbox/api/layout-gap.spec.ts +++ b/src/lib/flexbox/api/layout-gap.spec.ts @@ -23,6 +23,7 @@ import { } from '../../utils/testing/helpers'; describe('layout-gap directive', () => { + let fixture: ComponentFixture; let createTestComponent = makeCreateTestComponent(() => TestLayoutGapComponent); let expectDomForQuery = makeExpectDOMForQuery(() => TestLayoutGapComponent); @@ -61,7 +62,7 @@ describe('layout-gap directive', () => {
`; - let fixture = createTestComponent(template); // tslint:disable-line:no-shadowed-variable + fixture = createTestComponent(template); // tslint:disable-line:no-shadowed-variable fixture.detectChanges(); let nodes = queryFor(fixture, "[fxFlex]"); @@ -71,6 +72,49 @@ describe('layout-gap directive', () => { expect(nodes[2].nativeElement).toHaveCssStyle({'margin-left': '13px'}); }); + it('should add gap styles to dynamics rows EXCEPT first', () => { + let template = ` +
+
+
+ `; + fixture = createTestComponent(template); + fixture.componentInstance.direction = "row"; + fixture.detectChanges(); + + let nodes = queryFor(fixture, "[fxFlex]"); + expect(nodes.length).toEqual(4); + expect(nodes[0].nativeElement).not.toHaveCssStyle({'margin-left': '13px'}); + expect(nodes[1].nativeElement).toHaveCssStyle({'margin-left': '13px'}); + expect(nodes[2].nativeElement).toHaveCssStyle({'margin-left': '13px'}); + expect(nodes[3].nativeElement).toHaveCssStyle({'margin-left': '13px'}); + }); + + it('should add update gap styles when row items are removed', () => { + let nodes, + template = ` +
+
+
+ `; + fixture = createTestComponent(template); + fixture.componentInstance.direction = "row"; + fixture.detectChanges(); + + nodes = queryFor(fixture, "[fxFlex]"); + expect(nodes.length).toEqual(4); + + fixture.componentInstance.rows.shift(); + fixture.detectChanges(); + + nodes = queryFor(fixture, "[fxFlex]"); + expect(nodes.length).toEqual(3); + + expect(nodes[0].nativeElement).toHaveCssStyle({'margin-left': '0px'}); + expect(nodes[1].nativeElement).toHaveCssStyle({'margin-left': '13px'}); + expect(nodes[2].nativeElement).toHaveCssStyle({'margin-left': '13px'}); + }); + it('should apply margin-top for column layouts', () => { verifyCorrectMargin('column', 'margin-top'); }); @@ -84,7 +128,7 @@ describe('layout-gap directive', () => { }); it('should remove obsolete margin and apply valid margin for layout changes', () => { - let fixture: ComponentFixture = createTestComponent(` + fixture = createTestComponent(`
@@ -130,10 +174,10 @@ describe('layout-gap directive', () => {
`; - const fixture = createTestComponent(template); - fixture.detectChanges(); + const fixture1 = createTestComponent(template); + fixture1.detectChanges(); - const nodes = queryFor(fixture, 'span'); + const nodes = queryFor(fixture1, 'span'); expect(nodes[1].nativeElement).toHaveCssStyle({[marginKey]: margin}); } @@ -156,6 +200,7 @@ describe('layout-gap directive', () => { export class TestLayoutGapComponent implements OnInit { direction = "column"; gap = "8px"; + rows = new Array(4); constructor() { } diff --git a/src/lib/flexbox/api/layout-gap.ts b/src/lib/flexbox/api/layout-gap.ts index bb37536c4..650113882 100644 --- a/src/lib/flexbox/api/layout-gap.ts +++ b/src/lib/flexbox/api/layout-gap.ts @@ -28,9 +28,10 @@ import {LayoutDirective, LAYOUT_VALUES} from './layout'; * 'layout-padding' styling directive * Defines padding of child elements in a layout container */ -@Directive({selector: ` +@Directive({ + selector: ` [fxLayoutGap], - [fxLayoutGap.xs] + [fxLayoutGap.xs], [fxLayoutGap.gt-xs], [fxLayoutGap.sm], [fxLayoutGap.gt-sm] @@ -39,28 +40,58 @@ import {LayoutDirective, LAYOUT_VALUES} from './layout'; [fxLayoutGap.lg], [fxLayoutGap.gt-lg], [fxLayoutGap.xl] -`}) +` +}) export class LayoutGapDirective extends BaseFxDirective implements AfterContentInit, OnChanges, - OnDestroy { + OnDestroy { private _layout = 'row'; // default flex-direction private _layoutWatcher: Subscription; + private _observer: MutationObserver; - @Input('fxLayoutGap') set gap(val) { this._cacheInput('gap', val); } - @Input('fxLayoutGap.xs') set gapXs(val) { this._cacheInput('gapXs', val); } - @Input('fxLayoutGap.gt-xs') set gapGtXs(val) { this._cacheInput('gapGtXs', val); }; - @Input('fxLayoutGap.sm') set gapSm(val) { this._cacheInput('gapSm', val); }; - @Input('fxLayoutGap.gt-sm') set gapGtSm(val) { this._cacheInput('gapGtSm', val); }; - @Input('fxLayoutGap.md') set gapMd(val) { this._cacheInput('gapMd', val); }; - @Input('fxLayoutGap.gt-md') set gapGtMd(val) { this._cacheInput('gapGtMd', val); }; - @Input('fxLayoutGap.lg') set gapLg(val) { this._cacheInput('gapLg', val); }; - @Input('fxLayoutGap.gt-lg') set gapGtLg(val) { this._cacheInput('gapGtLg', val); }; - @Input('fxLayoutGap.xl') set gapXl(val) { this._cacheInput('gapXl', val); }; - - constructor( - monitor: MediaMonitor, - elRef: ElementRef, - renderer: Renderer, - @Optional() @Self() container: LayoutDirective) { + @Input('fxLayoutGap') set gap(val) { + this._cacheInput('gap', val); + } + + @Input('fxLayoutGap.xs') set gapXs(val) { + this._cacheInput('gapXs', val); + } + + @Input('fxLayoutGap.gt-xs') set gapGtXs(val) { + this._cacheInput('gapGtXs', val); + }; + + @Input('fxLayoutGap.sm') set gapSm(val) { + this._cacheInput('gapSm', val); + }; + + @Input('fxLayoutGap.gt-sm') set gapGtSm(val) { + this._cacheInput('gapGtSm', val); + }; + + @Input('fxLayoutGap.md') set gapMd(val) { + this._cacheInput('gapMd', val); + }; + + @Input('fxLayoutGap.gt-md') set gapGtMd(val) { + this._cacheInput('gapGtMd', val); + }; + + @Input('fxLayoutGap.lg') set gapLg(val) { + this._cacheInput('gapLg', val); + }; + + @Input('fxLayoutGap.gt-lg') set gapGtLg(val) { + this._cacheInput('gapGtLg', val); + }; + + @Input('fxLayoutGap.xl') set gapXl(val) { + this._cacheInput('gapXl', val); + }; + + constructor(monitor: MediaMonitor, + elRef: ElementRef, + renderer: Renderer, + @Optional() @Self() container: LayoutDirective) { super(monitor, elRef, renderer); if (container) { // Subscribe to layout direction changes @@ -83,6 +114,7 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI * mql change events to onMediaQueryChange handlers */ ngAfterContentInit() { + this._watchContentChanges(); this._listenForMediaQueryChanges('gap', '0', (changes: MediaChange) => { this._updateWithValue(changes.value); }); @@ -90,16 +122,35 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI } ngOnDestroy() { - super.ngOnDestroy(); - if (this._layoutWatcher) { - this._layoutWatcher.unsubscribe(); - } - } + super.ngOnDestroy(); + if (this._layoutWatcher) { + this._layoutWatcher.unsubscribe(); + } + if (this._observer) { + this._observer.disconnect(); + } + } // ********************************************* // Protected methods // ********************************************* + /** + * Watch for child nodes to be added... and apply the layout gap styles to each. + * NOTE: this does NOT! differentiate between viewChildren and contentChildren + */ + private _watchContentChanges() { + let onMutationCallback = (mutations) => { + // update gap styles only for 'addedNodes' events + mutations + .filter((it: MutationRecord) => it.addedNodes && it.addedNodes.length) + .map(() => this._updateWithValue()); + }; + + this._observer = new MutationObserver(onMutationCallback); + this._observer.observe(this._elementRef.nativeElement, {childList: true}); + } + /** * Cache the parent container 'flex-direction' and update the 'margin' styles */ @@ -108,7 +159,6 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI if (!LAYOUT_VALUES.find(x => x === this._layout)) { this._layout = 'row'; } - this._updateWithValue(); } @@ -121,11 +171,18 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI value = this._mqActivation.activatedInput; } - // For each `element` child, set the padding styles... + // Reset 1st child element to 0px gap let items = this.childrenNodes - .filter( el => (el.nodeType === 1)) // only Element types - .filter( (el, j) => j > 0 ); // skip first element since gaps are needed - this._applyStyleToElements(this._buildCSS(value), items ); + .filter(el => (el.nodeType === 1)) // only Element types + .filter((el, j) => j == 0); + this._applyStyleToElements(this._buildCSS(0), items); + + // For each `element` child, set the padding styles... + items = this.childrenNodes + .filter(el => (el.nodeType === 1)) // only Element types + .filter((el, j) => j > 0); // skip first element since gaps are needed + this._applyStyleToElements(this._buildCSS(value), items); + } /**