parent
7c95cea3a8
commit
b96e560c8d
@ -367,6 +367,8 @@ export class Directive extends Injectable {
|
|||||||
* - `event1`: the DOM event that the directive listens to.
|
* - `event1`: the DOM event that the directive listens to.
|
||||||
* - `statement`: the statement to execute when the event occurs.
|
* - `statement`: the statement to execute when the event occurs.
|
||||||
*
|
*
|
||||||
|
* To listen to global events, a target must be added to the event name.
|
||||||
|
* The target can be `window`, `document` or `body`.
|
||||||
*
|
*
|
||||||
* When writing a directive event binding, you can also refer to the following local variables:
|
* When writing a directive event binding, you can also refer to the following local variables:
|
||||||
* - `$event`: Current event object which triggered the event.
|
* - `$event`: Current event object which triggered the event.
|
||||||
@ -380,6 +382,7 @@ export class Directive extends Injectable {
|
|||||||
* @Directive({
|
* @Directive({
|
||||||
* hostListeners: {
|
* hostListeners: {
|
||||||
* 'event1': 'onMethod1(arguments)',
|
* 'event1': 'onMethod1(arguments)',
|
||||||
|
* 'target:event2': 'onMethod2(arguments)',
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
@ -387,19 +390,22 @@ export class Directive extends Injectable {
|
|||||||
*
|
*
|
||||||
* ## Basic Event Binding:
|
* ## Basic Event Binding:
|
||||||
*
|
*
|
||||||
* Suppose you want to write a directive that triggers on `change` hostListeners in the DOM. You would define the event
|
* Suppose you want to write a directive that triggers on `change` events in the DOM and on `resize` events in window.
|
||||||
* binding as follows:
|
* You would define the event binding as follows:
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* @Decorator({
|
* @Decorator({
|
||||||
* selector: 'input',
|
* selector: 'input',
|
||||||
* hostListeners: {
|
* hostListeners: {
|
||||||
* 'change': 'onChange($event)'
|
* 'change': 'onChange($event)',
|
||||||
|
* 'window:resize': 'onResize($event)'
|
||||||
* }
|
* }
|
||||||
* })
|
* })
|
||||||
* class InputDecorator {
|
* class InputDecorator {
|
||||||
* onChange(event:Event) {
|
* onChange(event:Event) {
|
||||||
* }
|
* }
|
||||||
|
* onResize(event:Event) {
|
||||||
|
* }
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
@ -118,9 +118,7 @@ export class ProtoViewFactory {
|
|||||||
protoView.bindElementProperty(astWithSource.ast, propertyName);
|
protoView.bindElementProperty(astWithSource.ast, propertyName);
|
||||||
});
|
});
|
||||||
// events
|
// events
|
||||||
MapWrapper.forEach(renderElementBinder.eventBindings, (astWithSource, eventName) => {
|
protoView.bindEvent(renderElementBinder.eventBindings, -1);
|
||||||
protoView.bindEvent(eventName, astWithSource.ast, -1);
|
|
||||||
});
|
|
||||||
// variables
|
// variables
|
||||||
// The view's locals needs to have a full set of variable names at construction time
|
// The view's locals needs to have a full set of variable names at construction time
|
||||||
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
|
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
|
||||||
@ -143,9 +141,7 @@ export class ProtoViewFactory {
|
|||||||
protoView.bindDirectiveProperty(i, astWithSource.ast, propertyName, setter);
|
protoView.bindDirectiveProperty(i, astWithSource.ast, propertyName, setter);
|
||||||
});
|
});
|
||||||
// directive events
|
// directive events
|
||||||
MapWrapper.forEach(renderDirectiveMetadata.eventBindings, (astWithSource, eventName) => {
|
protoView.bindEvent(renderDirectiveMetadata.eventBindings, i);
|
||||||
protoView.bindEvent(eventName, astWithSource.ast, i);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
modules/angular2/src/core/compiler/view.js
vendored
18
modules/angular2/src/core/compiler/view.js
vendored
@ -272,7 +272,7 @@ export class AppView {
|
|||||||
var elBinder = this.proto.elementBinders[elementIndex];
|
var elBinder = this.proto.elementBinders[elementIndex];
|
||||||
if (isBlank(elBinder.hostListeners)) return;
|
if (isBlank(elBinder.hostListeners)) return;
|
||||||
var eventMap = elBinder.hostListeners[eventName];
|
var eventMap = elBinder.hostListeners[eventName];
|
||||||
if (isBlank(eventName)) return;
|
if (isBlank(eventMap)) return;
|
||||||
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
||||||
var context;
|
var context;
|
||||||
if (directiveIndex === -1) {
|
if (directiveIndex === -1) {
|
||||||
@ -407,19 +407,23 @@ export class AppProtoView {
|
|||||||
* @param {int} directiveIndex The directive index in the binder or -1 when the event is not bound
|
* @param {int} directiveIndex The directive index in the binder or -1 when the event is not bound
|
||||||
* to a directive
|
* to a directive
|
||||||
*/
|
*/
|
||||||
bindEvent(eventName:string, expression:AST, directiveIndex: int = -1) {
|
bindEvent(eventBindings: List<renderApi.EventBinding>, directiveIndex: int = -1) {
|
||||||
var elBinder = this.elementBinders[this.elementBinders.length - 1];
|
var elBinder = this.elementBinders[this.elementBinders.length - 1];
|
||||||
var events = elBinder.hostListeners;
|
var events = elBinder.hostListeners;
|
||||||
if (isBlank(events)) {
|
if (isBlank(events)) {
|
||||||
events = StringMapWrapper.create();
|
events = StringMapWrapper.create();
|
||||||
elBinder.hostListeners = events;
|
elBinder.hostListeners = events;
|
||||||
}
|
}
|
||||||
var event = StringMapWrapper.get(events, eventName);
|
for (var i = 0; i < eventBindings.length; i++) {
|
||||||
if (isBlank(event)) {
|
var eventBinding = eventBindings[i];
|
||||||
event = MapWrapper.create();
|
var eventName = eventBinding.fullName;
|
||||||
StringMapWrapper.set(events, eventName, event);
|
var event = StringMapWrapper.get(events, eventName);
|
||||||
|
if (isBlank(event)) {
|
||||||
|
event = MapWrapper.create();
|
||||||
|
StringMapWrapper.set(events, eventName, event);
|
||||||
|
}
|
||||||
|
MapWrapper.set(event, directiveIndex, eventBinding.source);
|
||||||
}
|
}
|
||||||
MapWrapper.set(event, directiveIndex, expression);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -119,6 +119,12 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
// addEventListener misses zones so we use element.on.
|
// addEventListener misses zones so we use element.on.
|
||||||
element.on[event].listen(callback);
|
element.on[event].listen(callback);
|
||||||
}
|
}
|
||||||
|
Function onAndCancel(EventTarget element, String event, callback(arg)) {
|
||||||
|
// due to https://code.google.com/p/dart/issues/detail?id=17406
|
||||||
|
// addEventListener misses zones so we use element.on.
|
||||||
|
var subscription = element.on[event].listen(callback);
|
||||||
|
return subscription.cancel;
|
||||||
|
}
|
||||||
void dispatchEvent(EventTarget el, Event evt) {
|
void dispatchEvent(EventTarget el, Event evt) {
|
||||||
el.dispatchEvent(evt);
|
el.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
@ -288,4 +294,13 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
int keyCode = event.keyCode;
|
int keyCode = event.keyCode;
|
||||||
return _keyCodeToKeyMap.containsKey(keyCode) ? _keyCodeToKeyMap[keyCode] : 'Unidentified';
|
return _keyCodeToKeyMap.containsKey(keyCode) ? _keyCodeToKeyMap[keyCode] : 'Unidentified';
|
||||||
}
|
}
|
||||||
|
getGlobalEventTarget(String target) {
|
||||||
|
if (target == "window") {
|
||||||
|
return window;
|
||||||
|
} else if (target == "document") {
|
||||||
|
return document;
|
||||||
|
} else if (target == "body") {
|
||||||
|
return document.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,12 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
on(el, evt, listener) {
|
on(el, evt, listener) {
|
||||||
el.addEventListener(evt, listener, false);
|
el.addEventListener(evt, listener, false);
|
||||||
}
|
}
|
||||||
|
onAndCancel(el, evt, listener): Function {
|
||||||
|
el.addEventListener(evt, listener, false);
|
||||||
|
//Needed to follow Dart's subscription semantic, until fix of
|
||||||
|
//https://code.google.com/p/dart/issues/detail?id=17406
|
||||||
|
return () => {el.removeEventListener(evt, listener, false);};
|
||||||
|
}
|
||||||
dispatchEvent(el, evt) {
|
dispatchEvent(el, evt) {
|
||||||
el.dispatchEvent(evt);
|
el.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
@ -353,4 +359,13 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
getGlobalEventTarget(target:string) {
|
||||||
|
if (target == "window") {
|
||||||
|
return window;
|
||||||
|
} else if (target == "document") {
|
||||||
|
return document;
|
||||||
|
} else if (target == "body") {
|
||||||
|
return document.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
6
modules/angular2/src/dom/dom_adapter.js
vendored
6
modules/angular2/src/dom/dom_adapter.js
vendored
@ -39,6 +39,9 @@ export class DomAdapter {
|
|||||||
on(el, evt, listener) {
|
on(el, evt, listener) {
|
||||||
throw _abstract();
|
throw _abstract();
|
||||||
}
|
}
|
||||||
|
onAndCancel(el, evt, listener): Function {
|
||||||
|
throw _abstract();
|
||||||
|
}
|
||||||
dispatchEvent(el, evt) {
|
dispatchEvent(el, evt) {
|
||||||
throw _abstract();
|
throw _abstract();
|
||||||
}
|
}
|
||||||
@ -273,4 +276,7 @@ export class DomAdapter {
|
|||||||
supportsNativeShadowDOM(): boolean {
|
supportsNativeShadowDOM(): boolean {
|
||||||
throw _abstract();
|
throw _abstract();
|
||||||
}
|
}
|
||||||
|
getGlobalEventTarget(target:string) {
|
||||||
|
throw _abstract();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ class Html5LibDomAdapter implements DomAdapter {
|
|||||||
on(el, evt, listener) {
|
on(el, evt, listener) {
|
||||||
throw 'not implemented';
|
throw 'not implemented';
|
||||||
}
|
}
|
||||||
|
Function onAndCancel(el, evt, listener) {
|
||||||
|
throw 'not implemented';
|
||||||
|
}
|
||||||
dispatchEvent(el, evt) {
|
dispatchEvent(el, evt) {
|
||||||
throw 'not implemented';
|
throw 'not implemented';
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,9 @@ export class Parse5DomAdapter extends DomAdapter {
|
|||||||
on(el, evt, listener) {
|
on(el, evt, listener) {
|
||||||
//Do nothing, in order to not break forms integration tests
|
//Do nothing, in order to not break forms integration tests
|
||||||
}
|
}
|
||||||
|
onAndCancel(el, evt, listener): Function {
|
||||||
|
//Do nothing, in order to not break forms integration tests
|
||||||
|
}
|
||||||
dispatchEvent(el, evt) {
|
dispatchEvent(el, evt) {
|
||||||
throw _notImplemented('dispatchEvent');
|
throw _notImplemented('dispatchEvent');
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,6 @@ export class MockVmTurnZone extends VmTurnZone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runOutsideAngular(fn) {
|
runOutsideAngular(fn) {
|
||||||
fn();
|
return fn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
modules/angular2/src/render/api.js
vendored
14
modules/angular2/src/render/api.js
vendored
@ -15,6 +15,16 @@ import {ASTWithSource} from 'angular2/change_detection';
|
|||||||
* - render compiler is not on the critical path as
|
* - render compiler is not on the critical path as
|
||||||
* its output will be stored in precompiled templates.
|
* its output will be stored in precompiled templates.
|
||||||
*/
|
*/
|
||||||
|
export class EventBinding {
|
||||||
|
fullName: string; // name/target:name, e.g "click", "window:resize"
|
||||||
|
source: ASTWithSource;
|
||||||
|
|
||||||
|
constructor(fullName :string, source: ASTWithSource) {
|
||||||
|
this.fullName = fullName;
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ElementBinder {
|
export class ElementBinder {
|
||||||
index:number;
|
index:number;
|
||||||
parentIndex:number;
|
parentIndex:number;
|
||||||
@ -26,7 +36,7 @@ export class ElementBinder {
|
|||||||
// Note: this contains a preprocessed AST
|
// Note: this contains a preprocessed AST
|
||||||
// that replaced the values that should be extracted from the element
|
// that replaced the values that should be extracted from the element
|
||||||
// with a local name
|
// with a local name
|
||||||
eventBindings: Map<string, ASTWithSource>;
|
eventBindings: List<EventBinding>;
|
||||||
textBindings: List<ASTWithSource>;
|
textBindings: List<ASTWithSource>;
|
||||||
readAttributes: Map<string, string>;
|
readAttributes: Map<string, string>;
|
||||||
|
|
||||||
@ -57,7 +67,7 @@ export class DirectiveBinder {
|
|||||||
// Note: this contains a preprocessed AST
|
// Note: this contains a preprocessed AST
|
||||||
// that replaced the values that should be extracted from the element
|
// that replaced the values that should be extracted from the element
|
||||||
// with a local name
|
// with a local name
|
||||||
eventBindings: Map<string, ASTWithSource>;
|
eventBindings: List<EventBinding>;
|
||||||
constructor({
|
constructor({
|
||||||
directiveIndex, propertyBindings, eventBindings
|
directiveIndex, propertyBindings, eventBindings
|
||||||
}) {
|
}) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
import {Parser} from 'angular2/change_detection';
|
import {Parser} from 'angular2/change_detection';
|
||||||
@ -10,7 +10,7 @@ import {CompileElement} from './compile_element';
|
|||||||
import {CompileControl} from './compile_control';
|
import {CompileControl} from './compile_control';
|
||||||
|
|
||||||
import {DirectiveMetadata} from '../../api';
|
import {DirectiveMetadata} from '../../api';
|
||||||
import {dashCaseToCamelCase, camelCaseToDashCase} from '../util';
|
import {dashCaseToCamelCase, camelCaseToDashCase, EVENT_TARGET_SEPARATOR} from '../util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
||||||
@ -132,7 +132,13 @@ export class DirectiveParser extends CompileStep {
|
|||||||
|
|
||||||
_bindDirectiveEvent(eventName, action, compileElement, directiveBinder) {
|
_bindDirectiveEvent(eventName, action, compileElement, directiveBinder) {
|
||||||
var ast = this._parser.parseAction(action, compileElement.elementDescription);
|
var ast = this._parser.parseAction(action, compileElement.elementDescription);
|
||||||
directiveBinder.bindEvent(eventName, ast);
|
if (StringWrapper.contains(eventName, EVENT_TARGET_SEPARATOR)) {
|
||||||
|
var parts = eventName.split(EVENT_TARGET_SEPARATOR);
|
||||||
|
directiveBinder.bindEvent(parts[1], ast, parts[0]);
|
||||||
|
} else {
|
||||||
|
directiveBinder.bindEvent(eventName, ast);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_splitBindConfig(bindConfig:string) {
|
_splitBindConfig(bindConfig:string) {
|
||||||
|
@ -18,13 +18,15 @@ export class EventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(element, eventName: string, handler: Function) {
|
addEventListener(element, eventName: string, handler: Function) {
|
||||||
var shouldSupportBubble = eventName[0] == BUBBLE_SYMBOL;
|
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName);
|
||||||
if (shouldSupportBubble) {
|
var plugin = this._findPluginFor(withoutBubbleSymbol);
|
||||||
eventName = StringWrapper.substring(eventName, 1);
|
plugin.addEventListener(element, withoutBubbleSymbol, handler, withoutBubbleSymbol != eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var plugin = this._findPluginFor(eventName);
|
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
|
||||||
plugin.addEventListener(element, eventName, handler, shouldSupportBubble);
|
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName);
|
||||||
|
var plugin = this._findPluginFor(withoutBubbleSymbol);
|
||||||
|
return plugin.addGlobalEventListener(target, withoutBubbleSymbol, handler, withoutBubbleSymbol != eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getZone(): VmTurnZone {
|
getZone(): VmTurnZone {
|
||||||
@ -41,6 +43,10 @@ export class EventManager {
|
|||||||
}
|
}
|
||||||
throw new BaseException(`No event manager plugin found for event ${eventName}`);
|
throw new BaseException(`No event manager plugin found for event ${eventName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_removeBubbleSymbol(eventName: string): string {
|
||||||
|
return eventName[0] == BUBBLE_SYMBOL ? StringWrapper.substring(eventName, 1) : eventName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventManagerPlugin {
|
export class EventManagerPlugin {
|
||||||
@ -54,8 +60,11 @@ export class EventManagerPlugin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(element, eventName: string, handler: Function,
|
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) {
|
||||||
shouldSupportBubble: boolean) {
|
throw "not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
addGlobalEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean): Function {
|
||||||
throw "not implemented";
|
throw "not implemented";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,17 +78,27 @@ export class DomEventsPlugin extends EventManagerPlugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(element, eventName: string, handler: Function,
|
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) {
|
||||||
shouldSupportBubble: boolean) {
|
var outsideHandler = this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
|
||||||
var outsideHandler = shouldSupportBubble ?
|
|
||||||
DomEventsPlugin.bubbleCallback(element, handler, this.manager._zone) :
|
|
||||||
DomEventsPlugin.sameElementCallback(element, handler, this.manager._zone);
|
|
||||||
|
|
||||||
this.manager._zone.runOutsideAngular(() => {
|
this.manager._zone.runOutsideAngular(() => {
|
||||||
DOM.on(element, eventName, outsideHandler);
|
DOM.on(element, eventName, outsideHandler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addGlobalEventListener(target:string, eventName: string, handler: Function, shouldSupportBubble: boolean): Function {
|
||||||
|
var element = DOM.getGlobalEventTarget(target);
|
||||||
|
var outsideHandler = this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
|
||||||
|
return this.manager._zone.runOutsideAngular(() => {
|
||||||
|
return DOM.onAndCancel(element, eventName, outsideHandler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getOutsideHandler(shouldSupportBubble: boolean, element, handler: Function, zone: VmTurnZone) {
|
||||||
|
return shouldSupportBubble ?
|
||||||
|
DomEventsPlugin.bubbleCallback(element, handler, zone) :
|
||||||
|
DomEventsPlugin.sameElementCallback(element, handler, zone);
|
||||||
|
}
|
||||||
|
|
||||||
static sameElementCallback(element, handler, zone) {
|
static sameElementCallback(element, handler, zone) {
|
||||||
return (event) => {
|
return (event) => {
|
||||||
if (event.target === element) {
|
if (event.target === element) {
|
||||||
|
2
modules/angular2/src/render/dom/util.js
vendored
2
modules/angular2/src/render/dom/util.js
vendored
@ -3,6 +3,8 @@ import {StringWrapper, RegExpWrapper, isPresent} from 'angular2/src/facade/lang'
|
|||||||
export const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
export const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||||
export const NG_BINDING_CLASS = 'ng-binding';
|
export const NG_BINDING_CLASS = 'ng-binding';
|
||||||
|
|
||||||
|
export const EVENT_TARGET_SEPARATOR = ':';
|
||||||
|
|
||||||
var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])');
|
var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])');
|
||||||
var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])');
|
var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])');
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ export class ElementBinder {
|
|||||||
textNodeIndices: List<number>;
|
textNodeIndices: List<number>;
|
||||||
nestedProtoView: protoViewModule.RenderProtoView;
|
nestedProtoView: protoViewModule.RenderProtoView;
|
||||||
eventLocals: AST;
|
eventLocals: AST;
|
||||||
eventNames: List<string>;
|
localEvents: List<Event>;
|
||||||
|
globalEvents: List<Event>;
|
||||||
componentId: string;
|
componentId: string;
|
||||||
parentIndex:number;
|
parentIndex:number;
|
||||||
distanceToParent:number;
|
distanceToParent:number;
|
||||||
@ -20,7 +21,8 @@ export class ElementBinder {
|
|||||||
nestedProtoView,
|
nestedProtoView,
|
||||||
componentId,
|
componentId,
|
||||||
eventLocals,
|
eventLocals,
|
||||||
eventNames,
|
localEvents,
|
||||||
|
globalEvents,
|
||||||
parentIndex,
|
parentIndex,
|
||||||
distanceToParent,
|
distanceToParent,
|
||||||
propertySetters
|
propertySetters
|
||||||
@ -30,9 +32,22 @@ export class ElementBinder {
|
|||||||
this.nestedProtoView = nestedProtoView;
|
this.nestedProtoView = nestedProtoView;
|
||||||
this.componentId = componentId;
|
this.componentId = componentId;
|
||||||
this.eventLocals = eventLocals;
|
this.eventLocals = eventLocals;
|
||||||
this.eventNames = eventNames;
|
this.localEvents = localEvents;
|
||||||
|
this.globalEvents = globalEvents;
|
||||||
this.parentIndex = parentIndex;
|
this.parentIndex = parentIndex;
|
||||||
this.distanceToParent = distanceToParent;
|
this.distanceToParent = distanceToParent;
|
||||||
this.propertySetters = propertySetters;
|
this.propertySetters = propertySetters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Event {
|
||||||
|
name: string;
|
||||||
|
target: string;
|
||||||
|
fullName: string;
|
||||||
|
|
||||||
|
constructor(name: string, target: string, fullName: string) {
|
||||||
|
this.name = name;
|
||||||
|
this.target = target;
|
||||||
|
this.fullName = fullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||||
import {ListWrapper, MapWrapper, Set, SetWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, MapWrapper, Set, SetWrapper, List} from 'angular2/src/facade/collection';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -8,13 +8,13 @@ import {
|
|||||||
import {SetterFn} from 'angular2/src/reflection/types';
|
import {SetterFn} from 'angular2/src/reflection/types';
|
||||||
|
|
||||||
import {RenderProtoView} from './proto_view';
|
import {RenderProtoView} from './proto_view';
|
||||||
import {ElementBinder} from './element_binder';
|
import {ElementBinder, Event} from './element_binder';
|
||||||
import {setterFactory} from './property_setter_factory';
|
import {setterFactory} from './property_setter_factory';
|
||||||
|
|
||||||
import * as api from '../../api';
|
import * as api from '../../api';
|
||||||
import * as directDomRenderer from '../direct_dom_renderer';
|
import * as directDomRenderer from '../direct_dom_renderer';
|
||||||
|
|
||||||
import {NG_BINDING_CLASS} from '../util';
|
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR} from '../util';
|
||||||
|
|
||||||
export class ProtoViewBuilder {
|
export class ProtoViewBuilder {
|
||||||
rootElement;
|
rootElement;
|
||||||
@ -56,12 +56,12 @@ export class ProtoViewBuilder {
|
|||||||
var apiElementBinders = [];
|
var apiElementBinders = [];
|
||||||
ListWrapper.forEach(this.elements, (ebb) => {
|
ListWrapper.forEach(this.elements, (ebb) => {
|
||||||
var propertySetters = MapWrapper.create();
|
var propertySetters = MapWrapper.create();
|
||||||
var eventLocalsAstSplitter = new EventLocalsAstSplitter();
|
|
||||||
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (db) => {
|
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (db) => {
|
||||||
|
ebb.eventBuilder.merge(db.eventBuilder);
|
||||||
return new api.DirectiveBinder({
|
return new api.DirectiveBinder({
|
||||||
directiveIndex: db.directiveIndex,
|
directiveIndex: db.directiveIndex,
|
||||||
propertyBindings: db.propertyBindings,
|
propertyBindings: db.propertyBindings,
|
||||||
eventBindings: eventLocalsAstSplitter.splitEventAstIntoLocals(db.eventBindings)
|
eventBindings: db.eventBindings
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
MapWrapper.forEach(ebb.propertySetters, (setter, propertyName) => {
|
MapWrapper.forEach(ebb.propertySetters, (setter, propertyName) => {
|
||||||
@ -75,7 +75,7 @@ export class ProtoViewBuilder {
|
|||||||
directives: apiDirectiveBinders,
|
directives: apiDirectiveBinders,
|
||||||
nestedProtoView: nestedProtoView,
|
nestedProtoView: nestedProtoView,
|
||||||
propertyBindings: ebb.propertyBindings, variableBindings: ebb.variableBindings,
|
propertyBindings: ebb.propertyBindings, variableBindings: ebb.variableBindings,
|
||||||
eventBindings: eventLocalsAstSplitter.splitEventAstIntoLocals(ebb.eventBindings),
|
eventBindings: ebb.eventBindings,
|
||||||
textBindings: ebb.textBindings,
|
textBindings: ebb.textBindings,
|
||||||
readAttributes: ebb.readAttributes
|
readAttributes: ebb.readAttributes
|
||||||
}));
|
}));
|
||||||
@ -86,8 +86,9 @@ export class ProtoViewBuilder {
|
|||||||
distanceToParent: ebb.distanceToParent,
|
distanceToParent: ebb.distanceToParent,
|
||||||
nestedProtoView: isPresent(nestedProtoView) ? nestedProtoView.render.delegate : null,
|
nestedProtoView: isPresent(nestedProtoView) ? nestedProtoView.render.delegate : null,
|
||||||
componentId: ebb.componentId,
|
componentId: ebb.componentId,
|
||||||
eventLocals: eventLocalsAstSplitter.buildEventLocals(),
|
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
|
||||||
eventNames: eventLocalsAstSplitter.buildEventNames(),
|
localEvents: ebb.eventBuilder.buildLocalEvents(),
|
||||||
|
globalEvents: ebb.eventBuilder.buildGlobalEvents(),
|
||||||
propertySetters: propertySetters
|
propertySetters: propertySetters
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@ -112,7 +113,8 @@ export class ElementBinderBuilder {
|
|||||||
nestedProtoView:ProtoViewBuilder;
|
nestedProtoView:ProtoViewBuilder;
|
||||||
propertyBindings: Map<string, ASTWithSource>;
|
propertyBindings: Map<string, ASTWithSource>;
|
||||||
variableBindings: Map<string, string>;
|
variableBindings: Map<string, string>;
|
||||||
eventBindings: Map<string, ASTWithSource>;
|
eventBindings: List<api.EventBinding>;
|
||||||
|
eventBuilder: EventBuilder;
|
||||||
textBindingIndices: List<number>;
|
textBindingIndices: List<number>;
|
||||||
textBindings: List<ASTWithSource>;
|
textBindings: List<ASTWithSource>;
|
||||||
contentTagSelector:string;
|
contentTagSelector:string;
|
||||||
@ -129,7 +131,8 @@ export class ElementBinderBuilder {
|
|||||||
this.nestedProtoView = null;
|
this.nestedProtoView = null;
|
||||||
this.propertyBindings = MapWrapper.create();
|
this.propertyBindings = MapWrapper.create();
|
||||||
this.variableBindings = MapWrapper.create();
|
this.variableBindings = MapWrapper.create();
|
||||||
this.eventBindings = MapWrapper.create();
|
this.eventBindings = ListWrapper.create();
|
||||||
|
this.eventBuilder = new EventBuilder();
|
||||||
this.textBindings = [];
|
this.textBindings = [];
|
||||||
this.textBindingIndices = [];
|
this.textBindingIndices = [];
|
||||||
this.contentTagSelector = null;
|
this.contentTagSelector = null;
|
||||||
@ -191,8 +194,8 @@ export class ElementBinderBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvent(name, expression) {
|
bindEvent(name, expression, target = null) {
|
||||||
MapWrapper.set(this.eventBindings, name, expression);
|
ListWrapper.push(this.eventBindings, this.eventBuilder.add(name, expression, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
bindText(index, expression) {
|
bindText(index, expression) {
|
||||||
@ -212,49 +215,53 @@ export class ElementBinderBuilder {
|
|||||||
export class DirectiveBuilder {
|
export class DirectiveBuilder {
|
||||||
directiveIndex:number;
|
directiveIndex:number;
|
||||||
propertyBindings: Map<string, ASTWithSource>;
|
propertyBindings: Map<string, ASTWithSource>;
|
||||||
eventBindings: Map<string, ASTWithSource>;
|
eventBindings: List<api.EventBinding>;
|
||||||
|
eventBuilder: EventBuilder;
|
||||||
|
|
||||||
constructor(directiveIndex) {
|
constructor(directiveIndex) {
|
||||||
this.directiveIndex = directiveIndex;
|
this.directiveIndex = directiveIndex;
|
||||||
this.propertyBindings = MapWrapper.create();
|
this.propertyBindings = MapWrapper.create();
|
||||||
this.eventBindings = MapWrapper.create();
|
this.eventBindings = ListWrapper.create();
|
||||||
|
this.eventBuilder = new EventBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
bindProperty(name, expression) {
|
bindProperty(name, expression) {
|
||||||
MapWrapper.set(this.propertyBindings, name, expression);
|
MapWrapper.set(this.propertyBindings, name, expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvent(name, expression) {
|
bindEvent(name, expression, target = null) {
|
||||||
MapWrapper.set(this.eventBindings, name, expression);
|
ListWrapper.push(this.eventBindings, this.eventBuilder.add(name, expression, target));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventLocalsAstSplitter extends AstTransformer {
|
export class EventBuilder extends AstTransformer {
|
||||||
locals:List<AST>;
|
locals: List<AST>;
|
||||||
eventNames:List<string>;
|
localEvents: List<Event>;
|
||||||
_implicitReceiver:AST;
|
globalEvents: List<Event>;
|
||||||
|
_implicitReceiver: AST;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.locals = [];
|
this.locals = [];
|
||||||
this.eventNames = [];
|
this.localEvents = [];
|
||||||
|
this.globalEvents = [];
|
||||||
this._implicitReceiver = new ImplicitReceiver();
|
this._implicitReceiver = new ImplicitReceiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
splitEventAstIntoLocals(eventBindings:Map<string, ASTWithSource>):Map<string, ASTWithSource> {
|
add(name: string, source: ASTWithSource, target: string): api.EventBinding {
|
||||||
if (isPresent(eventBindings)) {
|
// TODO(tbosch): reenable this when we are parsing element properties
|
||||||
var result = MapWrapper.create();
|
// out of action expressions
|
||||||
MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
|
// var adjustedAst = astWithSource.ast.visit(this);
|
||||||
// TODO(tbosch): reenable this when we are parsing element properties
|
var adjustedAst = source.ast;
|
||||||
// out of action expressions
|
var fullName = isPresent(target) ? target + EVENT_TARGET_SEPARATOR + name : name;
|
||||||
// var adjustedAst = astWithSource.ast.visit(this);
|
var result = new api.EventBinding(fullName, new ASTWithSource(adjustedAst, source.source, ''));
|
||||||
var adjustedAst = astWithSource.ast;
|
var event = new Event(name, target, fullName);
|
||||||
MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, ''));
|
if (isBlank(target)) {
|
||||||
ListWrapper.push(this.eventNames, eventName);
|
ListWrapper.push(this.localEvents, event);
|
||||||
});
|
} else {
|
||||||
return result;
|
ListWrapper.push(this.globalEvents, event);
|
||||||
}
|
}
|
||||||
return null;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitAccessMember(ast:AccessMember) {
|
visitAccessMember(ast:AccessMember) {
|
||||||
@ -277,10 +284,32 @@ export class EventLocalsAstSplitter extends AstTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildEventLocals() {
|
buildEventLocals() {
|
||||||
return new LiteralArray(this.locals);
|
return this.locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildEventNames() {
|
buildLocalEvents() {
|
||||||
return this.eventNames;
|
return this.localEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildGlobalEvents() {
|
||||||
|
return this.globalEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
merge(eventBuilder: EventBuilder) {
|
||||||
|
this._merge(this.localEvents, eventBuilder.localEvents);
|
||||||
|
this._merge(this.globalEvents, eventBuilder.globalEvents);
|
||||||
|
ListWrapper.concat(this.locals, eventBuilder.locals);
|
||||||
|
}
|
||||||
|
|
||||||
|
_merge(host: List<Event>, tobeAdded: List<Event>) {
|
||||||
|
var names = ListWrapper.create();
|
||||||
|
for (var i = 0; i < host.length; i++) {
|
||||||
|
ListWrapper.push(names, host[i].fullName);
|
||||||
|
}
|
||||||
|
for (var j = 0; j < tobeAdded.length; j++) {
|
||||||
|
if (!ListWrapper.contains(names, tobeAdded[j].fullName)) {
|
||||||
|
ListWrapper.push(host, tobeAdded[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
modules/angular2/src/render/dom/view/view.js
vendored
34
modules/angular2/src/render/dom/view/view.js
vendored
@ -6,6 +6,7 @@ import {ViewContainer} from './view_container';
|
|||||||
import {RenderProtoView} from './proto_view';
|
import {RenderProtoView} from './proto_view';
|
||||||
import {LightDom} from '../shadow_dom/light_dom';
|
import {LightDom} from '../shadow_dom/light_dom';
|
||||||
import {Content} from '../shadow_dom/content_tag';
|
import {Content} from '../shadow_dom/content_tag';
|
||||||
|
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
|
||||||
|
|
||||||
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
||||||
|
|
||||||
@ -29,12 +30,14 @@ export class RenderView {
|
|||||||
contentTags: List<Content>;
|
contentTags: List<Content>;
|
||||||
lightDoms: List<LightDom>;
|
lightDoms: List<LightDom>;
|
||||||
proto: RenderProtoView;
|
proto: RenderProtoView;
|
||||||
|
eventManager: EventManager;
|
||||||
_hydrated: boolean;
|
_hydrated: boolean;
|
||||||
_eventDispatcher: any/*EventDispatcher*/;
|
_eventDispatcher: any/*EventDispatcher*/;
|
||||||
|
_eventHandlerRemovers: List<Function>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
proto:RenderProtoView, rootNodes:List,
|
proto:RenderProtoView, rootNodes:List,
|
||||||
boundTextNodes: List, boundElements:List, viewContainers:List, contentTags:List) {
|
boundTextNodes: List, boundElements:List, viewContainers:List, contentTags:List, eventManager: EventManager) {
|
||||||
this.proto = proto;
|
this.proto = proto;
|
||||||
this.rootNodes = rootNodes;
|
this.rootNodes = rootNodes;
|
||||||
this.boundTextNodes = boundTextNodes;
|
this.boundTextNodes = boundTextNodes;
|
||||||
@ -42,9 +45,11 @@ export class RenderView {
|
|||||||
this.viewContainers = viewContainers;
|
this.viewContainers = viewContainers;
|
||||||
this.contentTags = contentTags;
|
this.contentTags = contentTags;
|
||||||
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
|
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
|
||||||
|
this.eventManager = eventManager;
|
||||||
ListWrapper.fill(this.lightDoms, null);
|
ListWrapper.fill(this.lightDoms, null);
|
||||||
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
|
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
|
||||||
this._hydrated = false;
|
this._hydrated = false;
|
||||||
|
this._eventHandlerRemovers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrated() {
|
hydrated() {
|
||||||
@ -130,6 +135,26 @@ export class RenderView {
|
|||||||
lightDom.redistribute();
|
lightDom.redistribute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//add global events
|
||||||
|
this._eventHandlerRemovers = ListWrapper.create();
|
||||||
|
var binders = this.proto.elementBinders;
|
||||||
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||||
|
var binder = binders[binderIdx];
|
||||||
|
if (isPresent(binder.globalEvents)) {
|
||||||
|
for (var i = 0; i < binder.globalEvents.length; i++) {
|
||||||
|
var globalEvent = binder.globalEvents[i];
|
||||||
|
var remover = this._createGlobalEventListener(binderIdx, globalEvent.name, globalEvent.target, globalEvent.fullName);
|
||||||
|
ListWrapper.push(this._eventHandlerRemovers, remover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_createGlobalEventListener(elementIndex, eventName, eventTarget, fullName): Function {
|
||||||
|
return this.eventManager.addGlobalEventListener(eventTarget, eventName, (event) => {
|
||||||
|
this.dispatchEvent(elementIndex, fullName, event);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dehydrate() {
|
dehydrate() {
|
||||||
@ -156,6 +181,13 @@ export class RenderView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//remove global events
|
||||||
|
for (var i = 0; i < this._eventHandlerRemovers.length; i++) {
|
||||||
|
this._eventHandlerRemovers[i]();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._eventHandlerRemovers = null;
|
||||||
this._eventDispatcher = null;
|
this._eventDispatcher = null;
|
||||||
this._hydrated = false;
|
this._hydrated = false;
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ export class ViewFactory {
|
|||||||
|
|
||||||
var view = new viewModule.RenderView(
|
var view = new viewModule.RenderView(
|
||||||
protoView, viewRootNodes,
|
protoView, viewRootNodes,
|
||||||
boundTextNodes, boundElements, viewContainers, contentTags
|
boundTextNodes, boundElements, viewContainers, contentTags, this._eventManager
|
||||||
);
|
);
|
||||||
|
|
||||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||||
@ -139,10 +139,10 @@ export class ViewFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
if (isPresent(binder.eventLocals)) {
|
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
||||||
ListWrapper.forEach(binder.eventNames, (eventName) => {
|
for (var i = 0; i < binder.localEvents.length; i++) {
|
||||||
this._createEventListener(view, element, binderIdx, eventName, binder.eventLocals);
|
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name, binder.eventLocals);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,7 @@ import {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
|||||||
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
||||||
import {Directive} from 'angular2/src/core/annotations/annotations';
|
import {Directive} from 'angular2/src/core/annotations/annotations';
|
||||||
import {BindingPropagationConfig, Parser, Lexer} from 'angular2/change_detection';
|
import {BindingPropagationConfig, Parser, Lexer} from 'angular2/change_detection';
|
||||||
|
import {ViewRef, Renderer, EventBinding} from 'angular2/src/render/api';
|
||||||
import {ViewRef, Renderer} from 'angular2/src/render/api';
|
|
||||||
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
||||||
|
|
||||||
class DummyDirective extends Directive {
|
class DummyDirective extends Directive {
|
||||||
@ -701,7 +700,9 @@ export function main() {
|
|||||||
StringMapWrapper.set(handlers, eventName, eventHandler);
|
StringMapWrapper.set(handlers, eventName, eventHandler);
|
||||||
var pv = new AppProtoView(null, null, null);
|
var pv = new AppProtoView(null, null, null);
|
||||||
pv.bindElement(null, 0, null, null, null);
|
pv.bindElement(null, 0, null, null, null);
|
||||||
pv.bindEvent(eventName, new Parser(new Lexer()).parseAction('handler()', ''));
|
var eventBindings = ListWrapper.create();
|
||||||
|
ListWrapper.push(eventBindings, new EventBinding(eventName, new Parser(new Lexer()).parseAction('handler()', '')));
|
||||||
|
pv.bindEvent(eventBindings);
|
||||||
|
|
||||||
var view = new AppView(pv, MapWrapper.create());
|
var view = new AppView(pv, MapWrapper.create());
|
||||||
view.context = new ContextWithHandler(eventHandler);
|
view.context = new ContextWithHandler(eventHandler);
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
import {Type, isPresent, BaseException, assertionsEnabled, isJsObject} from 'angular2/src/facade/lang';
|
import {Type, isPresent, BaseException, assertionsEnabled, isJsObject, global} from 'angular2/src/facade/lang';
|
||||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
import {Injector, bind} from 'angular2/di';
|
import {Injector, bind} from 'angular2/di';
|
||||||
@ -541,6 +541,66 @@ export function main() {
|
|||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should support render global events', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
tb.overrideView(MyComp, new View({
|
||||||
|
template: '<div listener></div>',
|
||||||
|
directives: [DecoratorListeningDomEvent]
|
||||||
|
}));
|
||||||
|
|
||||||
|
tb.createView(MyComp, {context: ctx}).then((view) => {
|
||||||
|
var injector = view.rawView.elementInjectors[0];
|
||||||
|
|
||||||
|
var listener = injector.get(DecoratorListeningDomEvent);
|
||||||
|
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
|
||||||
|
expect(listener.eventType).toEqual('window_domEvent');
|
||||||
|
|
||||||
|
listener = injector.get(DecoratorListeningDomEvent);
|
||||||
|
dispatchEvent(DOM.getGlobalEventTarget("document"), 'domEvent');
|
||||||
|
expect(listener.eventType).toEqual('document_domEvent');
|
||||||
|
|
||||||
|
view.rawView.dehydrate();
|
||||||
|
listener = injector.get(DecoratorListeningDomEvent);
|
||||||
|
dispatchEvent(DOM.getGlobalEventTarget("body"), 'domEvent');
|
||||||
|
expect(listener.eventType).toEqual('');
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support render global events from multiple directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
tb.overrideView(MyComp, new View({
|
||||||
|
template: '<div *if="ctxBoolProp" listener listenerother></div>',
|
||||||
|
directives: [If, DecoratorListeningDomEvent, DecoratorListeningDomEventOther]
|
||||||
|
}));
|
||||||
|
|
||||||
|
tb.createView(MyComp, {context: ctx}).then((view) => {
|
||||||
|
globalCounter = 0;
|
||||||
|
ctx.ctxBoolProp = true;
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
var subview = view.rawView.viewContainers[0].get(0);
|
||||||
|
var injector = subview.elementInjectors[0];
|
||||||
|
var listener = injector.get(DecoratorListeningDomEvent);
|
||||||
|
var listenerother = injector.get(DecoratorListeningDomEventOther);
|
||||||
|
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
|
||||||
|
expect(listener.eventType).toEqual('window_domEvent');
|
||||||
|
expect(listenerother.eventType).toEqual('other_domEvent');
|
||||||
|
expect(globalCounter).toEqual(1);
|
||||||
|
|
||||||
|
ctx.ctxBoolProp = false;
|
||||||
|
view.detectChanges();
|
||||||
|
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
|
||||||
|
expect(globalCounter).toEqual(1);
|
||||||
|
|
||||||
|
ctx.ctxBoolProp = true;
|
||||||
|
view.detectChanges();
|
||||||
|
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
|
||||||
|
expect(globalCounter).toEqual(2);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("dynamic components", () => {
|
describe("dynamic components", () => {
|
||||||
@ -894,18 +954,49 @@ class DecoratorListeningEvent {
|
|||||||
|
|
||||||
@Decorator({
|
@Decorator({
|
||||||
selector: '[listener]',
|
selector: '[listener]',
|
||||||
hostListeners: {'domEvent': 'onEvent($event.type)'}
|
hostListeners: {
|
||||||
|
'domEvent': 'onEvent($event.type)',
|
||||||
|
'window:domEvent': 'onWindowEvent($event.type)',
|
||||||
|
'document:domEvent': 'onDocumentEvent($event.type)',
|
||||||
|
'body:domEvent': 'onBodyEvent($event.type)'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
class DecoratorListeningDomEvent {
|
class DecoratorListeningDomEvent {
|
||||||
eventType: string;
|
eventType: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.eventType = '';
|
this.eventType = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
onEvent(eventType: string) {
|
onEvent(eventType: string) {
|
||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
}
|
}
|
||||||
|
onWindowEvent(eventType: string) {
|
||||||
|
this.eventType = "window_" + eventType;
|
||||||
|
}
|
||||||
|
onDocumentEvent(eventType: string) {
|
||||||
|
this.eventType = "document_" + eventType;
|
||||||
|
}
|
||||||
|
onBodyEvent(eventType: string) {
|
||||||
|
this.eventType = "body_" + eventType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalCounter = 0;
|
||||||
|
@Decorator({
|
||||||
|
selector: '[listenerother]',
|
||||||
|
hostListeners: {
|
||||||
|
'window:domEvent': 'onEvent($event.type)'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class DecoratorListeningDomEventOther {
|
||||||
|
eventType: string;
|
||||||
|
counter: int;
|
||||||
|
constructor() {
|
||||||
|
this.eventType = '';
|
||||||
|
}
|
||||||
|
onEvent(eventType: string) {
|
||||||
|
globalCounter++;
|
||||||
|
this.eventType = "other_" + eventType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -23,7 +23,8 @@ export function main() {
|
|||||||
someDecorator,
|
someDecorator,
|
||||||
someDecoratorIgnoringChildren,
|
someDecoratorIgnoringChildren,
|
||||||
someDecoratorWithProps,
|
someDecoratorWithProps,
|
||||||
someDecoratorWithEvents
|
someDecoratorWithEvents,
|
||||||
|
someDecoratorWithGlobalEvents
|
||||||
];
|
];
|
||||||
parser = new Parser(new Lexer());
|
parser = new Parser(new Lexer());
|
||||||
});
|
});
|
||||||
@ -130,8 +131,21 @@ export function main() {
|
|||||||
el('<div some-decor-events></div>')
|
el('<div some-decor-events></div>')
|
||||||
);
|
);
|
||||||
var directiveBinding = results[0].directives[0];
|
var directiveBinding = results[0].directives[0];
|
||||||
expect(MapWrapper.get(directiveBinding.eventBindings, 'click').source)
|
expect(directiveBinding.eventBindings.length).toEqual(1);
|
||||||
.toEqual('doIt()');
|
var eventBinding = directiveBinding.eventBindings[0];
|
||||||
|
expect(eventBinding.fullName).toEqual('click');
|
||||||
|
expect(eventBinding.source.source).toEqual('doIt()');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bind directive global events', () => {
|
||||||
|
var results = process(
|
||||||
|
el('<div some-decor-globalevents></div>')
|
||||||
|
);
|
||||||
|
var directiveBinding = results[0].directives[0];
|
||||||
|
expect(directiveBinding.eventBindings.length).toEqual(1);
|
||||||
|
var eventBinding = directiveBinding.eventBindings[0];
|
||||||
|
expect(eventBinding.fullName).toEqual('window:resize');
|
||||||
|
expect(eventBinding.source.source).toEqual('doItGlobal()');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('viewport directives', () => {
|
describe('viewport directives', () => {
|
||||||
@ -246,3 +260,10 @@ var someDecoratorWithEvents = new DirectiveMetadata({
|
|||||||
'click': 'doIt()'
|
'click': 'doIt()'
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var someDecoratorWithGlobalEvents = new DirectiveMetadata({
|
||||||
|
selector: '[some-decor-globalevents]',
|
||||||
|
hostListeners: MapWrapper.createFromStringMap({
|
||||||
|
'window:resize': 'doItGlobal()'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
@ -96,10 +96,14 @@ export function main() {
|
|||||||
|
|
||||||
it('should detect () syntax', () => {
|
it('should detect () syntax', () => {
|
||||||
var results = process(el('<div (click)="b()"></div>'));
|
var results = process(el('<div (click)="b()"></div>'));
|
||||||
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('b()');
|
var eventBinding = results[0].eventBindings[0];
|
||||||
|
expect(eventBinding.source.source).toEqual('b()');
|
||||||
|
expect(eventBinding.fullName).toEqual('click');
|
||||||
// "(click[])" is not an expected syntax and is only used to validate the regexp
|
// "(click[])" is not an expected syntax and is only used to validate the regexp
|
||||||
results = process(el('<div (click[])="b()"></div>'));
|
results = process(el('<div (click[])="b()"></div>'));
|
||||||
expect(MapWrapper.get(results[0].eventBindings, 'click[]').source).toEqual('b()');
|
eventBinding = results[0].eventBindings[0];
|
||||||
|
expect(eventBinding.source.source).toEqual('b()');
|
||||||
|
expect(eventBinding.fullName).toEqual('click[]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect () syntax only if an attribute name starts and ends with ()', () => {
|
it('should detect () syntax only if an attribute name starts and ends with ()', () => {
|
||||||
@ -109,17 +113,23 @@ export function main() {
|
|||||||
|
|
||||||
it('should parse event handlers using () syntax as actions', () => {
|
it('should parse event handlers using () syntax as actions', () => {
|
||||||
var results = process(el('<div (click)="foo=bar"></div>'));
|
var results = process(el('<div (click)="foo=bar"></div>'));
|
||||||
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('foo=bar');
|
var eventBinding = results[0].eventBindings[0];
|
||||||
|
expect(eventBinding.source.source).toEqual('foo=bar');
|
||||||
|
expect(eventBinding.fullName).toEqual('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect on- syntax', () => {
|
it('should detect on- syntax', () => {
|
||||||
var results = process(el('<div on-click="b()"></div>'));
|
var results = process(el('<div on-click="b()"></div>'));
|
||||||
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('b()');
|
var eventBinding = results[0].eventBindings[0];
|
||||||
|
expect(eventBinding.source.source).toEqual('b()');
|
||||||
|
expect(eventBinding.fullName).toEqual('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse event handlers using on- syntax as actions', () => {
|
it('should parse event handlers using on- syntax as actions', () => {
|
||||||
var results = process(el('<div on-click="foo=bar"></div>'));
|
var results = process(el('<div on-click="foo=bar"></div>'));
|
||||||
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('foo=bar');
|
var eventBinding = results[0].eventBindings[0];
|
||||||
|
expect(eventBinding.source.source).toEqual('foo=bar');
|
||||||
|
expect(eventBinding.fullName).toEqual('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should store bound properties as temporal attributes', () => {
|
it('should store bound properties as temporal attributes', () => {
|
||||||
|
@ -83,6 +83,28 @@ export function main() {
|
|||||||
|
|
||||||
expect(receivedEvent).toBe(dispatchedEvent);
|
expect(receivedEvent).toBe(dispatchedEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add and remove global event listeners with correct bubbling', () => {
|
||||||
|
var element = el('<div><div></div></div>');
|
||||||
|
DOM.appendChild(document.body, element);
|
||||||
|
var dispatchedEvent = DOM.createMouseEvent('click');
|
||||||
|
var receivedEvent = null;
|
||||||
|
var handler = (e) => { receivedEvent = e; };
|
||||||
|
var manager = new EventManager([domEventPlugin], new FakeVmTurnZone());
|
||||||
|
|
||||||
|
var remover = manager.addGlobalEventListener("document", '^click', handler);
|
||||||
|
DOM.dispatchEvent(element, dispatchedEvent);
|
||||||
|
expect(receivedEvent).toBe(dispatchedEvent);
|
||||||
|
|
||||||
|
receivedEvent = null;
|
||||||
|
remover();
|
||||||
|
DOM.dispatchEvent(element, dispatchedEvent);
|
||||||
|
expect(receivedEvent).toBe(null);
|
||||||
|
|
||||||
|
remover = manager.addGlobalEventListener("document", 'click', handler);
|
||||||
|
DOM.dispatchEvent(element, dispatchedEvent);
|
||||||
|
expect(receivedEvent).toBe(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +126,8 @@ class FakeEventManagerPlugin extends EventManagerPlugin {
|
|||||||
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) {
|
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) {
|
||||||
MapWrapper.set(shouldSupportBubble ? this._bubbleEventHandlers : this._nonBubbleEventHandlers,
|
MapWrapper.set(shouldSupportBubble ? this._bubbleEventHandlers : this._nonBubbleEventHandlers,
|
||||||
eventName, handler);
|
eventName, handler);
|
||||||
|
return () => {MapWrapper.delete(shouldSupportBubble ? this._bubbleEventHandlers : this._nonBubbleEventHandlers,
|
||||||
|
eventName)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +141,6 @@ class FakeVmTurnZone extends VmTurnZone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runOutsideAngular(fn) {
|
runOutsideAngular(fn) {
|
||||||
fn();
|
return fn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,7 @@ export class FakeEventManagerPlugin extends EventManagerPlugin {
|
|||||||
|
|
||||||
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) {
|
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) {
|
||||||
MapWrapper.set(this._eventHandlers, eventName, handler);
|
MapWrapper.set(this._eventHandlers, eventName, handler);
|
||||||
|
return () => {MapWrapper.delete(this._eventHandlers, eventName);}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ export function main() {
|
|||||||
it('should attach the view nodes as child of the host element', () => {
|
it('should attach the view nodes as child of the host element', () => {
|
||||||
var host = el('<div><span>original content</span></div>');
|
var host = el('<div><span>original content</span></div>');
|
||||||
var nodes = el('<div>view</div>');
|
var nodes = el('<div>view</div>');
|
||||||
var view = new RenderView(null, [nodes], [], [], [], []);
|
var view = new RenderView(null, [nodes], [], [], [], [], null);
|
||||||
|
|
||||||
strategy.attachTemplate(host, view);
|
strategy.attachTemplate(host, view);
|
||||||
var firstChild = DOM.firstChild(host);
|
var firstChild = DOM.firstChild(host);
|
||||||
|
@ -42,7 +42,7 @@ export function main() {
|
|||||||
it('should attach the view nodes as child of the host element', () => {
|
it('should attach the view nodes as child of the host element', () => {
|
||||||
var host = el('<div><span>original content</span></div>');
|
var host = el('<div><span>original content</span></div>');
|
||||||
var nodes = el('<div>view</div>');
|
var nodes = el('<div>view</div>');
|
||||||
var view = new RenderView(null, [nodes], [], [], [], []);
|
var view = new RenderView(null, [nodes], [], [], [], [], null);
|
||||||
|
|
||||||
strategy.attachTemplate(host, view);
|
strategy.attachTemplate(host, view);
|
||||||
var firstChild = DOM.firstChild(host);
|
var firstChild = DOM.firstChild(host);
|
||||||
|
@ -35,7 +35,7 @@ export function main() {
|
|||||||
it('should attach the view nodes to the shadow root', () => {
|
it('should attach the view nodes to the shadow root', () => {
|
||||||
var host = el('<div><span>original content</span></div>');
|
var host = el('<div><span>original content</span></div>');
|
||||||
var nodes = el('<div>view</div>');
|
var nodes = el('<div>view</div>');
|
||||||
var view = new RenderView(null, [nodes], [], [], [], []);
|
var view = new RenderView(null, [nodes], [], [], [], [], null);
|
||||||
|
|
||||||
strategy.attachTemplate(host, view);
|
strategy.attachTemplate(host, view);
|
||||||
var shadowRoot = DOM.getShadowRoot(host);
|
var shadowRoot = DOM.getShadowRoot(host);
|
||||||
|
@ -2,6 +2,7 @@ import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} fr
|
|||||||
|
|
||||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||||
import {RenderView} from 'angular2/src/render/dom/view/view';
|
import {RenderView} from 'angular2/src/render/dom/view/view';
|
||||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||||
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
|
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
|
||||||
@ -9,14 +10,15 @@ import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
|
|||||||
export function main() {
|
export function main() {
|
||||||
|
|
||||||
function createView() {
|
function createView() {
|
||||||
var proto = null;
|
var proto = new RenderProtoView({element: el('<div></div>'), isRootView: false, elementBinders: []});
|
||||||
var rootNodes = [el('<div></div>')];
|
var rootNodes = [el('<div></div>')];
|
||||||
var boundTextNodes = [];
|
var boundTextNodes = [];
|
||||||
var boundElements = [el('<div></div>')];
|
var boundElements = [el('<div></div>')];
|
||||||
var viewContainers = [];
|
var viewContainers = [];
|
||||||
var contentTags = [];
|
var contentTags = [];
|
||||||
|
var eventManager = null;
|
||||||
return new RenderView(proto, rootNodes,
|
return new RenderView(proto, rootNodes,
|
||||||
boundTextNodes, boundElements, viewContainers, contentTags);
|
boundTextNodes, boundElements, viewContainers, contentTags, eventManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createShadowDomStrategy(log) {
|
function createShadowDomStrategy(log) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user