From 1c718b9213f6b433275686b40c43be933d92e945 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Sat, 19 Dec 2015 00:27:30 -0600 Subject: [PATCH] refactor(radio): query buttons via @ContentChildren --- ionic/components/list/list.ts | 31 +- ionic/components/radio/radio.ts | 315 +++++++++----------- ionic/components/radio/test/basic/main.html | 2 +- ionic/components/segment/segment.ts | 17 +- 4 files changed, 171 insertions(+), 194 deletions(-) diff --git a/ionic/components/list/list.ts b/ionic/components/list/list.ts index 6c4133cadf..8fd888ff64 100644 --- a/ionic/components/list/list.ts +++ b/ionic/components/list/list.ts @@ -1,10 +1,10 @@ -import {Directive, ElementRef, NgZone} from 'angular2/core'; +import {Directive, ElementRef, Renderer, Attribute, NgZone} from 'angular2/core'; import {Ion} from '../ion'; import {Config} from '../../config/config'; import {ListVirtualScroll} from './virtual'; import {ItemSlidingGesture} from '../item/item-sliding-gesture'; -import * as util from '../../util'; +import {isDefined} from '../../util'; /** * The List is a widely used interface element in almost any mobile app, and can include @@ -42,7 +42,7 @@ export class List extends Ion { ngOnInit() { super.ngOnInit(); - if (util.isDefined(this.virtual)) { + if (isDefined(this.virtual)) { console.log('Content', this.content); console.log('Virtual?', this.virtual); console.log('Items?', this.items.length, 'of \'em'); @@ -138,12 +138,21 @@ export class List extends Ion { * @private */ @Directive({ - selector: 'ion-list-header', - inputs: [ - 'id' - ], - host: { - '[attr.id]': 'id' - } + selector: 'ion-list-header' }) -export class ListHeader {} +export class ListHeader { + + constructor(private _renderer: Renderer, private _elementRef: ElementRef, @Attribute('id') id:string){ + this._id = id; + } + + public get id() { + return this._id; + } + + public set id(val) { + this._id = val; + this._renderer.setElementAttribute(this._elementRef, 'id', val); + } + +} diff --git a/ionic/components/radio/radio.ts b/ionic/components/radio/radio.ts index d4816e9cd1..ee9c312435 100644 --- a/ionic/components/radio/radio.ts +++ b/ionic/components/radio/radio.ts @@ -1,10 +1,84 @@ -import {Component, Directive, ElementRef, Host, Optional, Query, QueryList} from 'angular2/core'; +import {Component, Directive, ElementRef, Renderer, Optional, Input, Output, HostListener, ContentChildren, ContentChild, EventEmitter} from 'angular2/core'; import {NgControl} from 'angular2/common'; -import {Config} from '../../config/config'; -import {Ion} from '../ion'; import {ListHeader} from '../list/list'; import {Form} from '../../util/form'; +import {isDefined} from '../../util/util'; + + +/** + * @description + * A radio button with a unique value. Note that all `` components + * must be wrapped within a ``, and there must be at + * least two `` components within the radio group. + * + * See the [Angular 2 Docs](https://angular.io/docs/js/latest/api/forms/) for more info on forms and input. + * + * @usage + * ```html + * + * Radio Label + * + * ``` + * @demo /docs/v2/demos/radio/ + * @see {@link /docs/v2/components#radio Radio Component Docs} + */ +@Component({ + selector: 'ion-radio', + host: { + '[attr.id]': 'id', + '[attr.aria-disabled]': 'disabled', + '[attr.aria-labelledby]': 'labelId', + 'class': 'item', + 'role': 'radio', + 'tappable': '', + 'tabindex': '0' + }, + template: + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' +}) +export class RadioButton { + @Input() value: string = ''; + @Input() checked: boolean = false; + @Input() disabled: boolean = false; + @Input() id: string; + @Output() select: EventEmitter = new EventEmitter(); + + constructor(private _form: Form, private _renderer: Renderer, private _elementRef: ElementRef) { + this.isChecked = this.checked; + _renderer.setElementAttribute(_elementRef, 'checked', null); + } + + /** + * @private + */ + ngOnInit() { + if (!this.id) { + this.id = 'rb-' + this._form.nextId(); + } + this.labelId = 'lbl-' + this.id; + } + + /** + * @private + */ + @HostListener('click', ['$event']) + private onClick(ev) { + console.debug('RadioButton, select', this.value); + this.select.emit(ev, this.value); + } + + public set isChecked(isChecked) { + this._renderer.setElementAttribute(this._elementRef, 'aria-checked', isChecked); + } +} /** @@ -18,26 +92,34 @@ import {Form} from '../../util/form'; * * @usage * ```html - * + * * * - * Clientside + * Auto Manufactures * * - * - * Ember + * + * Cord * * - * - * Angular 1 + * + * Duesenberg * * - * - * Angular 2 + * + * Hudson * * - * - * React + * + * Packard + * + * + * + * Studebaker + * + * + * + * Tucker * * * @@ -48,79 +130,27 @@ import {Form} from '../../util/form'; @Directive({ selector: '[radio-group]', host: { - 'role': 'radiogroup', '[attr.aria-activedescendant]': 'activeId', - '[attr.aria-describedby]': 'describedById', + 'role': 'radiogroup' } }) -export class RadioGroup extends Ion { - radios: Array = []; +export class RadioGroup { + @Output() change: EventEmitter = new EventEmitter(); + @ContentChildren(RadioButton) private _buttons; + @ContentChild(ListHeader) private _header; - constructor( - elementRef: ElementRef, - config: Config, - @Optional() ngControl: NgControl, - @Query(ListHeader) private _headerQuery: QueryList - ) { - super(elementRef, config); + constructor(@Optional() ngControl: NgControl, private _renderer: Renderer, private _elementRef: ElementRef) { this.ngControl = ngControl; this.id = ++radioGroupIds; this.radioIds = -1; this.onChange = (_) => {}; this.onTouched = (_) => {}; - if (ngControl) this.ngControl.valueAccessor = this; - } - - /** - * @private - */ - ngOnInit() { - let header = this._headerQuery.first; - if (header) { - if (!header.id) { - header.id = 'radio-header-' + this.id; - } - this.describedById = header.id; + if (ngControl) { + this.ngControl.valueAccessor = this; } } - /** - * @private - * Register the specified radio button with the radio group. - * @param {RadioButton} radio The radio button to register. - */ - registerRadio(radio) { - radio.id = radio.id || ('radio-' + this.id + '-' + (++this.radioIds)); - this.radios.push(radio); - - if (this.value == radio.value) { - radio.check(this.value); - } - - if (radio.checked) { - this.value = radio.value; - this.onChange(this.value); - this.activeId = radio.id; - } - } - - /** - * @private - * Update which radio button in the group is checked, unchecking all others. - * @param {RadioButton} checkedRadio The radio button to check. - */ - update(checkedRadio) { - this.value = checkedRadio.value; - this.activeId = checkedRadio.id; - - for (let radio of this.radios) { - radio.checked = (radio === checkedRadio); - } - - this.onChange(this.value); - } - /** * @private * Angular2 Forms API method called by the model (Control) on change to update @@ -128,9 +158,46 @@ export class RadioGroup extends Ion { * https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34 */ writeValue(value) { - this.value = value; - for (let radio of this.radios) { - radio.checked = (radio.value == value); + this.value = isDefined(value) ? value : ''; + if (this._buttons) { + let buttons = this._buttons.toArray(); + for (let button of buttons) { + let isChecked = (button.value === this.value); + button.isChecked = isChecked; + if (isChecked) { + this._renderer.setElementAttribute(this._elementRef, 'aria-activedescendant', button.id); + } + } + } + } + + /** + * @private + */ + ngAfterContentInit() { + let header = this._header; + if (header) { + if (!header.id) { + header.id = 'rg-hdr-' + this.id; + } + this._renderer.setElementAttribute(this._elementRef, 'aria-describedby', header.id); + } + + let buttons = this._buttons.toArray(); + for (let button of buttons) { + button.select.subscribe(() => { + this.writeValue(button.value); + this.onChange(button.value); + this.change.emit(this.value); + }); + + if (isDefined(this.value)) { + let isChecked = (button.value === this.value) || button.checked; + button.isChecked = isChecked; + if (isChecked) { + this._renderer.setElementAttribute(this._elementRef, 'aria-activedescendant', button.id); + } + } } } @@ -152,100 +219,4 @@ export class RadioGroup extends Ion { registerOnTouched(fn) { this.onTouched = fn; } } - -/** - * @description - * A single radio component. - * - * See the [Angular 2 Docs](https://angular.io/docs/js/latest/api/forms/) for more info on forms and input. - * - * @usage - * ```html - * - * Radio Label - * - * ``` - * @demo /docs/v2/demos/radio/ - * @see {@link /docs/v2/components#radio Radio Component Docs} - */ -@Component({ - selector: 'ion-radio', - inputs: [ - 'value', - 'checked', - 'disabled', - 'id' - ], - host: { - 'role': 'radio', - 'tappable': '', - '[attr.id]': 'id', - '[tabindex]': 'tabIndex', - '[attr.aria-checked]': 'checked', - '[attr.aria-disabled]': 'disabled', - '[attr.aria-labelledby]': 'labelId', - '(click)': 'click($event)', - 'class': 'item' - }, - template: - '
' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '
' -}) -export class RadioButton extends Ion { - - constructor( - @Host() @Optional() group: RadioGroup, - elementRef: ElementRef, - config: Config, - private _form: Form - ) { - super(elementRef, config); - - this.group = group; - this.tabIndex = 0; - } - - /** - * @private - */ - ngOnInit() { - super.ngOnInit(); - if (!this.id) { - this.id = 'rb-' + this._form.nextId(); - } - this.labelId = 'lbl-' + this.id; - - if (this.group) { - this.group.registerRadio(this); - } else { - console.error(' must be within a '); - } - } - - /** - * @private - */ - click(ev) { - ev.preventDefault(); - ev.stopPropagation(); - this.check(); - } - - /** - * Update the checked state of this radio button. - * TODO: Call this toggle? Since unchecks as well - */ - check() { - this.checked = !this.checked; - this.group.update(this); - } - -} - let radioGroupIds = -1; diff --git a/ionic/components/radio/test/basic/main.html b/ionic/components/radio/test/basic/main.html index 0bc6ee37ba..0ec2435ad8 100644 --- a/ionic/components/radio/test/basic/main.html +++ b/ionic/components/radio/test/basic/main.html @@ -45,7 +45,7 @@
- + Currencies {{currency}} diff --git a/ionic/components/segment/segment.ts b/ionic/components/segment/segment.ts index 0db83bbabc..16764c152d 100644 --- a/ionic/components/segment/segment.ts +++ b/ionic/components/segment/segment.ts @@ -1,7 +1,6 @@ -import {Directive, Renderer, ElementRef, Host, Optional, EventEmitter, HostBinding, Input, Output, ContentChildren, HostListener} from 'angular2/core'; +import {Directive, ElementRef, Renderer, Optional, EventEmitter, Input, Output, HostListener, ContentChildren} from 'angular2/core'; import {NgControl} from 'angular2/common'; -import {Config} from '../../config/config'; import {isDefined} from '../../util/util'; @@ -55,7 +54,7 @@ import {isDefined} from '../../util/util'; } }) export class SegmentButton { - @Input() value: string + @Input() value: string; @Output() select: EventEmitter = new EventEmitter(); constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} @@ -133,9 +132,7 @@ export class SegmentButton { }) export class Segment { @Output() change: EventEmitter = new EventEmitter(); - - @ContentChildren(SegmentButton) buttons; - + @ContentChildren(SegmentButton) _buttons; value: any; constructor( @@ -154,9 +151,9 @@ export class Segment { * Write a new value to the element. */ writeValue(value) { - this.value = !value ? '' : value; - if (this.buttons) { - let buttons = this.buttons.toArray(); + this.value = isDefined(value) ? value : ''; + if (this._buttons) { + let buttons = this._buttons.toArray(); for (let button of buttons) { button.isActive = (button.value === this.value); } @@ -167,7 +164,7 @@ export class Segment { * @private */ ngAfterViewInit() { - let buttons = this.buttons.toArray(); + let buttons = this._buttons.toArray(); for (let button of buttons) { button.select.subscribe(() => { this.writeValue(button.value);