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

feat(srcset): add srcset directive to inject <source> elements to sup… #366

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/demo-app/app/docs-layout-responsive/_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Component } from '@angular/core';
<demo-responsive-flex-order class='small-demo'> </demo-responsive-flex-order>
<demo-responsive-show-hide class='small-demo'> </demo-responsive-show-hide>
<demo-responsive-style class='small-demo'> </demo-responsive-style>
<demo-responsive-picture class='small-demo'> </demo-responsive-picture>
`
})
export class DemosResponsiveLayout { }
Expand All @@ -23,6 +24,7 @@ import {DemoResponsiveShowHide} from './responsiveShowHide.demo';
import {DemoResponsiveFlexDirectives} from './responsiveFlexDirective.demo';
import {DemoResponsiveFlexOrder} from './responsiveFlexOrder.demo';
import {DemoResponsiveStyle} from './responsiveStyle.demo';
import {DemoResponsivePicture} from './responsivePicture.demo';

@NgModule({
declarations : [
Expand All @@ -33,7 +35,8 @@ import {DemoResponsiveStyle} from './responsiveStyle.demo';
DemoResponsiveFlexDirectives,
DemoResponsiveFlexOrder,
DemoResponsiveShowHide,
DemoResponsiveStyle
DemoResponsiveStyle,
DemoResponsivePicture
],
imports : [
SharedModule,
Expand Down
53 changes: 53 additions & 0 deletions src/demo-app/app/docs-layout-responsive/responsivePicture.demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {Component} from '@angular/core';

@Component({
moduleId: module.id,
selector: 'demo-responsive-picture',
template: `

<md-card class="card-demo" >
<md-card-title>Responsive Picture</md-card-title>
<md-card-subtitle>
Use the srcset API on an &lt;img&gt; to inject &lt;source&gt; elements within a
&lt;picture&gt; container.
</md-card-subtitle>

<md-card-content>
<div class="containerX">
<div fxLayout="row" fxFlex class="coloredContainerX box">
<picture>
<img style="width:auto;"
src="https://dummyimage.com/400x200/c7c224/000.png&text=default"
srcset.md="https://dummyimage.com/500x200/76c720/fff.png&text=md"
srcset.sm="https://dummyimage.com/400x200/b925c7/fff.png&text=sm"
srcset.lt-sm="https://dummyimage.com/300x200/c7751e/fff.png&text=lt-sm(1x) 1x,
https://dummyimage.com/300x200/f0b16e/fff.png&text=lt-sm(2x) 2x,
https://dummyimage.com/300x200/f6ca9a/fff.png&text=lt-sm(3x) 3x"
srcset.gt-lg="https://dummyimage.com/700x200/258cc7/fff.png&text=gt-lg" >
</picture>
</div>
</div>
</md-card-content>
<md-card-content>
<pre>
&lt;picture&gt;
&lt;img style="width:auto;"
src="https://dummyimage.com/400x200/c7c224/000.png&text=default"
srcset.md="https://dummyimage.com/500x200/76c720/fff.png&text=md"
srcset.sm="https://dummyimage.com/400x200/b925c7/fff.png&text=sm"
srcset.lt-sm="https://dummyimage.com/300x200/c7751e/fff.png&text=lt-sm(1x) 1x,
https://dummyimage.com/300x200/f0b16e/fff.png&text=lt-sm(2x) 2x,
https://dummyimage.com/300x200/f6ca9a/fff.png&text=lt-sm(3x) 3x"
srcset.gt-lg="https://dummyimage.com/700x200/258cc7/fff.png&text=gt-lg" &gt;
&lt;/picture&gt;
</pre>
</md-card-content>

<md-card-footer style="width:95%">
<media-query-status></media-query-status>
</md-card-footer>
</md-card>
`
})
export class DemoResponsivePicture {
}
10 changes: 7 additions & 3 deletions src/lib/flexbox/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
return this._elementRef.nativeElement.parentNode;
}

protected get nativeElement(): any {
return this._elementRef.nativeElement;
}

/**
* Access the current value (if any) of the @Input property.
*/
Expand Down Expand Up @@ -130,7 +134,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
* and optional restore it when the mediaQueries deactivate
*/
protected _getDisplayStyle(source?: HTMLElement): string {
let element: HTMLElement = source || this._elementRef.nativeElement;
let element: HTMLElement = source || this.nativeElement;
return lookupStyle(element, 'display');
}

Expand Down Expand Up @@ -161,7 +165,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
protected _applyStyleToElement(style: StyleDefinition,
value?: string | number,
nativeElement?: any) {
let element = nativeElement || this._elementRef.nativeElement;
let element = nativeElement || this.nativeElement;
applyStyleToElement(this._renderer, element, style, value);
}

Expand Down Expand Up @@ -209,7 +213,7 @@ 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.children;
const obj = this.nativeElement.children;
const buffer = [];

// iterate backwards ensuring that length is an UInt32
Expand Down
232 changes: 232 additions & 0 deletions src/lib/flexbox/api/img-srcset.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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 {Component, OnInit} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';

import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../media-query/breakpoints/break-points-provider';
import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry';
import {MockMatchMedia} from '../../media-query/mock/mock-match-media';
import {MatchMedia} from '../../media-query/match-media';
import {FlexLayoutModule} from '../../module';

import {customMatchers} from '../../utils/testing/custom-matchers';
import {makeCreateTestComponent, queryFor} from '../../utils/testing/helpers';
import {expect} from '../../utils/testing/custom-matchers';
import {_dom as _} from '../../utils/testing/dom-tools';

const SRCSET_URLS_MAP = {
'xs': [
'https://dummyimage.com/300x200/c7751e/fff.png',
'https://dummyimage.com/300x200/c7751e/000.png'
],
'gt-xs': [
'https://dummyimage.com/400x250/c7c224/fff.png',
'https://dummyimage.com/400x250/c7c224/000.png'
],
'md': [
'https://dummyimage.com/500x300/76c720/fff.png',
'https://dummyimage.com/500x300/76c720/000.png'
],
'lt-lg': [
'https://dummyimage.com/600x350/25c794/fff.png',
'https://dummyimage.com/600x350/25c794/000.png'
],
'lg': [
'https://dummyimage.com/700x400/258cc7/fff.png',
'https://dummyimage.com/700x400/258cc7/000.png'
],
'lt-xl': [
'https://dummyimage.com/800x500/b925c7/ffffff.png',
'https://dummyimage.com/800x500/b925c7/000.png'
]
};
const DEFAULT_SRC = 'https://dummyimage.com/300x300/c72538/ffffff.png';

describe('srcset directive', () => {
let fixture: ComponentFixture<any>;
let matchMedia: MockMatchMedia;
let breakpoints: BreakPointRegistry;

let componentWithTemplate = (template: string) => {
fixture = makeCreateTestComponent(() => TestSrcsetComponent)(template);

inject([MatchMedia, BreakPointRegistry],
(_matchMedia: MockMatchMedia, _breakpoints: BreakPointRegistry) => {
matchMedia = _matchMedia;
breakpoints = _breakpoints;
})();
};

beforeEach(() => {
jasmine.addMatchers(customMatchers);

// Configure testbed to prepare services
TestBed.configureTestingModule({
imports: [CommonModule, FlexLayoutModule],
declarations: [TestSrcsetComponent],
providers: [
BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER,
{provide: MatchMedia, useClass: MockMatchMedia}
]
});
});

it('should work when no srcset flex-layout directive is used', () => {
const template = `
<picture>
<img style="width:auto;" src="${DEFAULT_SRC}" >
</picture>
`;
componentWithTemplate(template);
fixture.detectChanges();

const nodes = queryFor(fixture, 'source');
const pictureElt = queryFor(fixture, 'picture')[0].nativeElement;

expect(nodes.length).toBe(0);
expect(pictureElt.children.length).toEqual(1);
expect(_.tagName(_.lastElementChild(pictureElt))).toEqual('IMG');
});

it('should keep img as the last child tag of <picture> after source tags injection', () => {
const template = `
<div>
<picture>
<img style="width:auto;"
src="${DEFAULT_SRC}"
srcset.gt-xs="${SRCSET_URLS_MAP['gt-xs'][0]}"
srcset.lt-lg="${SRCSET_URLS_MAP['lt-lg'][0]}" >
</picture>
</div>
`;
componentWithTemplate(template);
fixture.detectChanges();

const pictureElt = queryFor(fixture, 'picture')[0].nativeElement;

expect(_.tagName(_.lastElementChild(pictureElt))).toEqual('IMG');
});

it('should inject source elements from largest to smallest corresponding media queries', () => {
const template = `
<picture>
<img style="width:auto;"
src="${DEFAULT_SRC}"
srcset.xs="${SRCSET_URLS_MAP['xs'][0]}"
srcset.lg="${SRCSET_URLS_MAP['lg'][0]}"
srcset.md="${SRCSET_URLS_MAP['md'][0]}" >
</picture>
`;
componentWithTemplate(template);
fixture.detectChanges();

const nodes = queryFor(fixture, 'source');

expect(nodes.length).toBe(3);
expect(nodes[0].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['lg'][0]}`,
media: breakpoints.findByAlias('lg').mediaQuery
});
expect(nodes[1].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['md'][0]}`,
media: breakpoints.findByAlias('md').mediaQuery
});
expect(nodes[2].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['xs'][0]}`,
media: breakpoints.findByAlias('xs').mediaQuery
});
});

it('should update source elements srcset values when srcset input properties change', () => {
const template = `
<picture>
<img style="width:auto;"
src="${DEFAULT_SRC}"
[srcset.xs]="xsSrcSet"
[srcset.lg]="lgSrcSet"
[srcset.md]="mdSrcSet" >
</picture>
`;
componentWithTemplate(template);
fixture.detectChanges();

fixture.componentInstance.xsSrcSet = SRCSET_URLS_MAP['xs'][1];
fixture.componentInstance.mdSrcSet = SRCSET_URLS_MAP['md'][1];
fixture.componentInstance.lgSrcSet = SRCSET_URLS_MAP['lg'][1];
fixture.detectChanges();

let nodes = queryFor(fixture, 'source');

expect(nodes.length).toBe(3);
expect(nodes[0].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['lg'][1]}`,
media: breakpoints.findByAlias('lg').mediaQuery
});
expect(nodes[1].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['md'][1]}`,
media: breakpoints.findByAlias('md').mediaQuery
});
expect(nodes[2].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['xs'][1]}`,
media: breakpoints.findByAlias('xs').mediaQuery
});
});

it('should work with overlapping breakpoints', () => {
const template = `
<picture>
<img style="width:auto;"
src="${DEFAULT_SRC}"
srcset.lt-xl="${SRCSET_URLS_MAP['lt-xl'][0]}"
srcset.xs="${SRCSET_URLS_MAP['xs'][0]}"
srcset.lt-lg="${SRCSET_URLS_MAP['lt-lg'][0]}" >
</picture>
`;
componentWithTemplate(template);
fixture.detectChanges();

let nodes = queryFor(fixture, 'source');
expect(nodes[0].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['lt-xl'][0]}`,
media: breakpoints.findByAlias('lt-xl').mediaQuery
});
expect(nodes[1].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['lt-lg'][0]}`,
media: breakpoints.findByAlias('lt-lg').mediaQuery
});
expect(nodes[2].nativeElement).toHaveAttributes({
srcset: `${SRCSET_URLS_MAP['xs'][0]}`,
media: breakpoints.findByAlias('xs').mediaQuery
});
});
});

// *****************************************************************
// Template Component
// *****************************************************************

@Component({
selector: 'test-srcset-api',
template: ''
})
export class TestSrcsetComponent implements OnInit {
xsSrcSet: string;
mdSrcSet: string;
lgSrcSet: string;
constructor() {
this.xsSrcSet = SRCSET_URLS_MAP['xs'][0];
this.mdSrcSet = SRCSET_URLS_MAP['md'][0];
this.lgSrcSet = SRCSET_URLS_MAP['lg'][0];
}

ngOnInit() {
}
}


Loading