mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix: add event object for slide-toggle and checkbox. (#554)
* update(): add event object for slide-toggle and checkbox. Closes #552. * Fix Linters... again :P * update(): Address comments from @jelborun
This commit is contained in:
committed by
Jeremy Elbourn
parent
e1ff20220f
commit
55cc197cd4
@@ -11,7 +11,7 @@ import {FORM_DIRECTIVES, NgModel, NgControl} from '@angular/common';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {Component, DebugElement} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {MdCheckbox} from './checkbox';
|
||||
import {MdCheckbox, MdCheckboxChange} from './checkbox';
|
||||
import {PromiseCompleter} from '@angular2-material/core/async/promise-completer';
|
||||
|
||||
|
||||
@@ -273,26 +273,24 @@ describe('MdCheckbox', () => {
|
||||
testComponent = fixture.debugElement.componentInstance;
|
||||
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
||||
labelElement = <HTMLLabelElement>checkboxNativeElement.querySelector('label');
|
||||
|
||||
spyOn(testComponent, 'handleChange');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call the change event on first change after initialization', fakeAsync(() => {
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.handleChange).not.toHaveBeenCalled();
|
||||
expect(testComponent.lastEvent).toBeUndefined();
|
||||
|
||||
checkboxInstance.checked = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
tick();
|
||||
|
||||
expect(testComponent.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(testComponent.handleChange).toHaveBeenCalledWith(true);
|
||||
expect(testComponent.lastEvent.checked).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not emit a DOM event to the change output', async(() => {
|
||||
expect(testComponent.handleChange).not.toHaveBeenCalled();
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.lastEvent).toBeUndefined();
|
||||
|
||||
// Trigger the click on the inputElement, because the input will probably
|
||||
// emit a DOM event to the change output.
|
||||
@@ -303,8 +301,7 @@ describe('MdCheckbox', () => {
|
||||
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
|
||||
// emitted value can be a DOM Event, which is not valid.
|
||||
// See angular/angular#4059
|
||||
expect(testComponent.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(testComponent.handleChange).toHaveBeenCalledWith(true);
|
||||
expect(testComponent.lastEvent.checked).toBe(true);
|
||||
});
|
||||
|
||||
}));
|
||||
@@ -541,8 +538,8 @@ class CheckboxWithNameAttribute {}
|
||||
/** Simple test component with change event */
|
||||
@Component({
|
||||
directives: [MdCheckbox],
|
||||
template: `<md-checkbox (change)="handleChange($event)"></md-checkbox>`
|
||||
template: `<md-checkbox (change)="lastEvent = $event"></md-checkbox>`
|
||||
})
|
||||
class CheckboxWithChangeEvent {
|
||||
handleChange(value: boolean): void {}
|
||||
lastEvent: MdCheckboxChange;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ enum TransitionCheckState {
|
||||
Indeterminate
|
||||
}
|
||||
|
||||
// A simple change event emitted by the MdCheckbox component.
|
||||
export class MdCheckboxChange {
|
||||
source: MdCheckbox;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A material design checkbox component. Supports all of the functionality of an HTML5 checkbox,
|
||||
* and exposes a similar API. An MdCheckbox can be either checked, unchecked, indeterminate, or
|
||||
@@ -105,7 +111,7 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
|
||||
@Input() name: string = null;
|
||||
|
||||
/** Event emitted when the checkbox's `checked` value changes. */
|
||||
@Output() change: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
@Output() change: EventEmitter<MdCheckboxChange> = new EventEmitter<MdCheckboxChange>();
|
||||
|
||||
/** Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor. */
|
||||
onTouched: () => any = () => {};
|
||||
@@ -144,7 +150,7 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
|
||||
|
||||
// Only fire a change event if this isn't the first time the checked property is ever set.
|
||||
if (this._isInitialized) {
|
||||
this.change.emit(this._checked);
|
||||
this._emitChangeEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +231,14 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
private _emitChangeEvent() {
|
||||
let event = new MdCheckboxChange();
|
||||
event.source = this;
|
||||
event.checked = this.checked;
|
||||
|
||||
this.change.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the component when the input has focus so that we can style accordingly
|
||||
* @internal
|
||||
@@ -258,7 +272,7 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
|
||||
onInteractionEvent(event: Event) {
|
||||
// We always have to stop propagation on the change event.
|
||||
// Otherwise the change event, from the input element, will bubble up and
|
||||
// emit its event object to the `change` output.
|
||||
// emit its event object to the `change` output.
|
||||
event.stopPropagation();
|
||||
|
||||
if (!this.disabled) {
|
||||
|
||||
@@ -324,20 +324,19 @@ describe('MdRadio', () => {
|
||||
radioInstances = radioDebugElements.map(debugEl => debugEl.componentInstance);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(testComponent, 'onChange');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update the model before firing change event', fakeAsync(() => {
|
||||
expect(testComponent.modelValue).toBeUndefined();
|
||||
expect(testComponent.lastEvent).toBeUndefined();
|
||||
|
||||
groupInstance.value = 'chocolate';
|
||||
fixture.detectChanges();
|
||||
|
||||
tick();
|
||||
expect(testComponent.modelValue).toBe('chocolate');
|
||||
expect(testComponent.onChange).toHaveBeenCalledWith('chocolate');
|
||||
expect(testComponent.lastEvent.value).toBe('chocolate');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -432,7 +431,7 @@ class StandaloneRadioButtons { }
|
||||
@Component({
|
||||
directives: [MD_RADIO_DIRECTIVES, FORM_DIRECTIVES],
|
||||
template: `
|
||||
<md-radio-group [(ngModel)]="modelValue" (change)="onChange(modelValue)">
|
||||
<md-radio-group [(ngModel)]="modelValue" (change)="lastEvent = $event">
|
||||
<md-radio-button *ngFor="let option of options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</md-radio-button>
|
||||
@@ -446,7 +445,7 @@ class RadioGroupWithNgModel {
|
||||
{label: 'Chocolate', value: 'chocolate'},
|
||||
{label: 'Strawberry', value: 'strawberry'},
|
||||
];
|
||||
onChange(value: MdRadioChange) {}
|
||||
lastEvent: MdRadioChange;
|
||||
}
|
||||
|
||||
// TODO(jelbourn): remove eveything below when Angular supports faking events.
|
||||
|
||||
@@ -4,13 +4,12 @@ import {
|
||||
expect,
|
||||
beforeEach,
|
||||
inject,
|
||||
async,
|
||||
fakeAsync,
|
||||
async
|
||||
} from '@angular/core/testing';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Component} from '@angular/core';
|
||||
import {MdSlideToggle} from './slide-toggle';
|
||||
import {MdSlideToggle, MdSlideToggleChange} from './slide-toggle';
|
||||
import {NgControl} from '@angular/common';
|
||||
|
||||
describe('MdSlideToggle', () => {
|
||||
@@ -162,21 +161,21 @@ describe('MdSlideToggle', () => {
|
||||
expect(slideToggleElement.classList).not.toContain('ng-dirty');
|
||||
});
|
||||
|
||||
it('should emit the new values properly', fakeAsync(() => {
|
||||
spyOn(testComponent, 'onValueChange');
|
||||
|
||||
it('should emit the new values properly', async(() => {
|
||||
labelElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(testComponent.onValueChange).toHaveBeenCalledTimes(1);
|
||||
expect(testComponent.onValueChange).toHaveBeenCalledWith(true);
|
||||
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
|
||||
// emitted value can be a DOM Event, which is not valid.
|
||||
// See angular/angular#4059
|
||||
expect(testComponent.lastEvent.checked).toBe(true);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support subscription on the change observable', () => {
|
||||
slideToggle.change.subscribe(value => {
|
||||
expect(value).toBe(true);
|
||||
slideToggle.change.subscribe((event: MdSlideToggleChange) => {
|
||||
expect(event.checked).toBe(true);
|
||||
});
|
||||
|
||||
slideToggle.toggle();
|
||||
@@ -269,7 +268,7 @@ function dispatchFocusChangeEvent(eventName: string, element: HTMLElement): void
|
||||
<md-slide-toggle [(ngModel)]="slideModel" [disabled]="isDisabled" [color]="slideColor"
|
||||
[id]="slideId" [checked]="slideChecked" [name]="slideName"
|
||||
[aria-label]="slideLabel" [ariaLabel]="slideLabel"
|
||||
[ariaLabelledby]="slideLabelledBy" (change)="onValueChange($event)">
|
||||
[ariaLabelledby]="slideLabelledBy" (change)="lastEvent = $event">
|
||||
<span>Test Slide Toggle</span>
|
||||
</md-slide-toggle>
|
||||
`,
|
||||
@@ -284,6 +283,5 @@ class SlideToggleTestApp {
|
||||
slideName: string;
|
||||
slideLabel: string;
|
||||
slideLabelledBy: string;
|
||||
|
||||
onValueChange(value: boolean): void {};
|
||||
lastEvent: MdSlideToggleChange;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ export const MD_SLIDE_TOGGLE_VALUE_ACCESSOR: any = {
|
||||
multi: true
|
||||
};
|
||||
|
||||
// A simple change event emitted by the MdSlideToggle component.
|
||||
export class MdSlideToggleChange {
|
||||
source: MdSlideToggle;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
// Increasing integer for generating unique ids for slide-toggle components.
|
||||
let nextId = 0;
|
||||
|
||||
@@ -59,8 +65,8 @@ export class MdSlideToggle implements ControlValueAccessor {
|
||||
@Input() ariaLabel: string = null;
|
||||
@Input() ariaLabelledby: string = null;
|
||||
|
||||
@Output('change') private _change: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
change: Observable<boolean> = this._change.asObservable();
|
||||
private _change: EventEmitter<MdSlideToggleChange> = new EventEmitter<MdSlideToggleChange>();
|
||||
@Output() change: Observable<MdSlideToggleChange> = this._change.asObservable();
|
||||
|
||||
// Returns the unique id for the visual hidden input.
|
||||
getInputId = () => `${this.id || this._uniqueId}-input`;
|
||||
@@ -144,7 +150,7 @@ export class MdSlideToggle implements ControlValueAccessor {
|
||||
if (this.checked !== !!value) {
|
||||
this._checked = value;
|
||||
this.onChange(this._checked);
|
||||
this._change.emit(this._checked);
|
||||
this._emitChangeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +179,13 @@ export class MdSlideToggle implements ControlValueAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
private _emitChangeEvent() {
|
||||
let event = new MdSlideToggleChange();
|
||||
event.source = this;
|
||||
event.checked = this.checked;
|
||||
this._change.emit(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const MD_SLIDE_TOGGLE_DIRECTIVES = [MdSlideToggle];
|
||||
|
||||
Reference in New Issue
Block a user