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

@ -19,9 +19,14 @@ import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
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 {Template} from 'angular2/src/core/annotations/template';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter} from 'angular2/src/core/annotations/di';
import {If} from 'angular2/src/directives/if';
@ -57,7 +62,11 @@ export function main() {
var view, ctx, cd;
function createView(pv) {
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);
cd = view.changeDetector;
}
@ -435,6 +444,32 @@ export function main() {
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()) {
@ -651,3 +686,56 @@ class DoublePipeFactory {
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 {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
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 {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
@ -138,7 +138,6 @@ export function main() {
expect(pv.elementBinders[1].protoElementInjector).toBeNull();
});
it('should store the component directive', () => {
var directives = [SomeComponentDirective];
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 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);
});
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', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'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({
bind: {
'decorProp': 'boundprop1',

View File

@ -519,6 +519,20 @@ export function main() {
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', () => {
@ -690,7 +704,6 @@ class SomeViewport {
}
}
class AnotherDirective {
prop:string;
constructor() {
@ -708,6 +721,17 @@ class EventEmitterDirective {
}
}
class SomeDirectiveWithEventHandler {
event;
constructor() {
this.event = null;
}
onEvent(event) {
this.event = event;
}
}
class MyEvaluationContext {
foo:string;