feat(Directives): add the ability to declaratively bind events

relates to #621
This commit is contained in:
Victor Berchet 2015-03-06 15:44:59 +01:00
parent 86e9dd68a4
commit bfa18ffd9b
9 changed files with 259 additions and 49 deletions

View File

@ -8,16 +8,19 @@ export class Directive {
lightDomServices:any; //List; lightDomServices:any; //List;
implementsTypes:any; //List; implementsTypes:any; //List;
lifecycle:any; //List lifecycle:any; //List
events:any; //List
@CONST() @CONST()
constructor({ constructor({
selector, selector,
bind, bind,
events,
lightDomServices, lightDomServices,
implementsTypes, implementsTypes,
lifecycle lifecycle
}:{ }:{
selector:string, selector:string,
bind:any, bind:any,
events: any,
lightDomServices:List, lightDomServices:List,
implementsTypes:List, implementsTypes:List,
lifecycle:List lifecycle:List
@ -27,6 +30,7 @@ export class Directive {
this.lightDomServices = lightDomServices; this.lightDomServices = lightDomServices;
this.implementsTypes = implementsTypes; this.implementsTypes = implementsTypes;
this.bind = bind; this.bind = bind;
this.events = events;
this.lifecycle = lifecycle; this.lifecycle = lifecycle;
} }
@ -37,15 +41,14 @@ export class Directive {
export class Component extends Directive { export class Component extends Directive {
//TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works //TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works
lightDomServices:any; //List;
shadowDomServices:any; //List; shadowDomServices:any; //List;
componentServices:any; //List; componentServices:any; //List;
lifecycle:any; //List
@CONST() @CONST()
constructor({ constructor({
selector, selector,
bind, bind,
events,
lightDomServices, lightDomServices,
shadowDomServices, shadowDomServices,
componentServices, componentServices,
@ -54,6 +57,7 @@ export class Component extends Directive {
}:{ }:{
selector:String, selector:String,
bind:Object, bind:Object,
events:Object,
lightDomServices:List, lightDomServices:List,
shadowDomServices:List, shadowDomServices:List,
componentServices:List, componentServices:List,
@ -64,15 +68,14 @@ export class Component extends Directive {
super({ super({
selector: selector, selector: selector,
bind: bind, bind: bind,
events: events,
lightDomServices: lightDomServices, lightDomServices: lightDomServices,
implementsTypes: implementsTypes, implementsTypes: implementsTypes,
lifecycle: lifecycle lifecycle: lifecycle
}); });
this.lightDomServices = lightDomServices;
this.shadowDomServices = shadowDomServices; this.shadowDomServices = shadowDomServices;
this.componentServices = componentServices; this.componentServices = componentServices;
this.lifecycle = lifecycle;
} }
} }
@ -82,6 +85,7 @@ export class Decorator extends Directive {
constructor({ constructor({
selector, selector,
bind, bind,
events,
lightDomServices, lightDomServices,
implementsTypes, implementsTypes,
lifecycle, lifecycle,
@ -89,6 +93,7 @@ export class Decorator extends Directive {
}:{ }:{
selector:string, selector:string,
bind:any, bind:any,
events:any,
lightDomServices:List, lightDomServices:List,
implementsTypes:List, implementsTypes:List,
lifecycle:List, lifecycle:List,
@ -99,6 +104,7 @@ export class Decorator extends Directive {
super({ super({
selector: selector, selector: selector,
bind: bind, bind: bind,
events: events,
lightDomServices: lightDomServices, lightDomServices: lightDomServices,
implementsTypes: implementsTypes, implementsTypes: implementsTypes,
lifecycle: lifecycle lifecycle: lifecycle
@ -111,6 +117,7 @@ export class Viewport extends Directive {
constructor({ constructor({
selector, selector,
bind, bind,
events,
lightDomServices, lightDomServices,
implementsTypes, implementsTypes,
lifecycle lifecycle
@ -125,6 +132,7 @@ export class Viewport extends Directive {
super({ super({
selector: selector, selector: selector,
bind: bind, bind: bind,
events: events,
lightDomServices: lightDomServices, lightDomServices: lightDomServices,
implementsTypes: implementsTypes, implementsTypes: implementsTypes,
lifecycle: lifecycle lifecycle: lifecycle

View File

@ -1,6 +1,6 @@
import {ProtoElementInjector} from './element_injector'; import {ProtoElementInjector} from './element_injector';
import {DirectiveMetadata} from './directive_metadata'; import {DirectiveMetadata} from './directive_metadata';
import {List, Map} from 'angular2/src/facade/collection'; import {List, StringMap} from 'angular2/src/facade/collection';
import {ProtoView} from './view'; import {ProtoView} from './view';
export class ElementBinder { export class ElementBinder {
@ -10,7 +10,7 @@ export class ElementBinder {
textNodeIndices:List<int>; textNodeIndices:List<int>;
hasElementPropertyBindings:boolean; hasElementPropertyBindings:boolean;
nestedProtoView: ProtoView; nestedProtoView: ProtoView;
events:Map; events:StringMap;
constructor( constructor(
protoElementInjector: ProtoElementInjector, componentDirective:DirectiveMetadata, protoElementInjector: ProtoElementInjector, componentDirective:DirectiveMetadata,
viewportDirective:DirectiveMetadata) { viewportDirective:DirectiveMetadata) {

View File

@ -1,6 +1,6 @@
import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang'; import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang';
import {Math} from 'angular2/src/facade/math'; import {Math} from 'angular2/src/facade/math';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di'; import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di'; import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di';
@ -18,6 +18,8 @@ var MAX_DEPTH = Math.pow(2, 30) - 1;
var _undefined = new Object(); var _undefined = new Object();
var _noop = function(_) {};
var _staticKeys; var _staticKeys;
class StaticKeys { class StaticKeys {
@ -270,9 +272,9 @@ export class ProtoElementInjector {
} }
} }
instantiate(parent:ElementInjector, host:ElementInjector, eventCallbacks, instantiate(parent:ElementInjector, host:ElementInjector, events,
reflector: Reflector):ElementInjector { reflector: Reflector):ElementInjector {
return new ElementInjector(this, parent, host, eventCallbacks, reflector); return new ElementInjector(this, parent, host, events, reflector);
} }
directParent(): ProtoElementInjector { directParent(): ProtoElementInjector {
@ -325,11 +327,11 @@ export class ElementInjector extends TreeNode {
_obj9:any; _obj9:any;
_preBuiltObjects; _preBuiltObjects;
_constructionCounter; _constructionCounter;
_eventCallbacks; _events:StringMap;
_refelector: Reflector; _refelector: Reflector;
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector,
eventCallbacks: Map, reflector: Reflector) { events: StringMap, reflector: Reflector) {
super(parent); super(parent);
if (isPresent(parent) && isPresent(host)) { if (isPresent(parent) && isPresent(host)) {
throw new BaseException('Only either parent or host is allowed'); throw new BaseException('Only either parent or host is allowed');
@ -348,7 +350,7 @@ export class ElementInjector extends TreeNode {
this._preBuiltObjects = null; this._preBuiltObjects = null;
this._lightDomAppInjector = null; this._lightDomAppInjector = null;
this._shadowDomAppInjector = null; this._shadowDomAppInjector = null;
this._eventCallbacks = eventCallbacks; this._events = events;
this._obj0 = null; this._obj0 = null;
this._obj1 = null; this._obj1 = null;
this._obj2 = null; this._obj2 = null;
@ -513,13 +515,13 @@ export class ElementInjector extends TreeNode {
_buildEventEmitter(dep) { _buildEventEmitter(dep) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId); var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
if (isPresent(this._eventCallbacks)) { if (isPresent(this._events)) {
var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName); var eventMap = StringMapWrapper.get(this._events, dep.eventEmitterName);
if (isPresent(callback)) { if (isPresent(eventMap)) {
return ProtoView.buildInnerCallback(callback, view); return ProtoView.buildEventCallback(eventMap, view, this._proto.index);
} }
} }
return (_) => {}; return _noop;
} }
_buildPropSetter(dep) { _buildPropSetter(dep) {

View File

@ -183,7 +183,7 @@ function getElementDescription(domElement):string {
buf.add("<"); buf.add("<");
buf.add(DOM.tagName(domElement).toLowerCase()); buf.add(DOM.tagName(domElement).toLowerCase());
// show id and class first to ease element identification // show id and class first to ease element identification
addDescriptionAttribute(buf, "id", MapWrapper.get(atts, "id")); addDescriptionAttribute(buf, "id", MapWrapper.get(atts, "id"));
addDescriptionAttribute(buf, "class", MapWrapper.get(atts, "class")); addDescriptionAttribute(buf, "class", MapWrapper.get(atts, "class"));

View File

@ -152,7 +152,9 @@ export class ElementBinderBuilder extends CompileStep {
if (isPresent(current.eventBindings)) { if (isPresent(current.eventBindings)) {
this._bindEvents(protoView, current); this._bindEvents(protoView, current);
} }
this._bindDirectiveProperties(current.getAllDirectives(), current); var directives = current.getAllDirectives();
this._bindDirectiveProperties(directives, current);
this._bindDirectiveEvents(directives, current);
} else if (isPresent(parent)) { } else if (isPresent(parent)) {
elementBinder = parent.inheritedElementBinder; elementBinder = parent.inheritedElementBinder;
} }
@ -199,6 +201,19 @@ export class ElementBinderBuilder extends CompileStep {
}); });
} }
_bindDirectiveEvents(directives: List<DirectiveMetadata>, compileElement: CompileElement) {
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
var directive = directives[directiveIndex];
var annotation = directive.annotation;
if (isBlank(annotation.events)) continue;
var protoView = compileElement.inheritedProtoView;
StringMapWrapper.forEach(annotation.events, (action, eventName) => {
var expression = this._parser.parseAction(action, compileElement.elementDescription);
protoView.bindEvent(eventName, expression, directiveIndex);
});
}
}
_bindDirectiveProperties(directives: List<DirectiveMetadata>, _bindDirectiveProperties(directives: List<DirectiveMetadata>,
compileElement: CompileElement) { compileElement: CompileElement) {
var protoView = compileElement.inheritedProtoView; var protoView = compileElement.inheritedProtoView;

View File

@ -1,6 +1,6 @@
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {Promise} from 'angular2/src/facade/async'; import {Promise} from 'angular2/src/facade/async';
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord} import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord}
from 'angular2/change_detection'; from 'angular2/change_detection';
@ -326,8 +326,8 @@ export class ProtoView {
} }
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length); var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
for (var i = 0; i < elementsWithBindingsDynamic.length; ++i) { for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
elementsWithBindings[i] = elementsWithBindingsDynamic[i]; elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
} }
var viewNodes; var viewNodes;
@ -353,13 +353,13 @@ export class ProtoView {
var viewContainers = []; var viewContainers = [];
var componentChildViews = []; var componentChildViews = [];
for (var i = 0; i < binders.length; i++) { for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[i]; var binder = binders[binderIdx];
var element; var element;
if (i === 0 && this.rootBindingOffset === 1) { if (binderIdx === 0 && this.rootBindingOffset === 1) {
element = rootElementClone; element = rootElementClone;
} else { } else {
element = elementsWithBindings[i - this.rootBindingOffset]; element = elementsWithBindings[binderIdx - this.rootBindingOffset];
} }
var elementInjector = null; var elementInjector = null;
@ -376,7 +376,7 @@ export class ProtoView {
ListWrapper.push(rootElementInjectors, elementInjector); ListWrapper.push(rootElementInjectors, elementInjector);
} }
} }
elementInjectors[i] = elementInjector; elementInjectors[binderIdx] = elementInjector;
if (binder.hasElementPropertyBindings) { if (binder.hasElementPropertyBindings) {
ListWrapper.push(elementsWithPropertyBindings, element); ListWrapper.push(elementsWithPropertyBindings, element);
@ -421,15 +421,15 @@ export class ProtoView {
// preBuiltObjects // preBuiltObjects
if (isPresent(elementInjector)) { if (isPresent(elementInjector)) {
preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewContainer, preBuiltObjects[binderIdx] = new PreBuiltObjects(view, new NgElement(element), viewContainer,
lightDom, bindingPropagationConfig); lightDom, bindingPropagationConfig);
} }
// events // events
if (isPresent(binder.events)) { if (isPresent(binder.events)) {
MapWrapper.forEach(binder.events, (expr, eventName) => { StringMapWrapper.forEach(binder.events, (eventMap, eventName) => {
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) { if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
var handler = ProtoView.buildInnerCallback(expr, view); var handler = ProtoView.buildEventCallback(eventMap, view, binderIdx);
eventManager.addEventListener(element, eventName, handler); eventManager.addEventListener(element, eventName, handler);
} }
}); });
@ -446,17 +446,39 @@ export class ProtoView {
this._viewPool.push(view); this._viewPool.push(view);
} }
static buildInnerCallback(expr:AST, view:View) { /**
* Create an event callback invoked in the context of the enclosing View
*
* @param {AST} expr
* @param {View} view
* @returns {Function}
*/
/**
* Creates the event callback.
*
* @param {Map} eventMap Map directiveIndexes to expressions
* @param {View} view
* @param {int} injectorIdx
* @returns {Function}
*/
static buildEventCallback(eventMap: Map, view:View, injectorIdx: int) {
var locals = MapWrapper.create(); var locals = MapWrapper.create();
return (event) => { return (event) => {
// Most of the time the event will be fired only when the view is // Most of the time the event will be fired only when the view is in the live document.
// in the live document. However, in a rare circumstance the // However, in a rare circumstance the view might get dehydrated, in between the event
// view might get dehydrated, in between the event queuing up and // queuing up and firing.
// firing.
if (view.hydrated()) { if (view.hydrated()) {
MapWrapper.set(locals, '$event', event); MapWrapper.set(locals, '$event', event);
var context = new ContextWithVariableBindings(view.context, locals); MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
expr.eval(context); var context;
if (directiveIndex === -1) {
context = view.context;
} else {
context = view.elementInjectors[injectorIdx].getDirectiveAtIndex(directiveIndex);
}
expr.eval(new ContextWithVariableBindings(context, locals));
});
} }
} }
} }
@ -505,14 +527,31 @@ export class ProtoView {
} }
/** /**
* Adds an event binding for the last created ElementBinder via bindElement * Adds an event binding for the last created ElementBinder via bindElement.
*
* If the directive index is a positive integer, the event is evaluated in the context of
* the given directive.
*
* If the directive index is -1, the event is evaluated in the context of the enclosing view.
*
* @param {string} eventName
* @param {AST} expression
* @param {int} directiveIndex The directive index in the binder or -1 when the event is not bound
* to a directive
*/ */
bindEvent(eventName:string, expression:AST) { bindEvent(eventName:string, expression:AST, directiveIndex: int = -1) {
var elBinder = this.elementBinders[this.elementBinders.length-1]; var elBinder = this.elementBinders[this.elementBinders.length - 1];
if (isBlank(elBinder.events)) { var events = elBinder.events;
elBinder.events = MapWrapper.create(); if (isBlank(events)) {
events = StringMapWrapper.create();
elBinder.events = events;
} }
MapWrapper.set(elBinder.events, eventName, expression); var event = StringMapWrapper.get(events, eventName);
if (isBlank(event)) {
event = MapWrapper.create();
StringMapWrapper.set(events, eventName, event);
}
MapWrapper.set(event, directiveIndex, expression);
} }
/** /**

View File

@ -19,9 +19,14 @@ import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
import {CssProcessor} from 'angular2/src/core/compiler/css_processor'; import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
import {EventManager, DomEventsPlugin} from 'angular2/src/core/events/event_manager';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations'; import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations';
import {Template} from 'angular2/src/core/annotations/template'; import {Template} from 'angular2/src/core/annotations/template';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter} from 'angular2/src/core/annotations/di';
import {If} from 'angular2/src/directives/if'; import {If} from 'angular2/src/directives/if';
@ -57,7 +62,11 @@ export function main() {
var view, ctx, cd; var view, ctx, cd;
function createView(pv) { function createView(pv) {
ctx = new MyComp(); ctx = new MyComp();
view = pv.instantiate(null, null, reflector); view = pv.instantiate(
null,
new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()),
reflector
);
view.hydrate(new Injector([]), null, ctx); view.hydrate(new Injector([]), null, ctx);
cd = view.changeDetector; cd = view.changeDetector;
} }
@ -435,6 +444,32 @@ export function main() {
done(); done();
}) })
}); });
it('should support events', (done) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<div emitter listener></div>',
directives: [DecoratorEmitingEvent, DecoratorListeningEvent]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
var injector = view.elementInjectors[0];
var emitter = injector.get(DecoratorEmitingEvent);
var listener = injector.get(DecoratorListeningEvent);
expect(emitter.msg).toEqual('');
expect(listener.msg).toEqual('');
emitter.fireEvent('fired !');
expect(emitter.msg).toEqual('fired !');
expect(listener.msg).toEqual('fired !');
done();
});
});
}); });
if (assertionsEnabled()) { if (assertionsEnabled()) {
@ -651,3 +686,56 @@ class DoublePipeFactory {
return new DoublePipe(); return new DoublePipe();
} }
} }
@Decorator({
selector: '[emitter]',
events: {'event': 'onEvent($event)'}
})
class DecoratorEmitingEvent {
msg: string;
emitter;
constructor(@EventEmitter('event') emitter:Function) {
this.msg = '';
this.emitter = emitter;
}
fireEvent(msg: string) {
this.emitter(msg);
}
onEvent(msg: string) {
this.msg = msg;
}
}
@Decorator({
selector: '[listener]',
events: {'event': 'onEvent($event)'}
})
class DecoratorListeningEvent {
msg: string;
constructor() {
this.msg = '';
}
onEvent(msg: string) {
this.msg = msg;
}
}
class FakeVmTurnZone extends VmTurnZone {
constructor() {
super({enableLongStackTrace: false});
}
run(fn) {
fn();
}
runOutsideAngular(fn) {
fn();
}
}

View File

@ -1,7 +1,7 @@
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib'; import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
import {isPresent, normalizeBlank} from 'angular2/src/facade/lang'; import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, Map, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {ElementBinderBuilder} from 'angular2/src/core/compiler/pipeline/element_binder_builder'; import {ElementBinderBuilder} from 'angular2/src/core/compiler/pipeline/element_binder_builder';
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
@ -138,7 +138,6 @@ export function main() {
expect(pv.elementBinders[1].protoElementInjector).toBeNull(); expect(pv.elementBinders[1].protoElementInjector).toBeNull();
}); });
it('should store the component directive', () => { it('should store the component directive', () => {
var directives = [SomeComponentDirective]; var directives = [SomeComponentDirective];
var pipeline = createPipeline({protoElementInjector: null, directives: directives}); var pipeline = createPipeline({protoElementInjector: null, directives: directives});
@ -379,10 +378,30 @@ export function main() {
var results = pipeline.process(el('<div viewroot event-binding></div>')); var results = pipeline.process(el('<div viewroot event-binding></div>'));
var pv = results[0].inheritedProtoView; var pv = results[0].inheritedProtoView;
var ast = MapWrapper.get(pv.elementBinders[0].events, 'event1'); var eventMap = StringMapWrapper.get(pv.elementBinders[0].events, 'event1');
var ast = MapWrapper.get(eventMap, -1);
expect(ast.eval(null)).toBe(2); expect(ast.eval(null)).toBe(2);
}); });
it('should bind directive events', () => {
var directives = [SomeDecoratorWithEvent];
var protoElementInjector = new ProtoElementInjector(null, 0, directives, true);
var pipeline = createPipeline({
directives: directives,
protoElementInjector: protoElementInjector
});
var results = pipeline.process(el('<div viewroot directives></div>'));
var pv = results[0].inheritedProtoView;
var directiveEvents = pv.elementBinders[0].events;
var eventMap = StringMapWrapper.get(directiveEvents, 'event');
// Get the cb AST for the directive at index 0 (SomeDecoratorWithEvent)
var ast = MapWrapper.get(eventMap, 0);
var context = new SomeDecoratorWithEvent();
expect(ast.eval(context)).toEqual('onEvent() callback');
});
it('should bind directive properties', () => { it('should bind directive properties', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'boundprop1': 'prop1', 'boundprop1': 'prop1',
@ -516,6 +535,21 @@ class SomeDecoratorDirectiveWithBinding {
} }
} }
@Decorator({
events: {'event': 'onEvent($event)'}
})
class SomeDecoratorWithEvent {
// Added here so that we don't have to wrap the content in a ContextWithVariableBindings
$event: string;
constructor() {
this.$event = 'onEvent'
}
onEvent(event) {
return `${event}() callback`;
}
}
@Decorator({ @Decorator({
bind: { bind: {
'decorProp': 'boundprop1', 'decorProp': 'boundprop1',

View File

@ -519,6 +519,20 @@ export function main() {
expect(called).toEqual(1); expect(called).toEqual(1);
}); });
it('should bind to directive events', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler]));
pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0);
view = createView(pv, new EventManager([new DomEventsPlugin()], new FakeVmTurnZone()));
var directive = view.elementInjectors[0].get(SomeDirectiveWithEventHandler);
expect(directive.event).toEqual(null);
dispatchClick(view.nodes[0]);
expect(directive.event).toBe(dispatchedEvent);
});
}); });
describe('react to record changes', () => { describe('react to record changes', () => {
@ -690,7 +704,6 @@ class SomeViewport {
} }
} }
class AnotherDirective { class AnotherDirective {
prop:string; prop:string;
constructor() { constructor() {
@ -708,6 +721,17 @@ class EventEmitterDirective {
} }
} }
class SomeDirectiveWithEventHandler {
event;
constructor() {
this.event = null;
}
onEvent(event) {
this.event = event;
}
}
class MyEvaluationContext { class MyEvaluationContext {
foo:string; foo:string;