feat(Directives): add the ability to declaratively bind events
relates to #621
This commit is contained in:
parent
86e9dd68a4
commit
bfa18ffd9b
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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"));
|
||||||
|
@ -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;
|
||||||
|
87
modules/angular2/src/core/compiler/view.js
vendored
87
modules/angular2/src/core/compiler/view.js
vendored
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
26
modules/angular2/test/core/compiler/view_spec.js
vendored
26
modules/angular2/test/core/compiler/view_spec.js
vendored
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user