From 8f017c91cb657a26248e8de29cb66d4468bd9446 Mon Sep 17 00:00:00 2001 From: Ben Nadel Date: Mon, 4 Jul 2016 14:41:10 -0400 Subject: [PATCH 1/4] Cookbook for a component item template. This cookbook demonstrates how to provide a component with an external `TempalteRef` that the component can then use to render aspects of its own internal template. An example of this, as explored in the cookbook, is providing a select-menu component with an item template that the component can use to render the options in its dropdown. --- .../_examples/cb-item-template/e2e-spec.ts | 43 ++++++++ .../cb-item-template/ts/app/app.component.ts | 60 +++++++++++ .../_examples/cb-item-template/ts/app/main.ts | 12 +++ .../ts/app/simple-select.component.ts | 85 +++++++++++++++ .../cb-item-template/ts/example-config.json | 0 .../_examples/cb-item-template/ts/index.html | 39 +++++++ .../_examples/cb-item-template/ts/plnkr.json | 9 ++ .../_examples/cb-item-template/ts/sample.css | 65 ++++++++++++ public/docs/ts/latest/cookbook/_data.json | 5 + .../ts/latest/cookbook/item-template.jade | 98 ++++++++++++++++++ .../item-template/item-template-animation.gif | Bin 0 -> 2303349 bytes 11 files changed, 416 insertions(+) create mode 100644 public/docs/_examples/cb-item-template/e2e-spec.ts create mode 100644 public/docs/_examples/cb-item-template/ts/app/app.component.ts create mode 100644 public/docs/_examples/cb-item-template/ts/app/main.ts create mode 100644 public/docs/_examples/cb-item-template/ts/app/simple-select.component.ts create mode 100644 public/docs/_examples/cb-item-template/ts/example-config.json create mode 100644 public/docs/_examples/cb-item-template/ts/index.html create mode 100644 public/docs/_examples/cb-item-template/ts/plnkr.json create mode 100644 public/docs/_examples/cb-item-template/ts/sample.css create mode 100644 public/docs/ts/latest/cookbook/item-template.jade create mode 100644 public/resources/images/cookbooks/item-template/item-template-animation.gif diff --git a/public/docs/_examples/cb-item-template/e2e-spec.ts b/public/docs/_examples/cb-item-template/e2e-spec.ts new file mode 100644 index 0000000000..48bc04daed --- /dev/null +++ b/public/docs/_examples/cb-item-template/e2e-spec.ts @@ -0,0 +1,43 @@ +/// +'use strict'; +// FIRST RUN: gulp run-e2e-tests --filter=cb-item-template +// SUBSEQUENT RUNS: gulp run-e2e-tests --filter=cb-item-template --fast +describe('Item template renderer', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should toggle the menu', function () { + + expect( element( by.className( 'select-items' ) ).isPresent() ).toBe( false ); + element( by.className( 'select-root' ) ).click(); + expect( element( by.className( 'select-items' ) ).isPresent() ).toBe( true ); + element( by.className( 'select-root' ) ).click(); + expect( element( by.className( 'select-items' ) ).isPresent() ).toBe( false ); + + }); + + it('should select last item', function () { + + // Make sure all menu roots start out with Black. + element.all( by.className( 'select-root' ) ).each( + function iterator( element ) { + expect( element.getText() ).toContain( 'Black' ); + } + ); + + // Select Magenta. + element( by.className( 'select-root' ) ).click(); + element.all( by.css( 'ul.select-items li' ) ).last().click(); + + // Make sure all menu roots reflect selection (since they are sharing same model). + element.all( by.className( 'select-root' ) ).each( + function iterator( element ) { + expect( element.getText() ).toContain( 'Magenta' ); + } + ); + + }); + +}); diff --git a/public/docs/_examples/cb-item-template/ts/app/app.component.ts b/public/docs/_examples/cb-item-template/ts/app/app.component.ts new file mode 100644 index 0000000000..4e5dfecfc4 --- /dev/null +++ b/public/docs/_examples/cb-item-template/ts/app/app.component.ts @@ -0,0 +1,60 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { SimpleSelectComponent } from './simple-select.component'; + +interface Color { + hex: string; + name: string; +} + +// #docregion metadata +@Component({ + selector: 'my-app', + directives: [ SimpleSelectComponent ], + template: + ` +

+ Selected color: {{ selectedColor?.name || 'None selected' }}. +

+ + + + + + + + + + ` +}) +// #enddocregion metadata +export class AppComponent { + + public colors: Color[]; + public selectedColor: Color; + + public constructor() { + this.colors = [ + { hex: '#000000', name: 'Black' }, + { hex: '#FFFFFF', name: 'White' }, + { hex: '#FFD700', name: 'Gold' }, + { hex: '#7FFFD4', name: 'Aquamarine' }, + { hex: '#800080', name: 'Purple' }, + { hex: '#6DC066', name: 'Green' }, + { hex: '#FF00FF', name: 'Magenta' } + ]; + this.selectedColor = this.colors[ 0 ]; + } + +} diff --git a/public/docs/_examples/cb-item-template/ts/app/main.ts b/public/docs/_examples/cb-item-template/ts/app/main.ts new file mode 100644 index 0000000000..a6a7c2e4cc --- /dev/null +++ b/public/docs/_examples/cb-item-template/ts/app/main.ts @@ -0,0 +1,12 @@ +/* tslint:disable */ +// #docregion +import { bootstrap } from '@angular/platform-browser-dynamic'; +import { AppComponent } from './app.component'; + +bootstrap(AppComponent).then( + () => window.console.info( 'Angular finished bootstrapping your application!' ), + (error) => { + console.warn( 'Angular was not able to bootstrap your application.' ); + console.error( error ); + } +); diff --git a/public/docs/_examples/cb-item-template/ts/app/simple-select.component.ts b/public/docs/_examples/cb-item-template/ts/app/simple-select.component.ts new file mode 100644 index 0000000000..56ec63659d --- /dev/null +++ b/public/docs/_examples/cb-item-template/ts/app/simple-select.component.ts @@ -0,0 +1,85 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { ContentChild } from '@angular/core'; +import { EventEmitter } from '@angular/core'; +import { Input } from '@angular/core'; +import { Output } from '@angular/core'; +import { TemplateRef } from '@angular/core'; + +interface ItemContext { + item: any; + index: number; +} + +// #docregion component +// #docregion metadata +@Component({ + selector: 'simple-select', + template: + ` + + + + ` +}) +// #enddocregion metadata +export class SimpleSelectComponent { + + public isShowingItems: boolean; + @Input() public items: any[]; + public itemTemplateRef: TemplateRef; + @Input() public value: any; + @Output() public valueChange: EventEmitter; + + constructor() { + this.isShowingItems = false; + this.items = []; + this.itemTemplateRef = null; + this.value = null; + this.valueChange = new EventEmitter(); + } + + // #docregion setters + @Input() + set template( newTemplate: TemplateRef ) { + if ( newTemplate ) { + this.itemTemplateRef = newTemplate; + } + } + + @ContentChild( TemplateRef ) + set contentChildTemplateRef( newTemplate: TemplateRef ) { + if ( newTemplate ) { + this.itemTemplateRef = newTemplate; + } + } + // #enddocregion setters + + public selectItem( item: any ): void { + this.valueChange.emit( item ); + this.toggleItems(); + } + + public toggleItems(): void { + this.isShowingItems = ! this.isShowingItems; + } + +} +// #enddocregion component diff --git a/public/docs/_examples/cb-item-template/ts/example-config.json b/public/docs/_examples/cb-item-template/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-item-template/ts/index.html b/public/docs/_examples/cb-item-template/ts/index.html new file mode 100644 index 0000000000..4b67a30640 --- /dev/null +++ b/public/docs/_examples/cb-item-template/ts/index.html @@ -0,0 +1,39 @@ + + + + + + + + Providing An Item Template To A Component + + + + + + + + + + + + + + + + + +

+ Providing An Item Template To A Component +

+ + + Loading app... + + + + diff --git a/public/docs/_examples/cb-item-template/ts/plnkr.json b/public/docs/_examples/cb-item-template/ts/plnkr.json new file mode 100644 index 0000000000..0131610c20 --- /dev/null +++ b/public/docs/_examples/cb-item-template/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Set The Document Title In Angular 2", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": [ "cookbook" ] +} diff --git a/public/docs/_examples/cb-item-template/ts/sample.css b/public/docs/_examples/cb-item-template/ts/sample.css new file mode 100644 index 0000000000..927345d292 --- /dev/null +++ b/public/docs/_examples/cb-item-template/ts/sample.css @@ -0,0 +1,65 @@ +a { + color: #607D8B ; + text-decoration: underline ; +} + +simple-select { + cursor: pointer ; + display: 'inline-block' ; + font-size: 16px ; + position: relative ; + user-select: none ; + -moz-user-select: none ; + -webkit-user-select: none ; +} + +simple-select button.select-root { + background-color: #FFFFFF ; + border: 1px solid #CCCCCC ; + border-radius: 3px 3px 3px 3px ; + color: #333333 ; + font-size: inherit ; + padding: 7px 10px 7px 10px ; + position: relative ; +} + +simple-select ul.select-items { + background-color: #FFFFFF ; + border: 1px solid #CCCCCC ; + border-radius: 3px 3px 3px 3px ; + left: 0px ; + list-style-type: none ; + margin: 0px 0px 0px 0px ; + padding: 0px 0px 0px 0px ; + position: absolute ; + top: 102% ; + white-space: nowrap ; + z-index: 2 ; +} + +simple-select ul.select-items li { + border-top: 1px solid #CCCCCC ; + margin: 0px 0px 0px 0px ; + padding: 7px 10px 7px 10px ; + position: relative ; +} + +simple-select ul.select-items li:first-child { + border-top-width: 0px ; +} + +/* Custom template styles. */ + +simple-select { + display: table ; + margin: 16px 0px 16px 0px ; +} + +simple-select span.swatch { + border: 1px solid #CCCCCC ; + border-radius: 3px 3px 3px 3px ; + display: inline-block ; + height: 13px ; + margin: 2px 5px 0px 0px ; + width: 13px ; +} \ No newline at end of file diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index f82d816cef..579c5321c4 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -48,6 +48,11 @@ "intro": "Migrate your RC4 app to RC5 in minutes." }, + "item-template": { + "title": "Item Template", + "intro": "Providing a component with an item template for dynamic rendering." + }, + "set-document-title": { "title": "Set the Document Title", "intro": "Setting the document or window title using the Title service." diff --git a/public/docs/ts/latest/cookbook/item-template.jade b/public/docs/ts/latest/cookbook/item-template.jade new file mode 100644 index 0000000000..dcafa5f555 --- /dev/null +++ b/public/docs/ts/latest/cookbook/item-template.jade @@ -0,0 +1,98 @@ +include ../_util-fns + +a( id='top' ) +:marked + If a component needs to render a collection of items, we can keep the component generic + by providing the component with a `TemplateRef` that the component can use to render + the individual items. This way, the component's scope of responsibility is limited + while its reusability is enhanced. The `TemplateRef` can be explicitly injected into + the component as an input property; or, the `TemplateRef` can be implicitly provided as + part of the component's content. + +:marked + To explore this concept, we'll create a simple Select menu component that takes an + `items` input collection and uses an externally-provided `TemplateRef` to render each + item in the menu options list. Then, we'll instantiate the Select menu component twice + in order to demonstrate the various ways in which we can provide the `TemplateRef`. + +:marked + In the following root-component meta-data, each Select menu (selector: `simple-select`) + is rendering the same collection of color `items`; and, each select menu is binding to + the same `value`. The only difference between the two instances is in how the + `TemplateRef` is being defined: + +:marked + **See the [live example](/resources/live-examples/cb-item-template/ts/plnkr.html)**. + ++makeExample('cb-item-template/ts/app/app.component.ts', 'metadata', 'app/app.component.ts (metadata)')(format='.') + +:marked + In the first ``, the `TemplateRef` is being defined as a child of the + `` element. In the second ``, the `TemplateRef` is being + defined as a sibling element and is then being passed-in as the `[template]` property. + +.l-sub-section + :marked + There's no requirement that a component must accept a `TemplateRef` from multiple + sources. It does make the component more flexible; but, we're only doing it for + demonstrative purposes in this cookbook. + +:marked + Since the component is capable of accepting a `TemplateRef` by two different means, + we're going to define two setter methods — one for the `[template]` input and one + for the content query — that coalesce the two sources into a single `TemplateRef` + property. + ++makeExample('cb-item-template/ts/app/simple-select.component.ts', 'setters', 'app/simple-select.component.ts (TemplateRef setters')(format='.') + +:marked + Once the two `TemplateRef` sources have been coalesced into a single `itemTemplateRef` + property, we can then use it to render both the menu items and the menu root. + ++makeExample('cb-item-template/ts/app/simple-select.component.ts', 'metadata', 'app/simple-select.component.ts (metadata')(format='.') + +:marked + When we use the `