From 341f56e70b7ad0165b960dcbd91c63b88c0a1702 Mon Sep 17 00:00:00 2001 From: Ed Morales Date: Tue, 19 Jul 2016 11:25:56 -0700 Subject: [PATCH] feature(media-queries): service & directives (#22) * first draft on media service/directive * modified comments and little fixes * media docs/demo (first draft) * fixed unit test * update(cards): href for -prod routes - for link styles * update(media): change name to Media Queries - update description --- .../components/components.component.ts | 5 + .../components/components.routes.ts | 4 + src/app/components/components/media/index.ts | 1 + .../components/media/media.component.html | 134 ++++++++++++++ .../components/media/media.component.scss | 0 .../components/media/media.component.spec.ts | 51 ++++++ .../components/media/media.component.ts | 164 ++++++++++++++++++ .../overview/overview.component.html | 2 +- .../components/overview/overview.component.ts | 5 + src/app/components/home/home.component.html | 2 +- src/main.ts | 2 + src/platform/core/index.ts | 11 ++ .../directives/media-toggle.directive.ts | 100 +++++++++++ .../core/media/services/media.service.ts | 62 +++++++ src/system-config.ts | 1 + 15 files changed, 542 insertions(+), 2 deletions(-) create mode 100644 src/app/components/components/media/index.ts create mode 100644 src/app/components/components/media/media.component.html create mode 100644 src/app/components/components/media/media.component.scss create mode 100644 src/app/components/components/media/media.component.spec.ts create mode 100644 src/app/components/components/media/media.component.ts create mode 100644 src/platform/core/media/directives/media-toggle.directive.ts create mode 100644 src/platform/core/media/services/media.service.ts diff --git a/src/app/components/components/components.component.ts b/src/app/components/components/components.component.ts index 7eccce5eab..7071c053ff 100644 --- a/src/app/components/components/components.component.ts +++ b/src/app/components/components/components.component.ts @@ -54,6 +54,11 @@ export class ComponentsComponent { icon: 'chrome_reader_mode', route: 'markdown', title: 'Markdown', + }, { + description: 'Responsive service & directive', + icon: 'devices', + route: 'media', + title: 'Media Queries', }, { description: 'Custom Angular pipes (filters)', icon: 'filter_list', diff --git a/src/app/components/components/components.routes.ts b/src/app/components/components/components.routes.ts index 99398fba13..8456c8e929 100644 --- a/src/app/components/components/components.routes.ts +++ b/src/app/components/components/components.routes.ts @@ -8,6 +8,7 @@ import { ExpansionPanelDemoComponent } from './expansion-panel'; import { FileUploadDemoComponent } from './file-upload'; import { LoadingDemoComponent } from './loading'; import { MarkdownDemoComponent } from './markdown'; +import { MediaDemoComponent } from './media'; import { PipesComponent } from './pipes'; export const componentsRoutes: RouterConfig = [{ @@ -32,6 +33,9 @@ export const componentsRoutes: RouterConfig = [{ }, { component: MarkdownDemoComponent, path: 'markdown', + }, { + component: MediaDemoComponent, + path: 'media', }, { component: PipesComponent, path: 'pipes', diff --git a/src/app/components/components/media/index.ts b/src/app/components/components/media/index.ts new file mode 100644 index 0000000000..0cc59f4851 --- /dev/null +++ b/src/app/components/components/media/index.ts @@ -0,0 +1 @@ +export { MediaDemoComponent } from './media.component'; diff --git a/src/app/components/components/media/media.component.html b/src/app/components/components/media/media.component.html new file mode 100644 index 0000000000..39446936b8 --- /dev/null +++ b/src/app/components/components/media/media.component.html @@ -0,0 +1,134 @@ + + Media Queries + Responsive service & directive (for attributes) + + +

Media Queries:

+ + + +
+
+ + TdMediaService + How to use this service + + +

TdMediaService

+

This service is designed to provide basic media query evaluation for responsive applications.

+

It has pre-programmed support for media queries that match the layout + breakpoints: +

+ + + +

Methods:

+

The TdMediaService service has {{mediaServiceMethods.length}} properties:

+ + + +

Example:

+

Typescript:

+ + { + this.isSmallScreen = this._mediaService.query('sm'); // or '(min-width: 960px) and (max-width: 1279px)' + }); + } + + watchScreen(): void { + this._querySubscription = this._mediaService.registerQuery('sm').subscribe((matches: boolean) => { + this._ngZone.run(() => { + this.isSmallScreen = matches; + }); + }); + } + + ngOnInit(): void { + this.watchScreen(); + } + + ngOnDestroy(): void { + this._querySubscription.unsubscribe(); + } + } + ]]> + +

Note: Always unsubscribe from [Observable] objects when not using them anymore.

+

A good way of doing it is in the ngOnDestroy component life-cycle hook provided by the [OnDestroy] interface.

+
+
+ + TdMediaToggleDirective + How to use this directive + + +

tdMediaToggle

+

Simply add the tdMediaToggle attibute with a "media query" value to the element you want to modify depending on screen size.

+

Properties:

+

The tdMediaToggle directive has {{mediaAttrs.length}} properties:

+ + + +

Example:

+

HTML:

+ + + ... + + ]]> + +

Typescript:

+ + + +
+
diff --git a/src/app/components/components/media/media.component.scss b/src/app/components/components/media/media.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/components/components/media/media.component.spec.ts b/src/app/components/components/media/media.component.spec.ts new file mode 100644 index 0000000000..dfb884aae9 --- /dev/null +++ b/src/app/components/components/media/media.component.spec.ts @@ -0,0 +1,51 @@ +import { + beforeEach, + addProviders, + describe, + expect, + it, + inject, +} from '@angular/core/testing'; +import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing'; +import { Component, DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { MediaDemoComponent } from './media.component'; +import { TdMediaService } from '../../../../platform/core'; + +describe('Component: MediaDemo', () => { + let builder: TestComponentBuilder; + + beforeEach(() => { + addProviders([ + MediaDemoComponent, + TdMediaService, + ]); + }); + + beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder): void { + builder = tcb; + })); + + it('should inject the component', inject([MediaDemoComponent], (component: MediaDemoComponent) => { + expect(component).toBeTruthy(); + })); + + it('should create the component', inject([], () => { + return builder.createAsync(MediaDemoTestControllerComponent) + .then((fixture: ComponentFixture) => { + let query: DebugElement = fixture.debugElement.query(By.directive(MediaDemoComponent)); + expect(query).toBeTruthy(); + expect(query.componentInstance).toBeTruthy(); + }); + })); +}); + +@Component({ + directives: [MediaDemoComponent], + selector: 'td-test', + template: ` + + `, +}) +class MediaDemoTestControllerComponent { +} diff --git a/src/app/components/components/media/media.component.ts b/src/app/components/components/media/media.component.ts new file mode 100644 index 0000000000..2c0994e3d5 --- /dev/null +++ b/src/app/components/components/media/media.component.ts @@ -0,0 +1,164 @@ +import { Component, OnInit, NgZone, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; + +import { MD_CARD_DIRECTIVES } from '@angular2-material/card'; +import { MD_LIST_DIRECTIVES } from '@angular2-material/list'; +import { MdButton } from '@angular2-material/button'; +import { MD_INPUT_DIRECTIVES } from '@angular2-material/input'; + +import { TdMediaToggleDirective, TdMediaService } from '../../../../platform/core'; +import { TdHighlightComponent } from '../../../../platform/highlight'; + +@Component({ + directives: [ + MD_CARD_DIRECTIVES, + MD_LIST_DIRECTIVES, + MdButton, + MD_INPUT_DIRECTIVES, + TdMediaToggleDirective, + TdHighlightComponent, + ], + moduleId: module.id, + selector: 'td-media-demo', + styleUrls: [ 'media.component.css' ], + templateUrl: 'media.component.html', +}) +export class MediaDemoComponent implements OnInit, OnDestroy { + + private _subcriptions: Subscription[] = []; + + mediaDemo: any[] = [{ + query: 'xs', + value: false, + }, { + query: 'gt-xs', + value: false, + }, { + query: 'sm', + value: false, + }, { + query: 'gt-sm', + value: false, + }, { + query: 'md', + value: false, + }, { + query: 'gt-gm', + value: false, + }, { + query: 'lg', + value: false, + }, { + query: 'gt-lg', + value: false, + }, { + query: 'xl', + value: false, + }, { + query: 'landscape', + value: false, + }, { + query: 'portrait', + value: false, + }, { + query: 'print', + value: false, + }, { + query: '(max-width: 800px)', + value: false, + }, { + query: '(min-width: 700px)', + value: false, + }]; + + mediaServiceMethods: Object[] = [{ + description: `Used to evaluate whether a given media query is true or false given the + current device's screen / window size.`, + name: 'query', + type: 'function(query: string)', + }, { + description: `Registers a media query and returns an [Observable] that will re-evaluate and + return if the given media query matches on window resize.`, + name: 'registerQuery', + type: 'function(query: string)', + }]; + + mediaBreakpoints: Object[] = [{ + breakpoint: 'xs', + query: '(max-width: 599px)', + }, { + breakpoint: 'gt-xs', + query: '(min-width: 600px)', + }, { + breakpoint: 'sm', + query: '(min-width: 600px) and (max-width: 959px)', + }, { + breakpoint: 'gt-sm', + query: '(min-width: 960px)', + }, { + breakpoint: 'md', + query: '(min-width: 960px) and (max-width: 1279px)', + }, { + breakpoint: 'gt-gm', + query: '(min-width: 1280px)', + }, { + breakpoint: 'lg', + query: '(min-width: 1280px) and (max-width: 1919px)', + }, { + breakpoint: 'gt-lg', + query: '(min-width: 1920px)', + }, { + breakpoint: 'xl', + query: '(min-width: 1920px)', + }, { + breakpoint: 'landscape', + query: 'landscape', + }, { + breakpoint: 'portrait', + query: 'portrait', + }, { + breakpoint: 'print', + query: 'print', + }]; + + mediaAttrs: Object[] = [{ + description: `Media query used to evaluate screen/window size. + Toggles attributes, classes and styles if media query is matched.`, + name: 'tdMediaToggle', + type: 'string', + }, { + description: 'Attributes to be toggled when media query matches', + name: 'mediaAttributes?', + type: '{[key: string]: string}', + }, { + description: 'CSS Classes to be toggled when media query matches', + name: 'mediaClasses?', + type: 'string[]', + }, { + description: 'CSS Styles to be toggled when media query matches', + name: 'mediaStyles?', + type: '{[key: string]: string}', + }]; + + constructor(private _mediaService: TdMediaService, private _ngZone: NgZone) { } + + ngOnInit(): void { + for (let demoObj of this.mediaDemo) { + this._ngZone.run(() => { + demoObj.value = this._mediaService.query(demoObj.query); + }); + this._subcriptions.push(this._mediaService.registerQuery(demoObj.query).subscribe((matches: boolean) => { + this._ngZone.run(() => { + demoObj.value = matches; + }); + })); + } + } + + ngOnDestroy(): void { + this._subcriptions.forEach((subs: Subscription) => { + subs.unsubscribe(); + }); + } + +} diff --git a/src/app/components/components/overview/overview.component.html b/src/app/components/components/overview/overview.component.html index 8fd39fe9b9..f3dac79d15 100644 --- a/src/app/components/components/overview/overview.component.html +++ b/src/app/components/components/overview/overview.component.html @@ -8,7 +8,7 @@