Skip to content

Commit

Permalink
fix(fxLayoutGap): add gaps to dynamic content (#124)
Browse files Browse the repository at this point in the history
* fxLayoutGap not applied to children inserted after initial render

Fixes #95.
  • Loading branch information
ThomasBurleson authored and andrewseguin committed Jan 24, 2017
1 parent 976d3c2 commit 6482c12
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 35 deletions.
55 changes: 50 additions & 5 deletions src/lib/flexbox/api/layout-gap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '../../utils/testing/helpers';

describe('layout-gap directive', () => {
let fixture: ComponentFixture<any>;
let createTestComponent = makeCreateTestComponent(() => TestLayoutGapComponent);
let expectDomForQuery = makeExpectDOMForQuery(() => TestLayoutGapComponent);

Expand Down Expand Up @@ -61,7 +62,7 @@ describe('layout-gap directive', () => {
<div fxFlex></div>
</div>
`;
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]");
Expand All @@ -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 = `
<div fxLayoutAlign="center center" fxLayoutGap="13px">
<div fxFlex *ngFor="let row of rows"></div>
</div>
`;
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 = `
<div fxLayoutAlign="center center" fxLayoutGap="13px">
<div fxFlex *ngFor="let row of rows"></div>
</div>
`;
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');
});
Expand All @@ -84,7 +128,7 @@ describe('layout-gap directive', () => {
});

it('should remove obsolete margin and apply valid margin for layout changes', () => {
let fixture: ComponentFixture<any> = createTestComponent(`
fixture = createTestComponent(`
<div [fxLayout]="direction" [fxLayoutGap]="gap">
<span></span>
<span></span>
Expand Down Expand Up @@ -130,10 +174,10 @@ describe('layout-gap directive', () => {
</div>
`;

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});
}

Expand All @@ -156,6 +200,7 @@ describe('layout-gap directive', () => {
export class TestLayoutGapComponent implements OnInit {
direction = "column";
gap = "8px";
rows = new Array(4);

constructor() {
}
Expand Down
117 changes: 87 additions & 30 deletions src/lib/flexbox/api/layout-gap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand All @@ -83,23 +114,43 @@ 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);
});
this._updateWithValue();
}

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
*/
Expand All @@ -108,7 +159,6 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI
if (!LAYOUT_VALUES.find(x => x === this._layout)) {
this._layout = 'row';
}

this._updateWithValue();
}

Expand All @@ -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);

}

/**
Expand Down

0 comments on commit 6482c12

Please sign in to comment.