Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
45 changes: 14 additions & 31 deletions src/core/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {

import { DxTemplateDirective } from './template';
import { DxTemplateHost } from './template-host';
import { EmitterHelper } from './events-strategy';
import { WatcherHelper } from './watcher-helper';
import {
INestedOptionContainer,
Expand All @@ -14,11 +15,10 @@ import {
CollectionNestedOptionContainerImpl
} from './nested-option';

const startupEvents = ['onInitialized', 'onContentReady', 'onToolbarPreparing'];

export abstract class DxComponent implements INestedOptionContainer, ICollectionNestedOptionContainer {
private _initialOptions: any;
private _collectionContainerImpl: ICollectionNestedOptionContainer;
eventHelper: EmitterHelper;
templates: DxTemplateDirective[];
instance: any;

Expand All @@ -34,34 +34,18 @@ export abstract class DxComponent implements INestedOptionContainer, ICollection
}
}
private _initOptions() {
startupEvents.forEach(eventName => {
this._initialOptions[eventName] = (e) => {
let emitter = this[eventName];
return emitter && emitter.emit(e);
};
});

this._initialOptions.eventsStrategy = this.eventHelper.strategy;
this._initialOptions.integrationOptions.watchMethod = this.watcherHelper.getWatchMethod();
}
protected _createEventEmitters(events) {
events.forEach(event => {
this.eventHelper.createEmitter(event.emit, event.subscribe);
});
}
private _initEvents() {
this._events.forEach(event => {
if (event.subscribe) {
this.instance.on(event.subscribe, e => {
if (event.subscribe === 'optionChanged') {
let changeEventName = e.name + 'Change';
if (this[changeEventName]) {
this[changeEventName].emit(e.value);
}
this[event.emit].emit(e);
} else {
if (this[event.emit]) {
this.ngZone.run(() => {
this[event.emit].emit(e);
});
}
}
});
}
this.instance.on('optionChanged', e => {
let changeEventName = e.name + 'Change';
this.eventHelper.fireNgEvent(changeEventName, [e.value]);
});
}
protected _getOption(name: string) {
Expand All @@ -80,7 +64,6 @@ export abstract class DxComponent implements INestedOptionContainer, ICollection
}
protected abstract _createInstance(element, options)
protected _createWidget(element: any) {
this._initialOptions.integrationOptions = {};
this._initTemplates();
this._initOptions();
this.instance = this._createInstance(element, this._initialOptions);
Expand All @@ -91,11 +74,12 @@ export abstract class DxComponent implements INestedOptionContainer, ICollection
this.instance.element().triggerHandler({ type: 'dxremove', _angularIntegration: true });
}
}
constructor(protected element: ElementRef, private ngZone: NgZone, templateHost: DxTemplateHost, private watcherHelper: WatcherHelper) {
this._initialOptions = {};
constructor(protected element: ElementRef, ngZone: NgZone, templateHost: DxTemplateHost, private watcherHelper: WatcherHelper) {
this._initialOptions = { integrationOptions: {} };
this.templates = [];
templateHost.setHost(this);
this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this));
this.eventHelper = new EmitterHelper(ngZone, this);
}
setTemplate(template: DxTemplateDirective) {
this.templates.push(template);
Expand All @@ -112,4 +96,3 @@ export abstract class DxComponentExtension extends DxComponent {
}



69 changes: 69 additions & 0 deletions src/core/events-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EventEmitter, NgZone } from '@angular/core';
import { DxComponent } from './component';

const dxToNgEventNames = {};
const nullEmitter = new EventEmitter<any>();

interface EventSubscriber {
handler: any;
unsubscribe: () => void;
}

export class NgEventsStrategy {
private subscribers: { [key: string]: EventSubscriber[] } = {};

constructor(private ngZone: NgZone, private component: DxComponent) { }

hasEvent(name: string) {
let emitter = this.getEmitter(name);
return emitter !== nullEmitter && emitter.observers.length;
}

fireEvent(name, args) {
this.ngZone.run(() => {
this.getEmitter(name).next(args && args[0]);
});
}

on(name, handler) {
let eventSubscribers = this.subscribers[name] || [],
subsriber = this.getEmitter(name).subscribe(handler.bind(this.component.instance)),
unsubscribe = subsriber.unsubscribe.bind(subsriber);

eventSubscribers.push({ handler, unsubscribe });
this.subscribers[name] = eventSubscribers;
}

off(name, handler) {
let eventSubscribers = this.subscribers[name] || [];
eventSubscribers
.filter(i => !handler || i.handler === handler)
.forEach(i => i.unsubscribe());
}

dispose() {}

private getEmitter(eventName: string): EventEmitter<any> {
return this.component[dxToNgEventNames[eventName]] || nullEmitter;
}
}

export class EmitterHelper {
strategy: NgEventsStrategy;

constructor(ngZone: NgZone, private component: DxComponent) {
this.strategy = new NgEventsStrategy(ngZone, component);
}
fireNgEvent(eventName: string, eventArgs: any) {
let emitter = this.component[eventName];
if (emitter) {
emitter.next(eventArgs && eventArgs[0]);
}
}
createEmitter(ngEventName: string, dxEventName: string) {
this.component[ngEventName] = new EventEmitter();
if (dxEventName) {
dxToNgEventNames[dxEventName] = ngEventName;
}
}
}
7 changes: 3 additions & 4 deletions templates/component.tst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
Component,
NgModule,
ElementRef,
EventEmitter,
NgZone,
Input,
Output,
Expand Down Expand Up @@ -76,7 +75,7 @@ export class <#= it.className #>Component extends <#= baseClass #> <#? implement

<#?#><#~#>

<#~ it.events :event:i #>@Output() <#= event.emit #> = new EventEmitter<any>();<#? i < it.events.length-1 #>
<#~ it.events :event:i #>@Output() <#= event.emit #>;<#? i < it.events.length-1 #>
<#?#><#~#>

<#~ collectionNestedComponents :component:i #>
Expand All @@ -95,10 +94,10 @@ export class <#= it.className #>Component extends <#= baseClass #> <#? implement

super(elementRef, ngZone, templateHost, _watcherHelper);

this._events = [
this._createEventEmitters([
<#~ it.events :event:i #>{ <#? event.subscribe #>subscribe: '<#= event.subscribe #>', <#?#>emit: '<#= event.emit #>' }<#? i < it.events.length-1 #>,
<#?#><#~#>
];<#? collectionProperties.length #>
]);<#? collectionProperties.length #>

this._idh.setHost(this);<#?#>
optionHost.setHost(this);
Expand Down
84 changes: 82 additions & 2 deletions tests/src/core/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ export class DxTestWidgetComponent extends DxComponent implements AfterViewInit,
constructor(elementRef: ElementRef, ngZone: NgZone, templateHost: DxTemplateHost, _watcherHelper: WatcherHelper) {
super(elementRef, ngZone, templateHost, _watcherHelper);

this._events = [
this._createEventEmitters([
{ subscribe: 'optionChanged', emit: 'onOptionChanged' },
{ subscribe: 'initialized', emit: 'onInitialized' },
{ subscribe: 'disposing', emit: 'onDisposing' },
{ subscribe: 'contentReady', emit: 'onContentReady' },
{ emit: 'testOptionChange' }
];
]);
}

protected _createInstance(element, options) {
Expand Down Expand Up @@ -156,6 +156,24 @@ describe('DevExtreme Angular 2 widget', () => {

}));

it('should emit testOptionChange event', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: '<dx-test-widget [testOption]="\'Test Value\'" (testOptionChange)="testMethod()"></dx-test-widget>'
}
});
let fixture = TestBed.createComponent(TestContainerComponent);
fixture.detectChanges();

let component = fixture.componentInstance,
instance = getWidget(fixture),
testSpy = spyOn(component, 'testMethod');

instance.option('testOption', 'new value');
fixture.detectChanges();
expect(testSpy).toHaveBeenCalledTimes(1);
}));

it('should change component option value', async(() => {
let fixture = TestBed.createComponent(DxTestWidgetComponent);
fixture.detectChanges();
Expand Down Expand Up @@ -251,4 +269,66 @@ describe('DevExtreme Angular 2 widget', () => {

}));

it('should unsubscribe events', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: '<dx-test-widget></dx-test-widget>'
}
});

let fixture = TestBed.createComponent(TestContainerComponent);
fixture.detectChanges();

let instance = getWidget(fixture),
spy = jasmine.createSpy('spy');

instance.on('optionChanged', spy);
instance.off('optionChanged', spy);

instance.option('testOption', 'new value');
fixture.detectChanges();

expect(spy).toHaveBeenCalledTimes(0);
}));

it('should unsubscribe all events', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: '<dx-test-widget></dx-test-widget>'
}
});

let fixture = TestBed.createComponent(TestContainerComponent);
fixture.detectChanges();

let instance = getWidget(fixture),
spy = jasmine.createSpy('spy');

instance.on('optionChanged', spy);
instance.off('optionChanged');

instance.option('testOption', 'new value');
fixture.detectChanges();

expect(spy).toHaveBeenCalledTimes(0);
}));

it('should have correct context in events', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: '<dx-test-widget></dx-test-widget>'
}
});

let fixture = TestBed.createComponent(TestContainerComponent);
fixture.detectChanges();

let instance = getWidget(fixture);

instance.on('optionChanged', function() {
expect(this).toBe(instance);
});
instance.option('testOption', 'new value');
}));

});
4 changes: 2 additions & 2 deletions tests/src/core/template.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ export class DxTestWidgetComponent extends DxComponent implements AfterViewInit
constructor(elementRef: ElementRef, ngZone: NgZone, templateHost: DxTemplateHost, _watcherHelper: WatcherHelper) {
super(elementRef, ngZone, templateHost, _watcherHelper);

this._events = [
this._createEventEmitters([
{ subscribe: 'optionChanged', emit: 'onOptionChanged' },
{ subscribe: 'initialized', emit: 'onInitialized' }
];
]);
}

protected _createInstance(element, options) {
Expand Down