feat(button-toggle): add initial button-toggle (#517)

* md-toggle initial prototype

* Use directives, no sass nesting, and private event emitter

* Move toggle and radio dispatcher into shared unique selection dispatcher

* Rename toggles to button-toggles

* Update toggle to look more like radio after updates

* Update child name from parent name

* TSLint fixes

* Misc style fixes for PR.

* Update comment
* Remove unused `_buttonToggles`
* Use string literals for button toggle type
* Move instantiation into the constructor
* Add `isSingleSelection` getter

* Move click event binding to component

* Add missing tests to button toggles

* Exclusive toggle should deselect all when group value is cleared
* Button toggles should be checked when the checkbox is interacted with

* Remove onClick()

* Remove click event

* Remove unnecessary getters and setters.

* Add icons to demo app

* Add missing semicolon

* Change eventemitter to private in button toggle class

* Get native elements in tests from debug elements

* Move internal to end of comment and use BooleanFieldValue

* Fix typo in test description.

* vertical align middle all children in button toggle
This commit is contained in:
Ivey Padgett
2016-06-03 13:19:03 -07:00
committed by Jeremy Elbourn
parent e366fa0fbc
commit 013eabd672
13 changed files with 827 additions and 25 deletions
@@ -0,0 +1,13 @@
<label [attr.for]="inputId" class="md-button-toggle-label">
<input #input class="md-button-toggle-input"
[type]="type"
[id]="inputId"
[checked]="checked"
[disabled]="disabled"
[name]="name"
(change)="onInputChange($event)">
<div class="md-button-toggle-label-content">
<ng-content></ng-content>
</div>
</label>
@@ -0,0 +1,46 @@
@import "elevation";
@import "default-theme";
@import "palette";
@import "mixins";
$md-button-toggle-padding: 0 16px !default;
$md-button-toggle-line-height: 36px !default;
$md-button-toggle-border-radius: 3px !default;
md-button-toggle-group {
@include md-elevation(2);
position: relative;
display: inline-flex;
border-radius: $md-button-toggle-border-radius;
cursor: pointer;
white-space: nowrap;
}
.md-button-toggle-checked .md-button-toggle-label-content {
background-color: md-color($md-grey, 300);
}
.md-button-toggle-disabled .md-button-toggle-label-content {
background-color: md-color($md-foreground, disabled);
cursor: not-allowed;
}
md-button-toggle {
white-space: nowrap;
}
.md-button-toggle-input {
@include md-visually-hidden;
}
.md-button-toggle-label-content {
display: inline-block;
line-height: $md-button-toggle-line-height;
padding: $md-button-toggle-padding;
cursor: pointer;
}
.md-button-toggle-label-content > * {
vertical-align: middle;
}
@@ -0,0 +1,341 @@
import {
it,
describe,
beforeEach,
beforeEachProviders,
inject,
async,
fakeAsync,
tick,
} from '@angular/core/testing';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {Component, DebugElement, provide} from '@angular/core';
import {By} from '@angular/platform-browser';
import {
MD_BUTTON_TOGGLE_DIRECTIVES,
MdButtonToggleGroup,
MdButtonToggle,
MdButtonToggleGroupMultiple
} from './button-toggle';
import {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
describe('MdButtonToggle', () => {
let builder: TestComponentBuilder;
let dispatcher: MdUniqueSelectionDispatcher;
beforeEachProviders(() => [
provide(MdUniqueSelectionDispatcher, {useFactory: () => {
dispatcher = new MdUniqueSelectionDispatcher();
return dispatcher;
}})
]);
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));
describe('inside of an exclusive selection group', () => {
let fixture: ComponentFixture<ButtonTogglesInsideButtonToggleGroup>;
let groupDebugElement: DebugElement;
let groupNativeElement: HTMLElement;
let buttonToggleDebugElements: DebugElement[];
let buttonToggleNativeElements: HTMLElement[];
let groupInstance: MdButtonToggleGroup;
let buttonToggleInstances: MdButtonToggle[];
let testComponent: ButtonTogglesInsideButtonToggleGroup;
beforeEach(async(() => {
builder.createAsync(ButtonTogglesInsideButtonToggleGroup).then(f => {
fixture = f;
fixture.detectChanges();
testComponent = fixture.debugElement.componentInstance;
groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroup));
groupNativeElement = groupDebugElement.nativeElement;
groupInstance = groupDebugElement.injector.get(MdButtonToggleGroup);
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle));
buttonToggleNativeElements =
buttonToggleDebugElements.map(debugEl => debugEl.nativeElement);
buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance);
});
}));
it('should set individual button toggle names based on the group name', () => {
expect(groupInstance.name).toBeTruthy();
for (let buttonToggle of buttonToggleInstances) {
expect(buttonToggle.name).toBe(groupInstance.name);
}
});
it('should disable click interactions when the group is disabled', () => {
testComponent.isGroupDisabled = true;
fixture.detectChanges();
buttonToggleNativeElements[0].click();
expect(buttonToggleInstances[0].checked).toBe(false);
});
it('should update the group value when one of the toggles changes', () => {
expect(groupInstance.value).toBeFalsy();
let nativeCheckboxLabel = buttonToggleDebugElements[0].query(By.css('label')).nativeElement;
nativeCheckboxLabel.click();
fixture.detectChanges();
expect(groupInstance.value).toBe('test1');
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
});
it('should update the group and toggles when one of the button toggles is clicked', () => {
expect(groupInstance.value).toBeFalsy();
let nativeCheckboxLabel = buttonToggleDebugElements[0].query(By.css('label')).nativeElement;
nativeCheckboxLabel.click();
fixture.detectChanges();
expect(groupInstance.value).toBe('test1');
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
expect(buttonToggleInstances[0].checked).toBe(true);
expect(buttonToggleInstances[1].checked).toBe(false);
let nativeCheckboxLabel2 = buttonToggleDebugElements[1].query(By.css('label')).nativeElement;
nativeCheckboxLabel2.click();
fixture.detectChanges();
expect(groupInstance.value).toBe('test2');
expect(groupInstance.selected).toBe(buttonToggleInstances[1]);
expect(buttonToggleInstances[0].checked).toBe(false);
expect(buttonToggleInstances[1].checked).toBe(true);
});
it('should check a button toggle upon interaction with underlying native radio button', () => {
let nativeRadioInput = buttonToggleDebugElements[0].query(By.css('input')).nativeElement;
nativeRadioInput.click();
fixture.detectChanges();
expect(buttonToggleInstances[0].checked).toBe(true);
expect(groupInstance.value);
});
it('should emit a change event from button toggles', fakeAsync(() => {
expect(buttonToggleInstances[0].checked).toBe(false);
let changeSpy = jasmine.createSpy('button-toggle change listener');
buttonToggleInstances[0].change.subscribe(changeSpy);
buttonToggleInstances[0].checked = true;
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalled();
buttonToggleInstances[0].checked = false;
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalledTimes(2);
}));
it('should emit a change event from the button toggle group', fakeAsync(() => {
expect(groupInstance.value).toBeFalsy();
let changeSpy = jasmine.createSpy('button-toggle-group change listener');
groupInstance.change.subscribe(changeSpy);
groupInstance.value = 'test1';
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalled();
groupInstance.value = 'test2';
fixture.detectChanges();
tick();
expect(changeSpy).toHaveBeenCalledTimes(2);
}));
it('should update the group and button toggles when updating the group value', () => {
expect(groupInstance.value).toBeFalsy();
testComponent.groupValue = 'test1';
fixture.detectChanges();
expect(groupInstance.value).toBe('test1');
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
expect(buttonToggleInstances[0].checked).toBe(true);
expect(buttonToggleInstances[1].checked).toBe(false);
testComponent.groupValue = 'test2';
fixture.detectChanges();
expect(groupInstance.value).toBe('test2');
expect(groupInstance.selected).toBe(buttonToggleInstances[1]);
expect(buttonToggleInstances[0].checked).toBe(false);
expect(buttonToggleInstances[1].checked).toBe(true);
});
it('should deselect all of the checkboxes when the group value is cleared', () => {
buttonToggleInstances[0].checked = true;
expect(groupInstance.value).toBeTruthy();
groupInstance.value = null;
expect(buttonToggleInstances.every(toggle => !toggle.checked)).toBe(true);
});
});
describe('inside of a multiple selection group', () => {
let fixture: ComponentFixture<ButtonTogglesInsideButtonToggleGroupMultiple>;
let groupDebugElement: DebugElement;
let groupNativeElement: HTMLElement;
let buttonToggleDebugElements: DebugElement[];
let buttonToggleNativeElements: HTMLElement[];
let groupInstance: MdButtonToggleGroupMultiple;
let buttonToggleInstances: MdButtonToggle[];
let testComponent: ButtonTogglesInsideButtonToggleGroupMultiple;
beforeEach(async(() => {
builder.createAsync(ButtonTogglesInsideButtonToggleGroupMultiple).then(f => {
fixture = f;
fixture.detectChanges();
testComponent = fixture.debugElement.componentInstance;
groupDebugElement = fixture.debugElement.query(By.directive(MdButtonToggleGroupMultiple));
groupNativeElement = groupDebugElement.nativeElement;
groupInstance = groupDebugElement.injector.get(MdButtonToggleGroupMultiple);
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MdButtonToggle));
buttonToggleNativeElements =
buttonToggleDebugElements.map(debugEl => debugEl.nativeElement);
buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance);
});
}));
it('should disable click interactions when the group is disabled', () => {
testComponent.isGroupDisabled = true;
fixture.detectChanges();
buttonToggleNativeElements[0].click();
expect(buttonToggleInstances[0].checked).toBe(false);
});
it('should check a button toggle when clicked', () => {
expect(buttonToggleInstances.every(buttonToggle => !buttonToggle.checked)).toBe(true);
let nativeCheckboxLabel = buttonToggleDebugElements[0].query(By.css('label')).nativeElement;
nativeCheckboxLabel.click();
expect(buttonToggleInstances[0].checked).toBe(true);
});
it('should allow for multiple toggles to be selected', () => {
buttonToggleInstances[0].checked = true;
fixture.detectChanges();
expect(buttonToggleInstances[0].checked).toBe(true);
buttonToggleInstances[1].checked = true;
fixture.detectChanges();
expect(buttonToggleInstances[1].checked).toBe(true);
expect(buttonToggleInstances[0].checked).toBe(true);
});
it('should check a button toggle upon interaction with underlying native checkbox', () => {
let nativeCheckboxInput = buttonToggleDebugElements[0].query(By.css('input')).nativeElement;
nativeCheckboxInput.click();
fixture.detectChanges();
expect(buttonToggleInstances[0].checked).toBe(true);
});
it('should deselect a button toggle when selected twice', () => {
buttonToggleNativeElements[0].click();
fixture.detectChanges();
buttonToggleNativeElements[0].click();
fixture.detectChanges();
expect(buttonToggleInstances[0].checked).toBe(false);
});
});
describe('as standalone', () => {
let fixture: ComponentFixture<StandaloneButtonToggle>;
let buttonToggleDebugElement: DebugElement;
let buttonToggleNativeElement: HTMLElement;
let buttonToggleInstance: MdButtonToggle;
let testComponent: StandaloneButtonToggle;
beforeEach(async(() => {
builder.createAsync(StandaloneButtonToggle).then(f => {
fixture = f;
fixture.detectChanges();
testComponent = fixture.debugElement.componentInstance;
buttonToggleDebugElement = fixture.debugElement.query(By.directive(MdButtonToggle));
buttonToggleNativeElement = buttonToggleDebugElement.nativeElement;
buttonToggleInstance = buttonToggleDebugElement.componentInstance;
});
}));
it('should toggle when clicked', () => {
let nativeCheckboxLabel = buttonToggleDebugElement.query(By.css('label')).nativeElement;
nativeCheckboxLabel.click();
fixture.detectChanges();
expect(buttonToggleInstance.checked).toBe(true);
nativeCheckboxLabel.click();
fixture.detectChanges();
expect(buttonToggleInstance.checked).toBe(false);
});
});
});
@Component({
directives: [MD_BUTTON_TOGGLE_DIRECTIVES],
template: `
<md-button-toggle-group [disabled]="isGroupDisabled" [value]="groupValue">
<md-button-toggle value="test1">Test1</md-button-toggle>
<md-button-toggle value="test2">Test2</md-button-toggle>
<md-button-toggle value="test3">Test3</md-button-toggle>
</md-button-toggle-group>
`
})
class ButtonTogglesInsideButtonToggleGroup {
isGroupDisabled: boolean = false;
groupValue: string = null;
}
@Component({
directives: [MD_BUTTON_TOGGLE_DIRECTIVES],
template: `
<md-button-toggle-group [disabled]="isGroupDisabled" multiple>
<md-button-toggle value="eggs">Eggs</md-button-toggle>
<md-button-toggle value="flour">Flour</md-button-toggle>
<md-button-toggle value="sugar">Sugar</md-button-toggle>
</md-button-toggle-group>
`
})
class ButtonTogglesInsideButtonToggleGroupMultiple {
isGroupDisabled: boolean = false;
}
@Component({
directives: [MD_BUTTON_TOGGLE_DIRECTIVES],
template: `
<md-button-toggle>Yes</md-button-toggle>
`
})
class StandaloneButtonToggle { }
@@ -0,0 +1,335 @@
import {
Component,
ContentChildren,
Directive,
EventEmitter,
HostBinding,
Input,
OnInit,
Optional,
Output,
QueryList,
ViewEncapsulation,
forwardRef
} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
import {BooleanFieldValue} from '@angular2-material/core/annotations/field-value';
export type ToggleType = 'checkbox' | 'radio';
var _uniqueIdCounter = 0;
/** A simple change event emitted by either MdButtonToggle or MdButtonToggleGroup. */
export class MdButtonToggleChange {
source: MdButtonToggle;
value: any;
}
/** Exclusive selection button toggle group that behaves like a radio-button group. */
@Directive({
selector: 'md-button-toggle-group:not([multiple])',
host: {
'role': 'radiogroup',
},
})
export class MdButtonToggleGroup {
/** The value for the button toggle group. Should match currently selected button toggle. */
private _value: any = null;
/** The HTML name attribute applied to toggles in this group. */
private _name: string = `md-radio-group-${_uniqueIdCounter++}`;
/** Disables all toggles in the group. */
private _disabled: boolean = null;
/** The currently selected button toggle, should match the value. */
private _selected: MdButtonToggle = null;
/** Event emitted when the group's value changes. */
private _change: EventEmitter<MdButtonToggleChange> = new EventEmitter<MdButtonToggleChange>();
@Output() get change(): Observable<MdButtonToggleChange> {
return this._change.asObservable();
}
/** Child button toggle buttons. */
@ContentChildren(forwardRef(() => MdButtonToggle))
private _buttonToggles: QueryList<MdButtonToggle> = null;
@Input()
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
this._updateButtonToggleNames();
}
@Input()
@BooleanFieldValue()
get disabled(): boolean {
return this._disabled;
}
set disabled(value) {
this._disabled = (value != null && value !== false) ? true : null;
}
@Input()
get value(): any {
return this._value;
}
set value(newValue: any) {
if (this._value != newValue) {
this._value = newValue;
this._updateSelectedButtonToggleFromValue();
this._emitChangeEvent();
}
}
@Input()
get selected() {
return this._selected;
}
set selected(selected: MdButtonToggle) {
this._selected = selected;
this.value = selected ? selected.value : null;
if (selected && !selected.checked) {
selected.checked = true;
}
}
private _updateButtonToggleNames(): void {
(this._buttonToggles || []).forEach((toggle) => {
toggle.name = this._name;
});
}
// TODO: Refactor into shared code with radio.
private _updateSelectedButtonToggleFromValue(): void {
let isAlreadySelected = this._selected != null && this._selected.value == this._value;
if (this._buttonToggles != null && !isAlreadySelected) {
let matchingButtonToggle = this._buttonToggles.filter(
buttonToggle => buttonToggle.value == this._value)[0];
if (matchingButtonToggle) {
this.selected = matchingButtonToggle;
} else if (this.value == null) {
this.selected = null;
this._buttonToggles.forEach(buttonToggle => {buttonToggle.checked = false; });
}
}
}
/** Dispatch change event with current selection and group value. */
private _emitChangeEvent(): void {
let event = new MdButtonToggleChange();
event.source = this._selected;
event.value = this._value;
this._change.emit(event);
}
}
/** Multiple selection button-toggle group. */
@Directive({
selector: 'md-button-toggle-group[multiple]',
})
export class MdButtonToggleGroupMultiple {
/** Disables all toggles in the group. */
private _disabled: boolean = null;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value) {
this._disabled = (value != null && value !== false) ? true : null;
}
}
@Component({
moduleId: module.id,
selector: 'md-button-toggle',
templateUrl: 'button-toggle.html',
styleUrls: ['button-toggle.css'],
encapsulation: ViewEncapsulation.None,
})
export class MdButtonToggle implements OnInit {
/** Whether or not this button toggle is checked. */
private _checked: boolean = false;
/**
* Type of the button toggle. Either 'radio' or 'checkbox'.
* @internal
*/
type: ToggleType;
/** The unique ID for this button toggle. */
@HostBinding()
@Input()
id: string;
/** HTML's 'name' attribute used to group radios for unique selection. */
@Input()
name: string;
/** Whether or not this button toggle is disabled. */
private _disabled: boolean = null;
/** Value assigned to this button toggle. */
private _value: any = null;
/** Whether or not the button toggle is a single selection. */
private _isSingleSelector: boolean = null;
/** The parent button toggle group (exclusive selection). Optional. */
buttonToggleGroup: MdButtonToggleGroup;
/** The parent button toggle group (multiple selection). Optional. */
buttonToggleGroupMultiple: MdButtonToggleGroupMultiple;
/** Event emitted when the group value changes. */
private _change: EventEmitter<MdButtonToggleChange> = new EventEmitter<MdButtonToggleChange>();
@Output() get change(): Observable<MdButtonToggleChange> {
return this._change.asObservable();
}
constructor(@Optional() toggleGroup: MdButtonToggleGroup,
@Optional() toggleGroupMultiple: MdButtonToggleGroupMultiple,
public buttonToggleDispatcher: MdUniqueSelectionDispatcher) {
this.buttonToggleGroup = toggleGroup;
this.buttonToggleGroupMultiple = toggleGroupMultiple;
if (this.buttonToggleGroup) {
buttonToggleDispatcher.listen((id: string, name: string) => {
if (id != this.id && name == this.name) {
this.checked = false;
}
});
this.type = 'radio';
this.name = this.buttonToggleGroup.name;
this._isSingleSelector = true;
} else {
// Even if there is no group at all, treat the button toggle as a checkbox so it can be
// toggled on or off.
this.type = 'checkbox';
this._isSingleSelector = false;
}
}
/** @internal */
ngOnInit() {
if (this.id == null) {
this.id = `md-button-toggle-${_uniqueIdCounter++}`;
}
if (this.buttonToggleGroup && this._value == this.buttonToggleGroup.value) {
this._checked = true;
}
}
get inputId(): string {
return `${this.id}-input`;
}
@HostBinding('class.md-button-toggle-checked')
@Input()
get checked(): boolean {
return this._checked;
}
set checked(newCheckedState: boolean) {
if (this._isSingleSelector) {
if (newCheckedState) {
// Notify all button toggles with the same name (in the same group) to un-check.
this.buttonToggleDispatcher.notify(this.id, this.name);
}
if (newCheckedState != this._checked) {
this._emitChangeEvent();
}
}
this._checked = newCheckedState;
if (newCheckedState && this._isSingleSelector && this.buttonToggleGroup.value != this.value) {
this.buttonToggleGroup.selected = this;
}
}
/** MdButtonToggleGroup reads this to assign its own value. */
@Input()
get value(): any {
return this._value;
}
set value(value: any) {
if (this._value != value) {
if (this.buttonToggleGroup != null && this.checked) {
this.buttonToggleGroup.value = value;
}
this._value = value;
}
}
/** Dispatch change event with current value. */
private _emitChangeEvent(): void {
let event = new MdButtonToggleChange();
event.source = this;
event.value = this._value;
this._change.emit(event);
}
@HostBinding('class.md-button-toggle-disabled')
@Input()
get disabled(): boolean {
return this._disabled || (this.buttonToggleGroup != null && this.buttonToggleGroup.disabled) ||
(this.buttonToggleGroupMultiple != null && this.buttonToggleGroupMultiple.disabled);
}
set disabled(value: boolean) {
this._disabled = (value != null && value !== false) ? true : null;
}
/** Toggle the state of the current button toggle. */
private _toggle(): void {
this.checked = !this.checked;
}
/**
* Checks the button toggle due to an interaction with the underlying native input.
* @internal
*/
onInputChange(event: Event) {
event.stopPropagation();
if (this._isSingleSelector) {
// Propagate the change one-way via the group, which will in turn mark this
// button toggle as checked.
this.checked = true;
this.buttonToggleGroup.selected = this;
} else {
this._toggle();
}
}
}
export const MD_BUTTON_TOGGLE_DIRECTIVES = [
MdButtonToggleGroup,
MdButtonToggleGroupMultiple,
MdButtonToggle
];
+14 -12
View File
@@ -1,28 +1,30 @@
import {
it,
describe,
beforeEach,
beforeEachProviders,
inject,
async,
fakeAsync,
tick
it,
describe,
beforeEach,
beforeEachProviders,
inject,
async,
fakeAsync,
tick
} from '@angular/core/testing';
import {FORM_DIRECTIVES, NgControl} from '@angular/common';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {Component, DebugElement, provide} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MD_RADIO_DIRECTIVES, MdRadioGroup, MdRadioButton, MdRadioChange} from './radio';
import {MdRadioDispatcher} from './radio_dispatcher';
import {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
describe('MdRadio', () => {
let builder: TestComponentBuilder;
let dispatcher: MdRadioDispatcher;
let dispatcher: MdUniqueSelectionDispatcher;
beforeEachProviders(() => [
provide(MdRadioDispatcher, {useFactory: () => {
dispatcher = new MdRadioDispatcher();
provide(MdUniqueSelectionDispatcher, {useFactory: () => {
dispatcher = new MdUniqueSelectionDispatcher();
return dispatcher;
}})
]);
+8 -3
View File
@@ -18,11 +18,15 @@ import {
NG_VALUE_ACCESSOR,
ControlValueAccessor
} from '@angular/common';
import {MdRadioDispatcher} from './radio_dispatcher';
import {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
// Re-exports.
export {MdRadioDispatcher} from './radio_dispatcher';
export {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
@@ -273,7 +277,8 @@ export class MdRadioButton implements OnInit {
@Output()
change: EventEmitter<MdRadioChange> = new EventEmitter<MdRadioChange>();
constructor(@Optional() radioGroup: MdRadioGroup, public radioDispatcher: MdRadioDispatcher) {
constructor(@Optional() radioGroup: MdRadioGroup,
public radioDispatcher: MdUniqueSelectionDispatcher) {
// Assertions. Ideally these should be stripped out by the compiler.
// TODO(jelbourn): Assert that there's no name binding AND a parent radio group.
@@ -2,10 +2,10 @@ import {Injectable} from '@angular/core';
// Users of the Dispatcher never need to see this type, but TypeScript requires it to be exported.
export type MdRadioDispatcherListener = (id: string, name: string) => void;
export type MdUniqueSelectionDispatcherListener = (id: string, name: string) => void;
/**
* Class for radio buttons to coordinate unique selection based on name.
* Class to coordinate unique selection based on name.
* Intended to be consumed as an Angular service.
* This service is needed because native radio change events are only fired on the item currently
* being selected, and we still need to uncheck the previous selection.
@@ -14,18 +14,18 @@ export type MdRadioDispatcherListener = (id: string, name: string) => void;
* less error-prone if they are simply passed through when the events occur.
*/
@Injectable()
export class MdRadioDispatcher {
private _listeners: MdRadioDispatcherListener[] = [];
export class MdUniqueSelectionDispatcher {
private _listeners: MdUniqueSelectionDispatcherListener[] = [];
/** Notify other radio buttons that selection for the given name has been set. */
/** Notify other items that selection for the given name has been set. */
notify(id: string, name: string) {
for (let listener of this._listeners) {
listener(id, name);
}
}
/** Listen for future changes to radio button selection. */
listen(listener: MdRadioDispatcherListener) {
/** Listen for future changes to item selection. */
listen(listener: MdUniqueSelectionDispatcherListener) {
this._listeners.push(listener);
}
}
@@ -0,0 +1,39 @@
<h1>Exclusive Selection</h1>
<section class="demo-section">
<md-button-toggle-group name="alignment">
<md-button-toggle value="left"><md-icon>format_align_left</md-icon></md-button-toggle>
<md-button-toggle value="center"><md-icon>format_align_center</md-icon></md-button-toggle>
<md-button-toggle value="right"><md-icon>format_align_right</md-icon></md-button-toggle>
<md-button-toggle value="justify" disabled><md-icon>format_align_justify</md-icon></md-button-toggle>
</md-button-toggle-group>
</section>
<h1>Disabled Group</h1>
<section class="demo-section">
<md-button-toggle-group name="checkbox" disabled>
<md-button-toggle value="bold">
<md-icon class="md-24">format_bold</md-icon>
</md-button-toggle>
<md-button-toggle value="italic">
<md-icon class="md-24">format_italic</md-icon>
</md-button-toggle>
<md-button-toggle value="underline">
<md-icon class="md-24">format_underline</md-icon>
</md-button-toggle>
</md-button-toggle-group>
</section>
<h1>Multiple Selection</h1>
<section class="demo-section">
<md-button-toggle-group multiple>
<md-button-toggle>Flour</md-button-toggle>
<md-button-toggle>Eggs</md-button-toggle>
<md-button-toggle>Sugar</md-button-toggle>
<md-button-toggle disabled>Milk (disabled)</md-button-toggle>
</md-button-toggle-group>
</section>
<h1>Single Toggle</h1>
<md-button-toggle>Yes</md-button-toggle>
@@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {MD_BUTTON_TOGGLE_DIRECTIVES} from '@angular2-material/button-toggle/button-toggle';
import {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
import {MdIcon} from '@angular2-material/icon/icon';
@Component({
moduleId: module.id,
selector: 'button-toggle-demo',
templateUrl: 'button-toggle-demo.html',
providers: [MdUniqueSelectionDispatcher],
directives: [MD_BUTTON_TOGGLE_DIRECTIVES, MdIcon]
})
export class ButtonToggleDemo { }
+1
View File
@@ -2,6 +2,7 @@
<md-sidenav #start>
<md-nav-list>
<a md-list-item [routerLink]="['button']">Button</a>
<a md-list-item [routerLink]="['button-toggle']">Button Toggle</a>
<a md-list-item [routerLink]="['card']">Card</a>
<a md-list-item [routerLink]="['checkbox']">Checkbox</a>
<a md-list-item [routerLink]="['gestures']">Gestures</a>
+3 -1
View File
@@ -26,6 +26,7 @@ import {GesturesDemo} from '../gestures/gestures-demo';
import {GridListDemo} from '../grid-list/grid-list-demo';
import {TabsDemo} from '../tabs/tab-group-demo';
import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo';
import {ButtonToggleDemo} from '../button-toggle/button-toggle-demo';
@Component({
selector: 'home',
@@ -72,6 +73,7 @@ export class Home {}
new Route({path: '/live-announcer', component: LiveAnnouncerDemo}),
new Route({path: '/gestures', component: GesturesDemo}),
new Route({path: '/grid-list', component: GridListDemo}),
new Route({path: '/tabs', component: TabsDemo})
new Route({path: '/tabs', component: TabsDemo}),
new Route({path: '/button-toggle', component: ButtonToggleDemo}),
])
export class DemoApp { }
+4 -2
View File
@@ -1,13 +1,15 @@
import {Component} from '@angular/core';
import {MdRadioButton, MdRadioGroup} from '@angular2-material/radio/radio';
import {MdRadioDispatcher} from '@angular2-material/radio/radio_dispatcher';
import {
MdUniqueSelectionDispatcher
} from '@angular2-material/core/coordination/unique-selection-dispatcher';
@Component({
moduleId: module.id,
selector: 'radio-demo',
templateUrl: 'radio-demo.html',
styleUrls: ['radio-demo.css'],
providers: [MdRadioDispatcher],
providers: [MdUniqueSelectionDispatcher],
directives: [MdRadioButton, MdRadioGroup]
})
export class RadioDemo {
+1
View File
@@ -18,6 +18,7 @@ const components = [
'radio',
'sidenav',
'slide-toggle',
'button-toggle',
'tabs',
'toolbar'
];