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 #3864
This commit is contained in:
vsavkin
2015-09-01 08:52:54 -07:00
committed by Victor Savkin
parent 9934b3ec7f
commit 60ce884671
22 changed files with 113 additions and 243 deletions

View File

@ -12,14 +12,13 @@ import {dashCaseToCamelCase} from '../util';
// Group 1 = "bind-"
// Group 2 = "var-" or "#"
// Group 3 = "on-"
// Group 4 = "onbubble-"
// Group 5 = "bindon-"
// Group 6 = the identifier after "bind-", "var-/#", or "on-"
// Group 7 = idenitifer inside [()]
// Group 8 = idenitifer inside []
// Group 9 = identifier inside ()
// Group 4 = "bindon-"
// Group 5 = the identifier after "bind-", "var-/#", or "on-"
// Group 6 = idenitifer inside [()]
// Group 7 = idenitifer inside []
// Group 8 = identifier inside ()
var BIND_NAME_REGEXP =
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(onbubble-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
/**
* Parses the property bindings on a single element.
*/
@ -39,33 +38,30 @@ export class PropertyBindingParser implements CompileStep {
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
if (isPresent(bindParts)) {
if (isPresent(bindParts[1])) { // match: bind-prop
this._bindProperty(bindParts[6], attrValue, current, newAttrs);
this._bindProperty(bindParts[5], attrValue, current, newAttrs);
} else if (isPresent(
bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden"
var identifier = bindParts[6];
var identifier = bindParts[5];
var value = attrValue == '' ? '\$implicit' : attrValue;
this._bindVariable(identifier, value, current, newAttrs);
} else if (isPresent(bindParts[3])) { // match: on-event
this._bindEvent(bindParts[6], attrValue, current, newAttrs);
this._bindEvent(bindParts[5], attrValue, current, newAttrs);
} else if (isPresent(bindParts[4])) { // match: onbubble-event
this._bindEvent('^' + bindParts[6], attrValue, current, newAttrs);
} else if (isPresent(bindParts[4])) { // match: bindon-prop
this._bindProperty(bindParts[5], attrValue, current, newAttrs);
this._bindAssignmentEvent(bindParts[5], attrValue, current, newAttrs);
} else if (isPresent(bindParts[5])) { // match: bindon-prop
} else if (isPresent(bindParts[6])) { // match: [(expr)]
this._bindProperty(bindParts[6], attrValue, current, newAttrs);
this._bindAssignmentEvent(bindParts[6], attrValue, current, newAttrs);
} else if (isPresent(bindParts[7])) { // match: [(expr)]
} else if (isPresent(bindParts[7])) { // match: [expr]
this._bindProperty(bindParts[7], attrValue, current, newAttrs);
this._bindAssignmentEvent(bindParts[7], attrValue, current, newAttrs);
} else if (isPresent(bindParts[8])) { // match: [expr]
this._bindProperty(bindParts[8], attrValue, current, newAttrs);
} else if (isPresent(bindParts[9])) { // match: (event)
this._bindEvent(bindParts[9], attrValue, current, newAttrs);
} else if (isPresent(bindParts[8])) { // match: (event)
this._bindEvent(bindParts[8], attrValue, current, newAttrs);
}
} else {
var expr = this._parser.parseInterpolation(attrValue, current.elementDescription);

View File

@ -2,8 +2,6 @@ import {isBlank, BaseException, isPresent, StringWrapper} from 'angular2/src/cor
import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
const BUBBLE_SYMBOL = '^';
export class EventManager {
constructor(public _plugins: EventManagerPlugin[], public _zone: NgZone) {
for (var i = 0; i < _plugins.length; i++) {
@ -12,17 +10,13 @@ export class EventManager {
}
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName);
var plugin = this._findPluginFor(withoutBubbleSymbol);
plugin.addEventListener(element, withoutBubbleSymbol, handler,
withoutBubbleSymbol != eventName);
var plugin = this._findPluginFor(eventName);
plugin.addEventListener(element, eventName, handler);
}
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName);
var plugin = this._findPluginFor(withoutBubbleSymbol);
return plugin.addGlobalEventListener(target, withoutBubbleSymbol, handler,
withoutBubbleSymbol != eventName);
var plugin = this._findPluginFor(eventName);
return plugin.addGlobalEventListener(target, eventName, handler);
}
getZone(): NgZone { return this._zone; }
@ -37,28 +31,19 @@ export class EventManager {
}
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 {
manager: EventManager;
// We are assuming here that all plugins support bubbled and non-bubbled events.
// That is equivalent to having supporting $event.target
// The bubbling flag (currently ^) is stripped before calling the supports and
// addEventListener methods.
supports(eventName: string): boolean { return false; }
addEventListener(element: HTMLElement, eventName: string, handler: Function,
shouldSupportBubble: boolean) {
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
throw "not implemented";
}
addGlobalEventListener(element: string, eventName: string, handler: Function,
shouldSupportBubble: boolean): Function {
addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
throw "not implemented";
}
}
@ -70,39 +55,17 @@ export class DomEventsPlugin extends EventManagerPlugin {
// events.
supports(eventName: string): boolean { return true; }
addEventListener(element: HTMLElement, eventName: string, handler: Function,
shouldSupportBubble: boolean) {
var outsideHandler =
this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
var zone = this.manager._zone;
var outsideHandler = (event) => zone.run(() => handler(event));
this.manager._zone.runOutsideAngular(() => { DOM.on(element, eventName, outsideHandler); });
}
addGlobalEventListener(target: string, eventName: string, handler: Function,
shouldSupportBubble: boolean): Function {
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
var element = DOM.getGlobalEventTarget(target);
var outsideHandler =
this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
var zone = this.manager._zone;
var outsideHandler = (event) => zone.run(() => handler(event));
return this.manager._zone.runOutsideAngular(
() => { return DOM.onAndCancel(element, eventName, outsideHandler); });
}
_getOutsideHandler(shouldSupportBubble: boolean, element: HTMLElement, handler: Function,
zone: NgZone) {
return shouldSupportBubble ? DomEventsPlugin.bubbleCallback(element, handler, zone) :
DomEventsPlugin.sameElementCallback(element, handler, zone);
}
static sameElementCallback(element: HTMLElement, handler: Function, zone: NgZone):
(event: Event) => void {
return (event) => {
if (event.target === element) {
zone.run(() => handler(event));
}
};
}
static bubbleCallback(element: HTMLElement, handler: Function, zone: NgZone):
(event: Event) => void {
return (event) => zone.run(() => handler(event));
}
}

View File

@ -18,10 +18,7 @@ class HammerGesturesPlugin extends HammerGesturesPluginCommon {
return true;
}
addEventListener(Element element, String eventName, Function handler,
bool shouldSupportBubble) {
if (shouldSupportBubble) throw new BaseException(
'Hammer.js plugin does not support bubbling gestures.');
addEventListener(Element element, String eventName, Function handler) {
var zone = this.manager.getZone();
eventName = eventName.toLowerCase();

View File

@ -16,10 +16,7 @@ export class HammerGesturesPlugin extends HammerGesturesPluginCommon {
return true;
}
addEventListener(element: HTMLElement, eventName: string, handler: Function,
shouldSupportBubble: boolean) {
if (shouldSupportBubble)
throw new BaseException('Hammer.js plugin does not support bubbling gestures.');
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
var zone = this.manager.getZone();
eventName = eventName.toLowerCase();

View File

@ -26,13 +26,11 @@ export class KeyEventsPlugin extends EventManagerPlugin {
return isPresent(KeyEventsPlugin.parseEventName(eventName));
}
addEventListener(element: HTMLElement, eventName: string, handler: (Event: any) => any,
shouldSupportBubble: boolean) {
addEventListener(element: HTMLElement, eventName: string, handler: (Event: any) => any) {
var parsedEvent = KeyEventsPlugin.parseEventName(eventName);
var outsideHandler = KeyEventsPlugin.eventCallback(element, shouldSupportBubble,
StringMapWrapper.get(parsedEvent, 'fullKey'),
handler, this.manager.getZone());
var outsideHandler = KeyEventsPlugin.eventCallback(
element, StringMapWrapper.get(parsedEvent, 'fullKey'), handler, this.manager.getZone());
this.manager.getZone().runOutsideAngular(() => {
DOM.on(element, StringMapWrapper.get(parsedEvent, 'domEventName'), outsideHandler);
@ -91,11 +89,10 @@ export class KeyEventsPlugin extends EventManagerPlugin {
return fullKey;
}
static eventCallback(element: HTMLElement, shouldSupportBubble: boolean, fullKey: any,
handler: (Event) => any, zone: NgZone): (event: Event) => void {
static eventCallback(element: HTMLElement, fullKey: any, handler: (Event) => any, zone: NgZone):
(event: Event) => void {
return (event) => {
var correctElement = shouldSupportBubble || event.target === element;
if (correctElement && StringWrapper.equals(KeyEventsPlugin.getEventFullKey(event), fullKey)) {
if (StringWrapper.equals(KeyEventsPlugin.getEventFullKey(event), fullKey)) {
zone.run(() => handler(event));
}
};

View File

@ -38,7 +38,7 @@ import {Instruction, stringifyInstruction} from './instruction';
selector: '[router-link]',
properties: ['routeParams: routerLink'],
host: {
'(^click)': 'onClick()',
'(click)': 'onClick()',
'[attr.href]': 'visibleHref',
'[class.router-link-active]': 'isRouteActive'
}