Skip to content

Commit b96e560

Browse files
committed
feat(events): add support for global events
Fixes angular#1098 Closes angular#1255
1 parent 7c95cea commit b96e560

27 files changed

+414
-103
lines changed

modules/angular2/src/core/annotations/annotations.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ export class Directive extends Injectable {
367367
* - `event1`: the DOM event that the directive listens to.
368368
* - `statement`: the statement to execute when the event occurs.
369369
*
370+
* To listen to global events, a target must be added to the event name.
371+
* The target can be `window`, `document` or `body`.
370372
*
371373
* When writing a directive event binding, you can also refer to the following local variables:
372374
* - `$event`: Current event object which triggered the event.
@@ -380,26 +382,30 @@ export class Directive extends Injectable {
380382
* @Directive({
381383
* hostListeners: {
382384
* 'event1': 'onMethod1(arguments)',
385+
* 'target:event2': 'onMethod2(arguments)',
383386
* ...
384387
* }
385388
* }
386389
* ```
387390
*
388391
* ## Basic Event Binding:
389392
*
390-
* Suppose you want to write a directive that triggers on `change` hostListeners in the DOM. You would define the event
391-
* binding as follows:
393+
* Suppose you want to write a directive that triggers on `change` events in the DOM and on `resize` events in window.
394+
* You would define the event binding as follows:
392395
*
393396
* ```
394397
* @Decorator({
395398
* selector: 'input',
396399
* hostListeners: {
397-
* 'change': 'onChange($event)'
400+
* 'change': 'onChange($event)',
401+
* 'window:resize': 'onResize($event)'
398402
* }
399403
* })
400404
* class InputDecorator {
401405
* onChange(event:Event) {
402406
* }
407+
* onResize(event:Event) {
408+
* }
403409
* }
404410
* ```
405411
*

modules/angular2/src/core/compiler/proto_view_factory.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,7 @@ export class ProtoViewFactory {
118118
protoView.bindElementProperty(astWithSource.ast, propertyName);
119119
});
120120
// events
121-
MapWrapper.forEach(renderElementBinder.eventBindings, (astWithSource, eventName) => {
122-
protoView.bindEvent(eventName, astWithSource.ast, -1);
123-
});
121+
protoView.bindEvent(renderElementBinder.eventBindings, -1);
124122
// variables
125123
// The view's locals needs to have a full set of variable names at construction time
126124
// 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 {
143141
protoView.bindDirectiveProperty(i, astWithSource.ast, propertyName, setter);
144142
});
145143
// directive events
146-
MapWrapper.forEach(renderDirectiveMetadata.eventBindings, (astWithSource, eventName) => {
147-
protoView.bindEvent(eventName, astWithSource.ast, i);
148-
});
144+
protoView.bindEvent(renderDirectiveMetadata.eventBindings, i);
149145
}
150146
}
151147

modules/angular2/src/core/compiler/view.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export class AppView {
272272
var elBinder = this.proto.elementBinders[elementIndex];
273273
if (isBlank(elBinder.hostListeners)) return;
274274
var eventMap = elBinder.hostListeners[eventName];
275-
if (isBlank(eventName)) return;
275+
if (isBlank(eventMap)) return;
276276
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
277277
var context;
278278
if (directiveIndex === -1) {
@@ -407,19 +407,23 @@ export class AppProtoView {
407407
* @param {int} directiveIndex The directive index in the binder or -1 when the event is not bound
408408
* to a directive
409409
*/
410-
bindEvent(eventName:string, expression:AST, directiveIndex: int = -1) {
410+
bindEvent(eventBindings: List<renderApi.EventBinding>, directiveIndex: int = -1) {
411411
var elBinder = this.elementBinders[this.elementBinders.length - 1];
412412
var events = elBinder.hostListeners;
413413
if (isBlank(events)) {
414414
events = StringMapWrapper.create();
415415
elBinder.hostListeners = events;
416416
}
417-
var event = StringMapWrapper.get(events, eventName);
418-
if (isBlank(event)) {
419-
event = MapWrapper.create();
420-
StringMapWrapper.set(events, eventName, event);
417+
for (var i = 0; i < eventBindings.length; i++) {
418+
var eventBinding = eventBindings[i];
419+
var eventName = eventBinding.fullName;
420+
var event = StringMapWrapper.get(events, eventName);
421+
if (isBlank(event)) {
422+
event = MapWrapper.create();
423+
StringMapWrapper.set(events, eventName, event);
424+
}
425+
MapWrapper.set(event, directiveIndex, eventBinding.source);
421426
}
422-
MapWrapper.set(event, directiveIndex, expression);
423427
}
424428

425429
/**

modules/angular2/src/dom/browser_adapter.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
119119
// addEventListener misses zones so we use element.on.
120120
element.on[event].listen(callback);
121121
}
122+
Function onAndCancel(EventTarget element, String event, callback(arg)) {
123+
// due to https://code.google.com/p/dart/issues/detail?id=17406
124+
// addEventListener misses zones so we use element.on.
125+
var subscription = element.on[event].listen(callback);
126+
return subscription.cancel;
127+
}
122128
void dispatchEvent(EventTarget el, Event evt) {
123129
el.dispatchEvent(evt);
124130
}
@@ -288,4 +294,13 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
288294
int keyCode = event.keyCode;
289295
return _keyCodeToKeyMap.containsKey(keyCode) ? _keyCodeToKeyMap[keyCode] : 'Unidentified';
290296
}
297+
getGlobalEventTarget(String target) {
298+
if (target == "window") {
299+
return window;
300+
} else if (target == "document") {
301+
return document;
302+
} else if (target == "body") {
303+
return document.body;
304+
}
305+
}
291306
}

modules/angular2/src/dom/browser_adapter.es6

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
7373
on(el, evt, listener) {
7474
el.addEventListener(evt, listener, false);
7575
}
76+
onAndCancel(el, evt, listener): Function {
77+
el.addEventListener(evt, listener, false);
78+
//Needed to follow Dart's subscription semantic, until fix of
79+
//https://code.google.com/p/dart/issues/detail?id=17406
80+
return () => {el.removeEventListener(evt, listener, false);};
81+
}
7682
dispatchEvent(el, evt) {
7783
el.dispatchEvent(evt);
7884
}
@@ -353,4 +359,13 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
353359
}
354360
return key;
355361
}
362+
getGlobalEventTarget(target:string) {
363+
if (target == "window") {
364+
return window;
365+
} else if (target == "document") {
366+
return document;
367+
} else if (target == "body") {
368+
return document.body;
369+
}
370+
}
356371
}

modules/angular2/src/dom/dom_adapter.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export class DomAdapter {
3939
on(el, evt, listener) {
4040
throw _abstract();
4141
}
42+
onAndCancel(el, evt, listener): Function {
43+
throw _abstract();
44+
}
4245
dispatchEvent(el, evt) {
4346
throw _abstract();
4447
}
@@ -273,4 +276,7 @@ export class DomAdapter {
273276
supportsNativeShadowDOM(): boolean {
274277
throw _abstract();
275278
}
279+
getGlobalEventTarget(target:string) {
280+
throw _abstract();
281+
}
276282
}

modules/angular2/src/dom/html_adapter.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class Html5LibDomAdapter implements DomAdapter {
2929
on(el, evt, listener) {
3030
throw 'not implemented';
3131
}
32+
Function onAndCancel(el, evt, listener) {
33+
throw 'not implemented';
34+
}
3235
dispatchEvent(el, evt) {
3336
throw 'not implemented';
3437
}

modules/angular2/src/dom/parse5_adapter.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export class Parse5DomAdapter extends DomAdapter {
8686
on(el, evt, listener) {
8787
//Do nothing, in order to not break forms integration tests
8888
}
89+
onAndCancel(el, evt, listener): Function {
90+
//Do nothing, in order to not break forms integration tests
91+
}
8992
dispatchEvent(el, evt) {
9093
throw _notImplemented('dispatchEvent');
9194
}

modules/angular2/src/mock/vm_turn_zone_mock.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export class MockVmTurnZone extends VmTurnZone {
1010
}
1111

1212
runOutsideAngular(fn) {
13-
fn();
13+
return fn();
1414
}
1515
}

modules/angular2/src/render/api.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ import {ASTWithSource} from 'angular2/change_detection';
1515
* - render compiler is not on the critical path as
1616
* its output will be stored in precompiled templates.
1717
*/
18+
export class EventBinding {
19+
fullName: string; // name/target:name, e.g "click", "window:resize"
20+
source: ASTWithSource;
21+
22+
constructor(fullName :string, source: ASTWithSource) {
23+
this.fullName = fullName;
24+
this.source = source;
25+
}
26+
}
27+
1828
export class ElementBinder {
1929
index:number;
2030
parentIndex:number;
@@ -26,7 +36,7 @@ export class ElementBinder {
2636
// Note: this contains a preprocessed AST
2737
// that replaced the values that should be extracted from the element
2838
// with a local name
29-
eventBindings: Map<string, ASTWithSource>;
39+
eventBindings: List<EventBinding>;
3040
textBindings: List<ASTWithSource>;
3141
readAttributes: Map<string, string>;
3242

@@ -57,7 +67,7 @@ export class DirectiveBinder {
5767
// Note: this contains a preprocessed AST
5868
// that replaced the values that should be extracted from the element
5969
// with a local name
60-
eventBindings: Map<string, ASTWithSource>;
70+
eventBindings: List<EventBinding>;
6171
constructor({
6272
directiveIndex, propertyBindings, eventBindings
6373
}) {

0 commit comments

Comments
 (0)