Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update(): add event object for slide-toggle and checkbox. #554

Merged
merged 3 commits into from
May 31, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions src/components/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';


Expand Down Expand Up @@ -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.value).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.
Expand All @@ -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.value).toBe(true);
});

}));
Expand Down Expand Up @@ -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;
}
20 changes: 17 additions & 3 deletions src/components/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ enum TransitionCheckState {
Indeterminate
}

// A simple change event emitted by the MdCheckbox component.
export class MdCheckboxChange {
source: MdCheckbox;
value: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for checkbox and slide-toggle we'd want the property to be checked instead of value

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/**
* 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
Expand Down Expand Up @@ -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 = () => {};
Expand Down Expand Up @@ -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();
}
}
}
Expand Down Expand Up @@ -225,6 +231,14 @@ export class MdCheckbox implements AfterContentInit, ControlValueAccessor {
}
}

private _emitChangeEvent() {
let event = new MdCheckboxChange();
event.source = this;
event.value = this.checked;

this.change.emit(event);
}

/**
* Informs the component when the input has focus so that we can style accordingly
* @internal
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 4 additions & 5 deletions src/components/radio/radio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}));
});

Expand Down Expand Up @@ -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>
Expand All @@ -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.
Expand Down
24 changes: 11 additions & 13 deletions src/components/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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.value).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.value).toBe(true);
});

slideToggle.toggle();
Expand Down Expand Up @@ -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>
`,
Expand All @@ -284,6 +283,5 @@ class SlideToggleTestApp {
slideName: string;
slideLabel: string;
slideLabelledBy: string;

onValueChange(value: boolean): void {};
lastEvent: MdSlideToggleChange;
}
21 changes: 18 additions & 3 deletions src/components/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
value: boolean;
}

// Increasing integer for generating unique ids for slide-toggle components.
let nextId = 0;

Expand Down Expand Up @@ -59,8 +65,10 @@ 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();
@Output('change')
private _change: EventEmitter<MdSlideToggleChange> = new EventEmitter<MdSlideToggleChange>();

change: Observable<MdSlideToggleChange> = this._change.asObservable();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @Output() can go on the change Observable rather than the EventEmitter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be a big deal - Updated.


// Returns the unique id for the visual hidden input.
getInputId = () => `${this.id || this._uniqueId}-input`;
Expand Down Expand Up @@ -144,7 +152,7 @@ export class MdSlideToggle implements ControlValueAccessor {
if (this.checked !== !!value) {
this._checked = value;
this.onChange(this._checked);
this._change.emit(this._checked);
this._emitChangeEvent();
}
}

Expand Down Expand Up @@ -173,6 +181,13 @@ export class MdSlideToggle implements ControlValueAccessor {
}
}

private _emitChangeEvent() {
let event = new MdSlideToggleChange();
event.source = this;
event.value = this.checked;
this._change.emit(event);
}

}

export const MD_SLIDE_TOGGLE_DIRECTIVES = [MdSlideToggle];