Skip to content

Commit 60ce884

Browse files
committed
feat(core): remove the (^ syntax and make all DOM events bubbling
BREAKING CHANGE Before <div (^click)="onEventHandler()"> <button></button> </div> After <div (click)="onEventHandler()"> <button></button> </div> Closes angular#3864
1 parent 9934b3e commit 60ce884

File tree

22 files changed

+113
-243
lines changed

22 files changed

+113
-243
lines changed

modules/angular2/docs/core/01_templates.md

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -130,33 +130,6 @@ Example:
130130

131131
Example:
132132

133-
`<div on-click="doX()">`
134-
</td>
135-
</tr>
136-
<tr>
137-
<th>Event binding (bubbling)</th>
138-
<td>
139-
`<div (^event)="statement">`
140-
141-
Example:
142-
143-
`<div (^mouseover)="hlite()">`
144-
</td>
145-
<td>
146-
`<div on-bubble-event="statement">`
147-
148-
Example:
149-
150-
`<div on-bubble-mouseover="hlite()">`
151-
</td>
152-
</tr>
153-
<tr>
154-
<th>Declare reference</th>
155-
<td>
156-
`<div #symbol>`
157-
158-
Example:
159-
160133
<pre>
161134
```
162135
<video #player>
@@ -420,7 +393,7 @@ of the templates occurs. One such example is `ng-for`.
420393

421394
Where:
422395
* `[ng-for]` triggers the for directive.
423-
* `#person` exports the implicit `ng-for` item.
396+
* `#person` exports the implicit `ng-for` item.
424397
* `[ng-for-of]="people"` binds an iterable object to the `ng-for` controller.
425398
* `#i=index` exports item index as `i`.
426399

@@ -515,18 +488,16 @@ Where:
515488
* `statement` is a valid statement (as defined in section below).
516489
If the execution of the statement returns `false`, then `preventDefault`is applied on the DOM event.
517490

518-
By default, angular only listens to the element on the event, and ignores events which bubble. To listen to bubbled
519-
events (as in the case of clicking on any child) use the bubble option (`(event)` or `on-bubble-event`) as shown
520-
below.
491+
Angular listens to bubbled DOM events (as in the case of clicking on any child), as shown below:
521492

522493
<table>
523494
<tr>
524495
<th>Short form</th>
525-
<td>`<some-element (^some-event)="statement">`</td>
496+
<td>`<some-element (some-event)="statement">`</td>
526497
</tr>
527498
<tr>
528499
<th>Canonical form</th>
529-
<td>`<some-element on-bubble-some-event="statement">`</td>
500+
<td>`<some-element on-some-event="statement">`</td>
530501
</tr>
531502
</table>
532503

modules/angular2/src/core/render/dom/compiler/property_binding_parser.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ import {dashCaseToCamelCase} from '../util';
1212
// Group 1 = "bind-"
1313
// Group 2 = "var-" or "#"
1414
// Group 3 = "on-"
15-
// Group 4 = "onbubble-"
16-
// Group 5 = "bindon-"
17-
// Group 6 = the identifier after "bind-", "var-/#", or "on-"
18-
// Group 7 = idenitifer inside [()]
19-
// Group 8 = idenitifer inside []
20-
// Group 9 = identifier inside ()
15+
// Group 4 = "bindon-"
16+
// Group 5 = the identifier after "bind-", "var-/#", or "on-"
17+
// Group 6 = idenitifer inside [()]
18+
// Group 7 = idenitifer inside []
19+
// Group 8 = identifier inside ()
2120
var BIND_NAME_REGEXP =
22-
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(onbubble-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
21+
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
2322
/**
2423
* Parses the property bindings on a single element.
2524
*/
@@ -39,33 +38,30 @@ export class PropertyBindingParser implements CompileStep {
3938
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
4039
if (isPresent(bindParts)) {
4140
if (isPresent(bindParts[1])) { // match: bind-prop
42-
this._bindProperty(bindParts[6], attrValue, current, newAttrs);
41+
this._bindProperty(bindParts[5], attrValue, current, newAttrs);
4342

4443
} else if (isPresent(
4544
bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden"
46-
var identifier = bindParts[6];
45+
var identifier = bindParts[5];
4746
var value = attrValue == '' ? '\$implicit' : attrValue;
4847
this._bindVariable(identifier, value, current, newAttrs);
4948

5049
} else if (isPresent(bindParts[3])) { // match: on-event
51-
this._bindEvent(bindParts[6], attrValue, current, newAttrs);
50+
this._bindEvent(bindParts[5], attrValue, current, newAttrs);
5251

53-
} else if (isPresent(bindParts[4])) { // match: onbubble-event
54-
this._bindEvent('^' + bindParts[6], attrValue, current, newAttrs);
52+
} else if (isPresent(bindParts[4])) { // match: bindon-prop
53+
this._bindProperty(bindParts[5], attrValue, current, newAttrs);
54+
this._bindAssignmentEvent(bindParts[5], attrValue, current, newAttrs);
5555

56-
} else if (isPresent(bindParts[5])) { // match: bindon-prop
56+
} else if (isPresent(bindParts[6])) { // match: [(expr)]
5757
this._bindProperty(bindParts[6], attrValue, current, newAttrs);
5858
this._bindAssignmentEvent(bindParts[6], attrValue, current, newAttrs);
5959

60-
} else if (isPresent(bindParts[7])) { // match: [(expr)]
60+
} else if (isPresent(bindParts[7])) { // match: [expr]
6161
this._bindProperty(bindParts[7], attrValue, current, newAttrs);
62-
this._bindAssignmentEvent(bindParts[7], attrValue, current, newAttrs);
63-
64-
} else if (isPresent(bindParts[8])) { // match: [expr]
65-
this._bindProperty(bindParts[8], attrValue, current, newAttrs);
6662

67-
} else if (isPresent(bindParts[9])) { // match: (event)
68-
this._bindEvent(bindParts[9], attrValue, current, newAttrs);
63+
} else if (isPresent(bindParts[8])) { // match: (event)
64+
this._bindEvent(bindParts[8], attrValue, current, newAttrs);
6965
}
7066
} else {
7167
var expr = this._parser.parseInterpolation(attrValue, current.elementDescription);

modules/angular2/src/core/render/dom/events/event_manager.ts

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import {isBlank, BaseException, isPresent, StringWrapper} from 'angular2/src/cor
22
import {DOM} from 'angular2/src/core/dom/dom_adapter';
33
import {NgZone} from 'angular2/src/core/zone/ng_zone';
44

5-
const BUBBLE_SYMBOL = '^';
6-
75
export class EventManager {
86
constructor(public _plugins: EventManagerPlugin[], public _zone: NgZone) {
97
for (var i = 0; i < _plugins.length; i++) {
@@ -12,17 +10,13 @@ export class EventManager {
1210
}
1311

1412
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
15-
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName);
16-
var plugin = this._findPluginFor(withoutBubbleSymbol);
17-
plugin.addEventListener(element, withoutBubbleSymbol, handler,
18-
withoutBubbleSymbol != eventName);
13+
var plugin = this._findPluginFor(eventName);
14+
plugin.addEventListener(element, eventName, handler);
1915
}
2016

2117
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
22-
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName);
23-
var plugin = this._findPluginFor(withoutBubbleSymbol);
24-
return plugin.addGlobalEventListener(target, withoutBubbleSymbol, handler,
25-
withoutBubbleSymbol != eventName);
18+
var plugin = this._findPluginFor(eventName);
19+
return plugin.addGlobalEventListener(target, eventName, handler);
2620
}
2721

2822
getZone(): NgZone { return this._zone; }
@@ -37,28 +31,19 @@ export class EventManager {
3731
}
3832
throw new BaseException(`No event manager plugin found for event ${eventName}`);
3933
}
40-
41-
_removeBubbleSymbol(eventName: string): string {
42-
return eventName[0] == BUBBLE_SYMBOL ? StringWrapper.substring(eventName, 1) : eventName;
43-
}
4434
}
4535

4636
export class EventManagerPlugin {
4737
manager: EventManager;
4838

49-
// We are assuming here that all plugins support bubbled and non-bubbled events.
5039
// That is equivalent to having supporting $event.target
51-
// The bubbling flag (currently ^) is stripped before calling the supports and
52-
// addEventListener methods.
5340
supports(eventName: string): boolean { return false; }
5441

55-
addEventListener(element: HTMLElement, eventName: string, handler: Function,
56-
shouldSupportBubble: boolean) {
42+
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
5743
throw "not implemented";
5844
}
5945

60-
addGlobalEventListener(element: string, eventName: string, handler: Function,
61-
shouldSupportBubble: boolean): Function {
46+
addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
6247
throw "not implemented";
6348
}
6449
}
@@ -70,39 +55,17 @@ export class DomEventsPlugin extends EventManagerPlugin {
7055
// events.
7156
supports(eventName: string): boolean { return true; }
7257

73-
addEventListener(element: HTMLElement, eventName: string, handler: Function,
74-
shouldSupportBubble: boolean) {
75-
var outsideHandler =
76-
this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
58+
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
59+
var zone = this.manager._zone;
60+
var outsideHandler = (event) => zone.run(() => handler(event));
7761
this.manager._zone.runOutsideAngular(() => { DOM.on(element, eventName, outsideHandler); });
7862
}
7963

80-
addGlobalEventListener(target: string, eventName: string, handler: Function,
81-
shouldSupportBubble: boolean): Function {
64+
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
8265
var element = DOM.getGlobalEventTarget(target);
83-
var outsideHandler =
84-
this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
66+
var zone = this.manager._zone;
67+
var outsideHandler = (event) => zone.run(() => handler(event));
8568
return this.manager._zone.runOutsideAngular(
8669
() => { return DOM.onAndCancel(element, eventName, outsideHandler); });
8770
}
88-
89-
_getOutsideHandler(shouldSupportBubble: boolean, element: HTMLElement, handler: Function,
90-
zone: NgZone) {
91-
return shouldSupportBubble ? DomEventsPlugin.bubbleCallback(element, handler, zone) :
92-
DomEventsPlugin.sameElementCallback(element, handler, zone);
93-
}
94-
95-
static sameElementCallback(element: HTMLElement, handler: Function, zone: NgZone):
96-
(event: Event) => void {
97-
return (event) => {
98-
if (event.target === element) {
99-
zone.run(() => handler(event));
100-
}
101-
};
102-
}
103-
104-
static bubbleCallback(element: HTMLElement, handler: Function, zone: NgZone):
105-
(event: Event) => void {
106-
return (event) => zone.run(() => handler(event));
107-
}
10871
}

modules/angular2/src/core/render/dom/events/hammer_gestures.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ class HammerGesturesPlugin extends HammerGesturesPluginCommon {
1818
return true;
1919
}
2020

21-
addEventListener(Element element, String eventName, Function handler,
22-
bool shouldSupportBubble) {
23-
if (shouldSupportBubble) throw new BaseException(
24-
'Hammer.js plugin does not support bubbling gestures.');
21+
addEventListener(Element element, String eventName, Function handler) {
2522
var zone = this.manager.getZone();
2623
eventName = eventName.toLowerCase();
2724

modules/angular2/src/core/render/dom/events/hammer_gestures.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ export class HammerGesturesPlugin extends HammerGesturesPluginCommon {
1616
return true;
1717
}
1818

19-
addEventListener(element: HTMLElement, eventName: string, handler: Function,
20-
shouldSupportBubble: boolean) {
21-
if (shouldSupportBubble)
22-
throw new BaseException('Hammer.js plugin does not support bubbling gestures.');
19+
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
2320
var zone = this.manager.getZone();
2421
eventName = eventName.toLowerCase();
2522

modules/angular2/src/core/render/dom/events/key_events.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@ export class KeyEventsPlugin extends EventManagerPlugin {
2626
return isPresent(KeyEventsPlugin.parseEventName(eventName));
2727
}
2828

29-
addEventListener(element: HTMLElement, eventName: string, handler: (Event: any) => any,
30-
shouldSupportBubble: boolean) {
29+
addEventListener(element: HTMLElement, eventName: string, handler: (Event: any) => any) {
3130
var parsedEvent = KeyEventsPlugin.parseEventName(eventName);
3231

33-
var outsideHandler = KeyEventsPlugin.eventCallback(element, shouldSupportBubble,
34-
StringMapWrapper.get(parsedEvent, 'fullKey'),
35-
handler, this.manager.getZone());
32+
var outsideHandler = KeyEventsPlugin.eventCallback(
33+
element, StringMapWrapper.get(parsedEvent, 'fullKey'), handler, this.manager.getZone());
3634

3735
this.manager.getZone().runOutsideAngular(() => {
3836
DOM.on(element, StringMapWrapper.get(parsedEvent, 'domEventName'), outsideHandler);
@@ -91,11 +89,10 @@ export class KeyEventsPlugin extends EventManagerPlugin {
9189
return fullKey;
9290
}
9391

94-
static eventCallback(element: HTMLElement, shouldSupportBubble: boolean, fullKey: any,
95-
handler: (Event) => any, zone: NgZone): (event: Event) => void {
92+
static eventCallback(element: HTMLElement, fullKey: any, handler: (Event) => any, zone: NgZone):
93+
(event: Event) => void {
9694
return (event) => {
97-
var correctElement = shouldSupportBubble || event.target === element;
98-
if (correctElement && StringWrapper.equals(KeyEventsPlugin.getEventFullKey(event), fullKey)) {
95+
if (StringWrapper.equals(KeyEventsPlugin.getEventFullKey(event), fullKey)) {
9996
zone.run(() => handler(event));
10097
}
10198
};

modules/angular2/src/router/router_link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {Instruction, stringifyInstruction} from './instruction';
3838
selector: '[router-link]',
3939
properties: ['routeParams: routerLink'],
4040
host: {
41-
'(^click)': 'onClick()',
41+
'(click)': 'onClick()',
4242
'[attr.href]': 'visibleHref',
4343
'[class.router-link-active]': 'isRouteActive'
4444
}

modules/angular2/test/core/compiler/integration_spec.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,9 @@ export function main() {
856856

857857
dispatchEvent(tc.nativeElement, 'domEvent');
858858

859-
expect(listener.eventType).toEqual('domEvent');
859+
expect(listener.eventTypes)
860+
.toEqual(
861+
['domEvent', 'body_domEvent', 'document_domEvent', 'window_domEvent']);
860862

861863
async.done();
862864
});
@@ -874,16 +876,16 @@ export function main() {
874876
var tc = rootTC.componentViewChildren[0];
875877
var listener = tc.inject(DirectiveListeningDomEvent);
876878
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
877-
expect(listener.eventType).toEqual('window_domEvent');
879+
expect(listener.eventTypes).toEqual(['window_domEvent']);
878880

879-
listener = tc.inject(DirectiveListeningDomEvent);
881+
listener.eventTypes = [];
880882
dispatchEvent(DOM.getGlobalEventTarget("document"), 'domEvent');
881-
expect(listener.eventType).toEqual('document_domEvent');
883+
expect(listener.eventTypes).toEqual(['document_domEvent', 'window_domEvent']);
882884

883885
rootTC.destroy();
884-
listener = tc.inject(DirectiveListeningDomEvent);
886+
listener.eventTypes = [];
885887
dispatchEvent(DOM.getGlobalEventTarget("body"), 'domEvent');
886-
expect(listener.eventType).toEqual('');
888+
expect(listener.eventTypes).toEqual([]);
887889

888890
async.done();
889891
});
@@ -983,7 +985,7 @@ export function main() {
983985
var listener = tc.inject(DirectiveListeningDomEvent);
984986
var listenerother = tc.inject(DirectiveListeningDomEventOther);
985987
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
986-
expect(listener.eventType).toEqual('window_domEvent');
988+
expect(listener.eventTypes).toEqual(['window_domEvent']);
987989
expect(listenerother.eventType).toEqual('other_domEvent');
988990
expect(globalCounter).toEqual(1);
989991

@@ -1851,12 +1853,11 @@ class DirectiveListeningEvent {
18511853
})
18521854
@Injectable()
18531855
class DirectiveListeningDomEvent {
1854-
eventType: string;
1855-
constructor() { this.eventType = ''; }
1856-
onEvent(eventType: string) { this.eventType = eventType; }
1857-
onWindowEvent(eventType: string) { this.eventType = "window_" + eventType; }
1858-
onDocumentEvent(eventType: string) { this.eventType = "document_" + eventType; }
1859-
onBodyEvent(eventType: string) { this.eventType = "body_" + eventType; }
1856+
eventTypes: string[] = [];
1857+
onEvent(eventType: string) { this.eventTypes.push(eventType); }
1858+
onWindowEvent(eventType: string) { this.eventTypes.push("window_" + eventType); }
1859+
onDocumentEvent(eventType: string) { this.eventTypes.push("document_" + eventType); }
1860+
onBodyEvent(eventType: string) { this.eventTypes.push("body_" + eventType); }
18601861
}
18611862

18621863
var globalCounter = 0;

modules/angular2/test/core/render/dom/compiler/property_binding_parser_spec.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -182,20 +182,6 @@ export function main() {
182182
expect(eventBinding.fullName).toEqual('click');
183183
});
184184

185-
it('should detect onbubble- syntax', () => {
186-
var results = process(el('<div onbubble-click="b()"></div>'));
187-
var eventBinding = results[0].eventBindings[0];
188-
expect(eventBinding.source.source).toEqual('b()');
189-
expect(eventBinding.fullName).toEqual('^click');
190-
});
191-
192-
it('should detect onbubble- syntax with data- prefix', () => {
193-
var results = process(el('<div data-onbubble-click="b()"></div>'));
194-
var eventBinding = results[0].eventBindings[0];
195-
expect(eventBinding.source.source).toEqual('b()');
196-
expect(eventBinding.fullName).toEqual('^click');
197-
});
198-
199185
it('should parse event handlers using on- syntax as actions', () => {
200186
var results = process(el('<div on-click="foo=bar"></div>'));
201187
var eventBinding = results[0].eventBindings[0];

0 commit comments

Comments
 (0)