feat(events): adds support for injectable angular event emitters.
Event emitters can be injected into Directives. Event emitters take over
browser events with the same name. Emitted events do not bubble. Event
emitters can be injected even if there is no corresponding callback in
the template.
Use as follows:
@Decorator(...)
class MyDec(@EventEmitter('click') clickEmitter) {
...
fireClick() {
var eventData = {...};
this._clickEmitter(eventData);
}
}
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import {CONST} from 'facade/lang';
|
||||
import {DependencyAnnotation} from 'di/di';
|
||||
|
||||
/**
|
||||
* The directive can inject an emitter function that would emit events onto the
|
||||
* directive host element.
|
||||
*/
|
||||
export class EventEmitter extends DependencyAnnotation {
|
||||
eventName: string;
|
||||
@CONST()
|
||||
constructor(eventName) {
|
||||
this.eventName = eventName;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'facade/lang';
|
||||
import {Math} from 'facade/math';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {List, ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
|
||||
import {Parent, Ancestor} from 'core/annotations/visibility';
|
||||
import {EventEmitter} from 'core/annotations/events';
|
||||
import {onDestroy} from 'core/annotations/annotations';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {View, ProtoView} from 'core/compiler/view';
|
||||
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/compiler/shadow_dom_emulation/light_dom';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
@@ -89,15 +90,18 @@ class TreeNode {
|
||||
|
||||
export class DirectiveDependency extends Dependency {
|
||||
depth:int;
|
||||
eventEmitterName:string;
|
||||
|
||||
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int) {
|
||||
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int, eventEmitterName: string) {
|
||||
super(key, asPromise, lazy, properties);
|
||||
this.depth = depth;
|
||||
this.eventEmitterName = eventEmitterName;
|
||||
}
|
||||
|
||||
static createFrom(d:Dependency):Dependency {
|
||||
return new DirectiveDependency(d.key, d.asPromise, d.lazy,
|
||||
d.properties, DirectiveDependency._depth(d.properties));
|
||||
d.properties, DirectiveDependency._depth(d.properties),
|
||||
DirectiveDependency._eventEmitterName(d.properties));
|
||||
}
|
||||
|
||||
static _depth(properties):int {
|
||||
@@ -106,6 +110,15 @@ export class DirectiveDependency extends Dependency {
|
||||
if (ListWrapper.any(properties, p => p instanceof Ancestor)) return MAX_DEPTH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static _eventEmitterName(properties):string {
|
||||
for (var i = 0; i < properties.length; i++) {
|
||||
if (properties[i] instanceof EventEmitter) {
|
||||
return properties[i].eventName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class DirectiveBinding extends Binding {
|
||||
@@ -128,6 +141,10 @@ export class DirectiveBinding extends Binding {
|
||||
var binding = bind(type).toClass(type);
|
||||
return DirectiveBinding.createFromBinding(binding, annotation);
|
||||
}
|
||||
|
||||
static _hasEventEmitter(eventName: string, binding: DirectiveBinding) {
|
||||
return ListWrapper.any(binding.dependencies, (d) => (d.eventEmitterName == eventName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,8 +242,8 @@ export class ProtoElementInjector {
|
||||
}
|
||||
}
|
||||
|
||||
instantiate(parent:ElementInjector, host:ElementInjector):ElementInjector {
|
||||
return new ElementInjector(this, parent, host);
|
||||
instantiate(parent:ElementInjector, host:ElementInjector, eventCallbacks):ElementInjector {
|
||||
return new ElementInjector(this, parent, host, eventCallbacks);
|
||||
}
|
||||
|
||||
_createBinding(bindingOrType) {
|
||||
@@ -241,6 +258,21 @@ export class ProtoElementInjector {
|
||||
get hasBindings():boolean {
|
||||
return isPresent(this._binding0);
|
||||
}
|
||||
|
||||
hasEventEmitter(eventName: string) {
|
||||
var p = this;
|
||||
if (isPresent(p._binding0) && DirectiveBinding._hasEventEmitter(eventName, p._binding0)) return true;
|
||||
if (isPresent(p._binding1) && DirectiveBinding._hasEventEmitter(eventName, p._binding1)) return true;
|
||||
if (isPresent(p._binding2) && DirectiveBinding._hasEventEmitter(eventName, p._binding2)) return true;
|
||||
if (isPresent(p._binding3) && DirectiveBinding._hasEventEmitter(eventName, p._binding3)) return true;
|
||||
if (isPresent(p._binding4) && DirectiveBinding._hasEventEmitter(eventName, p._binding4)) return true;
|
||||
if (isPresent(p._binding5) && DirectiveBinding._hasEventEmitter(eventName, p._binding5)) return true;
|
||||
if (isPresent(p._binding6) && DirectiveBinding._hasEventEmitter(eventName, p._binding6)) return true;
|
||||
if (isPresent(p._binding7) && DirectiveBinding._hasEventEmitter(eventName, p._binding7)) return true;
|
||||
if (isPresent(p._binding8) && DirectiveBinding._hasEventEmitter(eventName, p._binding8)) return true;
|
||||
if (isPresent(p._binding9) && DirectiveBinding._hasEventEmitter(eventName, p._binding9)) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementInjector extends TreeNode {
|
||||
@@ -261,7 +293,8 @@ export class ElementInjector extends TreeNode {
|
||||
_view:View;
|
||||
_preBuiltObjects;
|
||||
_constructionCounter;
|
||||
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) {
|
||||
_eventCallbacks;
|
||||
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, eventCallbacks: Map) {
|
||||
super(parent);
|
||||
if (isPresent(parent) && isPresent(host)) {
|
||||
throw new BaseException('Only either parent or host is allowed');
|
||||
@@ -279,6 +312,7 @@ export class ElementInjector extends TreeNode {
|
||||
this._preBuiltObjects = null;
|
||||
this._lightDomAppInjector = null;
|
||||
this._shadowDomAppInjector = null;
|
||||
this._eventCallbacks = eventCallbacks;
|
||||
this._obj0 = null;
|
||||
this._obj1 = null;
|
||||
this._obj2 = null;
|
||||
@@ -431,9 +465,22 @@ export class ElementInjector extends TreeNode {
|
||||
}
|
||||
|
||||
_getByDependency(dep:DirectiveDependency, requestor:Key) {
|
||||
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
|
||||
return this._getByKey(dep.key, dep.depth, requestor);
|
||||
}
|
||||
|
||||
_buildEventEmitter(dep) {
|
||||
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
|
||||
if (isPresent(this._eventCallbacks)) {
|
||||
var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName);
|
||||
if (isPresent(callback)) {
|
||||
var locals = MapWrapper.create();
|
||||
return ProtoView.buildInnerCallback(callback, view, locals);
|
||||
}
|
||||
}
|
||||
return (_) => {};
|
||||
}
|
||||
|
||||
/*
|
||||
* It is fairly easy to annotate keys with metadata.
|
||||
* For example, key.metadata = 'directive'.
|
||||
@@ -534,6 +581,10 @@ export class ElementInjector extends TreeNode {
|
||||
hasInstances() {
|
||||
return this._constructionCounter > 0;
|
||||
}
|
||||
|
||||
hasEventEmitter(eventName: string) {
|
||||
return this._proto.hasEventEmitter(eventName);
|
||||
}
|
||||
}
|
||||
|
||||
class OutOfBoundsAccess extends Error {
|
||||
|
||||
@@ -325,9 +325,9 @@ export class ProtoView {
|
||||
if (isPresent(protoElementInjector)) {
|
||||
if (isPresent(protoElementInjector.parent)) {
|
||||
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
|
||||
elementInjector = protoElementInjector.instantiate(parentElementInjector, null);
|
||||
elementInjector = protoElementInjector.instantiate(parentElementInjector, null, binder.events);
|
||||
} else {
|
||||
elementInjector = protoElementInjector.instantiate(null, hostElementInjector);
|
||||
elementInjector = protoElementInjector.instantiate(null, hostElementInjector, binder.events);
|
||||
ListWrapper.push(rootElementInjectors, elementInjector);
|
||||
}
|
||||
}
|
||||
@@ -376,10 +376,10 @@ export class ProtoView {
|
||||
|
||||
// events
|
||||
if (isPresent(binder.events)) {
|
||||
// TODO(rado): if there is directive at this element that injected an
|
||||
// event emitter for that eventType do not attach the handler.
|
||||
MapWrapper.forEach(binder.events, (expr, eventName) => {
|
||||
ProtoView._addNativeEventListener(element, eventName, expr, view);
|
||||
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
|
||||
ProtoView._addNativeEventListener(element, eventName, expr, view);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -390,23 +390,30 @@ export class ProtoView {
|
||||
return view;
|
||||
}
|
||||
|
||||
static _addNativeEventListener(element: Element, eventName: string, expr, view: View) {
|
||||
static _addNativeEventListener(element: Element, eventName: string, expr: AST, view: View) {
|
||||
var locals = MapWrapper.create();
|
||||
var innerCallback = ProtoView.buildInnerCallback(expr, view, locals);
|
||||
DOM.on(element, eventName, (event) => {
|
||||
if (event.target === element) {
|
||||
// Most of the time the event will be fired only when the view is
|
||||
// in the live document. However, in a rare circumstance the
|
||||
// view might get dehydrated, in between the event queuing up and
|
||||
// firing.
|
||||
if (view.hydrated()) {
|
||||
MapWrapper.set(locals, '\$event', event);
|
||||
var context = new ContextWithVariableBindings(view.context, locals);
|
||||
expr.eval(context);
|
||||
}
|
||||
innerCallback(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static buildInnerCallback(expr:AST, view:View, locals: Map) {
|
||||
return (event) => {
|
||||
// Most of the time the event will be fired only when the view is
|
||||
// in the live document. However, in a rare circumstance the
|
||||
// view might get dehydrated, in between the event queuing up and
|
||||
// firing.
|
||||
if (view.hydrated()) {
|
||||
MapWrapper.set(locals, '\$event', event);
|
||||
var context = new ContextWithVariableBindings(view.context, locals);
|
||||
expr.eval(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_parentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
|
||||
var p = protoElementInjector.parent;
|
||||
return isPresent(p) ? preBuiltObjects[p.index].lightDom : null;
|
||||
|
||||
Reference in New Issue
Block a user