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 
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
modules

View File

@ -130,33 +130,6 @@ Example:
Example: Example:
`<div on-click="doX()">`
</td>
</tr>
<tr>
<th>Event binding (bubbling)</th>
<td>
`<div (^event)="statement">`
Example:
`<div (^mouseover)="hlite()">`
</td>
<td>
`<div on-bubble-event="statement">`
Example:
`<div on-bubble-mouseover="hlite()">`
</td>
</tr>
<tr>
<th>Declare reference</th>
<td>
`<div #symbol>`
Example:
<pre> <pre>
``` ```
<video #player> <video #player>
@ -515,18 +488,16 @@ Where:
* `statement` is a valid statement (as defined in section below). * `statement` is a valid statement (as defined in section below).
If the execution of the statement returns `false`, then `preventDefault`is applied on the DOM event. If the execution of the statement returns `false`, then `preventDefault`is applied on the DOM event.
By default, angular only listens to the element on the event, and ignores events which bubble. To listen to bubbled Angular listens to bubbled DOM events (as in the case of clicking on any child), as shown below:
events (as in the case of clicking on any child) use the bubble option (`(event)` or `on-bubble-event`) as shown
below.
<table> <table>
<tr> <tr>
<th>Short form</th> <th>Short form</th>
<td>`<some-element (^some-event)="statement">`</td> <td>`<some-element (some-event)="statement">`</td>
</tr> </tr>
<tr> <tr>
<th>Canonical form</th> <th>Canonical form</th>
<td>`<some-element on-bubble-some-event="statement">`</td> <td>`<some-element on-some-event="statement">`</td>
</tr> </tr>
</table> </table>

View File

@ -12,14 +12,13 @@ import {dashCaseToCamelCase} from '../util';
// Group 1 = "bind-" // Group 1 = "bind-"
// Group 2 = "var-" or "#" // Group 2 = "var-" or "#"
// Group 3 = "on-" // Group 3 = "on-"
// Group 4 = "onbubble-" // Group 4 = "bindon-"
// Group 5 = "bindon-" // Group 5 = the identifier after "bind-", "var-/#", or "on-"
// Group 6 = the identifier after "bind-", "var-/#", or "on-" // Group 6 = idenitifer inside [()]
// Group 7 = idenitifer inside [()] // Group 7 = idenitifer inside []
// Group 8 = idenitifer inside [] // Group 8 = identifier inside ()
// Group 9 = identifier inside ()
var BIND_NAME_REGEXP = var BIND_NAME_REGEXP =
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(onbubble-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g; /^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
/** /**
* Parses the property bindings on a single element. * 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); var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
if (isPresent(bindParts)) { if (isPresent(bindParts)) {
if (isPresent(bindParts[1])) { // match: bind-prop if (isPresent(bindParts[1])) { // match: bind-prop
this._bindProperty(bindParts[6], attrValue, current, newAttrs); this._bindProperty(bindParts[5], attrValue, current, newAttrs);
} else if (isPresent( } else if (isPresent(
bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden" bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden"
var identifier = bindParts[6]; var identifier = bindParts[5];
var value = attrValue == '' ? '\$implicit' : attrValue; var value = attrValue == '' ? '\$implicit' : attrValue;
this._bindVariable(identifier, value, current, newAttrs); this._bindVariable(identifier, value, current, newAttrs);
} else if (isPresent(bindParts[3])) { // match: on-event } 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 } else if (isPresent(bindParts[4])) { // match: bindon-prop
this._bindEvent('^' + bindParts[6], attrValue, current, newAttrs); 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._bindProperty(bindParts[6], attrValue, current, newAttrs);
this._bindAssignmentEvent(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._bindProperty(bindParts[7], attrValue, current, newAttrs);
this._bindAssignmentEvent(bindParts[7], attrValue, current, newAttrs);
} else if (isPresent(bindParts[8])) { // match: [expr] } else if (isPresent(bindParts[8])) { // match: (event)
this._bindProperty(bindParts[8], attrValue, current, newAttrs); this._bindEvent(bindParts[8], attrValue, current, newAttrs);
} else if (isPresent(bindParts[9])) { // match: (event)
this._bindEvent(bindParts[9], attrValue, current, newAttrs);
} }
} else { } else {
var expr = this._parser.parseInterpolation(attrValue, current.elementDescription); 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 {DOM} from 'angular2/src/core/dom/dom_adapter';
import {NgZone} from 'angular2/src/core/zone/ng_zone'; import {NgZone} from 'angular2/src/core/zone/ng_zone';
const BUBBLE_SYMBOL = '^';
export class EventManager { export class EventManager {
constructor(public _plugins: EventManagerPlugin[], public _zone: NgZone) { constructor(public _plugins: EventManagerPlugin[], public _zone: NgZone) {
for (var i = 0; i < _plugins.length; i++) { for (var i = 0; i < _plugins.length; i++) {
@ -12,17 +10,13 @@ export class EventManager {
} }
addEventListener(element: HTMLElement, eventName: string, handler: Function) { addEventListener(element: HTMLElement, eventName: string, handler: Function) {
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName); var plugin = this._findPluginFor(eventName);
var plugin = this._findPluginFor(withoutBubbleSymbol); plugin.addEventListener(element, eventName, handler);
plugin.addEventListener(element, withoutBubbleSymbol, handler,
withoutBubbleSymbol != eventName);
} }
addGlobalEventListener(target: string, eventName: string, handler: Function): Function { addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
var withoutBubbleSymbol = this._removeBubbleSymbol(eventName); var plugin = this._findPluginFor(eventName);
var plugin = this._findPluginFor(withoutBubbleSymbol); return plugin.addGlobalEventListener(target, eventName, handler);
return plugin.addGlobalEventListener(target, withoutBubbleSymbol, handler,
withoutBubbleSymbol != eventName);
} }
getZone(): NgZone { return this._zone; } getZone(): NgZone { return this._zone; }
@ -37,28 +31,19 @@ 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 {
manager: EventManager; manager: EventManager;
// We are assuming here that all plugins support bubbled and non-bubbled events.
// That is equivalent to having supporting $event.target // 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; } supports(eventName: string): boolean { return false; }
addEventListener(element: HTMLElement, eventName: string, handler: Function, addEventListener(element: HTMLElement, eventName: string, handler: Function) {
shouldSupportBubble: boolean) {
throw "not implemented"; throw "not implemented";
} }
addGlobalEventListener(element: string, eventName: string, handler: Function, addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
shouldSupportBubble: boolean): Function {
throw "not implemented"; throw "not implemented";
} }
} }
@ -70,39 +55,17 @@ export class DomEventsPlugin extends EventManagerPlugin {
// events. // events.
supports(eventName: string): boolean { return true; } supports(eventName: string): boolean { return true; }
addEventListener(element: HTMLElement, eventName: string, handler: Function, addEventListener(element: HTMLElement, eventName: string, handler: Function) {
shouldSupportBubble: boolean) { var zone = this.manager._zone;
var outsideHandler = var outsideHandler = (event) => zone.run(() => handler(event));
this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone);
this.manager._zone.runOutsideAngular(() => { DOM.on(element, eventName, outsideHandler); }); this.manager._zone.runOutsideAngular(() => { DOM.on(element, eventName, outsideHandler); });
} }
addGlobalEventListener(target: string, eventName: string, handler: Function, addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
shouldSupportBubble: boolean): Function {
var element = DOM.getGlobalEventTarget(target); var element = DOM.getGlobalEventTarget(target);
var outsideHandler = var zone = this.manager._zone;
this._getOutsideHandler(shouldSupportBubble, element, handler, this.manager._zone); var outsideHandler = (event) => zone.run(() => handler(event));
return this.manager._zone.runOutsideAngular( return this.manager._zone.runOutsideAngular(
() => { return DOM.onAndCancel(element, eventName, outsideHandler); }); () => { 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; return true;
} }
addEventListener(Element element, String eventName, Function handler, addEventListener(Element element, String eventName, Function handler) {
bool shouldSupportBubble) {
if (shouldSupportBubble) throw new BaseException(
'Hammer.js plugin does not support bubbling gestures.');
var zone = this.manager.getZone(); var zone = this.manager.getZone();
eventName = eventName.toLowerCase(); eventName = eventName.toLowerCase();

View File

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

View File

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

View File

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

View File

@ -856,7 +856,9 @@ export function main() {
dispatchEvent(tc.nativeElement, 'domEvent'); dispatchEvent(tc.nativeElement, 'domEvent');
expect(listener.eventType).toEqual('domEvent'); expect(listener.eventTypes)
.toEqual(
['domEvent', 'body_domEvent', 'document_domEvent', 'window_domEvent']);
async.done(); async.done();
}); });
@ -874,16 +876,16 @@ export function main() {
var tc = rootTC.componentViewChildren[0]; var tc = rootTC.componentViewChildren[0];
var listener = tc.inject(DirectiveListeningDomEvent); var listener = tc.inject(DirectiveListeningDomEvent);
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent'); dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
expect(listener.eventType).toEqual('window_domEvent'); expect(listener.eventTypes).toEqual(['window_domEvent']);
listener = tc.inject(DirectiveListeningDomEvent); listener.eventTypes = [];
dispatchEvent(DOM.getGlobalEventTarget("document"), 'domEvent'); dispatchEvent(DOM.getGlobalEventTarget("document"), 'domEvent');
expect(listener.eventType).toEqual('document_domEvent'); expect(listener.eventTypes).toEqual(['document_domEvent', 'window_domEvent']);
rootTC.destroy(); rootTC.destroy();
listener = tc.inject(DirectiveListeningDomEvent); listener.eventTypes = [];
dispatchEvent(DOM.getGlobalEventTarget("body"), 'domEvent'); dispatchEvent(DOM.getGlobalEventTarget("body"), 'domEvent');
expect(listener.eventType).toEqual(''); expect(listener.eventTypes).toEqual([]);
async.done(); async.done();
}); });
@ -983,7 +985,7 @@ export function main() {
var listener = tc.inject(DirectiveListeningDomEvent); var listener = tc.inject(DirectiveListeningDomEvent);
var listenerother = tc.inject(DirectiveListeningDomEventOther); var listenerother = tc.inject(DirectiveListeningDomEventOther);
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent'); dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
expect(listener.eventType).toEqual('window_domEvent'); expect(listener.eventTypes).toEqual(['window_domEvent']);
expect(listenerother.eventType).toEqual('other_domEvent'); expect(listenerother.eventType).toEqual('other_domEvent');
expect(globalCounter).toEqual(1); expect(globalCounter).toEqual(1);
@ -1851,12 +1853,11 @@ class DirectiveListeningEvent {
}) })
@Injectable() @Injectable()
class DirectiveListeningDomEvent { class DirectiveListeningDomEvent {
eventType: string; eventTypes: string[] = [];
constructor() { this.eventType = ''; } onEvent(eventType: string) { this.eventTypes.push(eventType); }
onEvent(eventType: string) { this.eventType = eventType; } onWindowEvent(eventType: string) { this.eventTypes.push("window_" + eventType); }
onWindowEvent(eventType: string) { this.eventType = "window_" + eventType; } onDocumentEvent(eventType: string) { this.eventTypes.push("document_" + eventType); }
onDocumentEvent(eventType: string) { this.eventType = "document_" + eventType; } onBodyEvent(eventType: string) { this.eventTypes.push("body_" + eventType); }
onBodyEvent(eventType: string) { this.eventType = "body_" + eventType; }
} }
var globalCounter = 0; var globalCounter = 0;

View File

@ -182,20 +182,6 @@ export function main() {
expect(eventBinding.fullName).toEqual('click'); expect(eventBinding.fullName).toEqual('click');
}); });
it('should detect onbubble- syntax', () => {
var results = process(el('<div onbubble-click="b()"></div>'));
var eventBinding = results[0].eventBindings[0];
expect(eventBinding.source.source).toEqual('b()');
expect(eventBinding.fullName).toEqual('^click');
});
it('should detect onbubble- syntax with data- prefix', () => {
var results = process(el('<div data-onbubble-click="b()"></div>'));
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>'));
var eventBinding = results[0].eventBindings[0]; var eventBinding = results[0].eventBindings[0];

View File

@ -31,16 +31,7 @@ export function main() {
var plugin = new FakeEventManagerPlugin(['click']); var plugin = new FakeEventManagerPlugin(['click']);
var manager = new EventManager([plugin, domEventPlugin], new FakeNgZone()); var manager = new EventManager([plugin, domEventPlugin], new FakeNgZone());
manager.addEventListener(element, 'click', handler); manager.addEventListener(element, 'click', handler);
expect(plugin._nonBubbleEventHandlers.get('click')).toBe(handler); expect(plugin._eventHandler.get('click')).toBe(handler);
});
it('should delegate bubbling events to plugins', () => {
var element = el('<div></div>');
var handler = (e) => e;
var plugin = new FakeEventManagerPlugin(['click']);
var manager = new EventManager([plugin, domEventPlugin], new FakeNgZone());
manager.addEventListener(element, '^click', handler);
expect(plugin._bubbleEventHandlers.get('click')).toBe(handler);
}); });
it('should delegate event bindings to the first plugin supporting the event', () => { it('should delegate event bindings to the first plugin supporting the event', () => {
@ -52,10 +43,10 @@ export function main() {
var manager = new EventManager([plugin1, plugin2], new FakeNgZone()); var manager = new EventManager([plugin1, plugin2], new FakeNgZone());
manager.addEventListener(element, 'click', clickHandler); manager.addEventListener(element, 'click', clickHandler);
manager.addEventListener(element, 'dblclick', dblClickHandler); manager.addEventListener(element, 'dblclick', dblClickHandler);
expect(plugin1._nonBubbleEventHandlers.has('click')).toBe(false); expect(plugin1._eventHandler.has('click')).toBe(false);
expect(plugin2._nonBubbleEventHandlers.get('click')).toBe(clickHandler); expect(plugin2._eventHandler.get('click')).toBe(clickHandler);
expect(plugin2._nonBubbleEventHandlers.has('dblclick')).toBe(false); expect(plugin2._eventHandler.has('dblclick')).toBe(false);
expect(plugin1._nonBubbleEventHandlers.get('dblclick')).toBe(dblClickHandler); expect(plugin1._eventHandler.get('dblclick')).toBe(dblClickHandler);
}); });
it('should throw when no plugin can handle the event', () => { it('should throw when no plugin can handle the event', () => {
@ -66,20 +57,7 @@ export function main() {
.toThrowError('No event manager plugin found for event click'); .toThrowError('No event manager plugin found for event click');
}); });
it('by default events are only caught on same element', () => { it('events are caught when fired from a child', () => {
var element = el('<div><div></div></div>');
var child = DOM.firstChild(element);
var dispatchedEvent = DOM.createMouseEvent('click');
var receivedEvent = null;
var handler = (e) => { receivedEvent = e; };
var manager = new EventManager([domEventPlugin], new FakeNgZone());
manager.addEventListener(element, 'click', handler);
DOM.dispatchEvent(child, dispatchedEvent);
expect(receivedEvent).toBe(null);
});
it('bubbled events are caught when fired from a child', () => {
var element = el('<div><div></div></div>'); var element = el('<div><div></div></div>');
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755 // Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
DOM.appendChild(DOM.defaultDoc().body, element); DOM.appendChild(DOM.defaultDoc().body, element);
@ -89,13 +67,13 @@ export function main() {
var receivedEvent = null; var receivedEvent = null;
var handler = (e) => { receivedEvent = e; }; var handler = (e) => { receivedEvent = e; };
var manager = new EventManager([domEventPlugin], new FakeNgZone()); var manager = new EventManager([domEventPlugin], new FakeNgZone());
manager.addEventListener(element, '^click', handler); manager.addEventListener(element, 'click', handler);
DOM.dispatchEvent(child, dispatchedEvent); DOM.dispatchEvent(child, dispatchedEvent);
expect(receivedEvent).toBe(dispatchedEvent); expect(receivedEvent).toBe(dispatchedEvent);
}); });
it('should add and remove global event listeners with correct bubbling', () => { it('should add and remove global event listeners', () => {
var element = el('<div><div></div></div>'); var element = el('<div><div></div></div>');
DOM.appendChild(DOM.defaultDoc().body, element); DOM.appendChild(DOM.defaultDoc().body, element);
var dispatchedEvent = DOM.createMouseEvent('click'); var dispatchedEvent = DOM.createMouseEvent('click');
@ -103,7 +81,7 @@ export function main() {
var handler = (e) => { receivedEvent = e; }; var handler = (e) => { receivedEvent = e; };
var manager = new EventManager([domEventPlugin], new FakeNgZone()); var manager = new EventManager([domEventPlugin], new FakeNgZone());
var remover = manager.addGlobalEventListener("document", '^click', handler); var remover = manager.addGlobalEventListener("document", 'click', handler);
DOM.dispatchEvent(element, dispatchedEvent); DOM.dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(dispatchedEvent); expect(receivedEvent).toBe(dispatchedEvent);
@ -111,37 +89,19 @@ export function main() {
remover(); remover();
DOM.dispatchEvent(element, dispatchedEvent); DOM.dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(null); expect(receivedEvent).toBe(null);
remover = manager.addGlobalEventListener("document", 'click', handler);
DOM.dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(null);
}); });
}); });
} }
class FakeEventManagerPlugin extends EventManagerPlugin { class FakeEventManagerPlugin extends EventManagerPlugin {
_supports: string[]; _eventHandler: Map<string, Function> = new Map();
_nonBubbleEventHandlers: Map<string, Function>; constructor(public _supports: string[]) { super(); }
_bubbleEventHandlers: Map<string, Function>;
constructor(supports: string[]) {
super();
this._supports = supports;
this._nonBubbleEventHandlers = new Map();
this._bubbleEventHandlers = new Map();
}
supports(eventName: string): boolean { return ListWrapper.contains(this._supports, eventName); } supports(eventName: string): boolean { return ListWrapper.contains(this._supports, eventName); }
addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) { addEventListener(element, eventName: string, handler: Function) {
if (shouldSupportBubble) { this._eventHandler.set(eventName, handler);
this._bubbleEventHandlers.set(eventName, handler); return () => { MapWrapper.delete(this._eventHandler, eventName) };
} else {
this._nonBubbleEventHandlers.set(eventName, handler);
}
return () => {
MapWrapper.delete(
shouldSupportBubble ? this._bubbleEventHandlers : this._nonBubbleEventHandlers, eventName)
};
} }
} }

View File

@ -65,7 +65,7 @@ export function main() {
.then((testComponent) => { .then((testComponent) => {
testComponent.detectChanges(); testComponent.detectChanges();
// TODO: shouldn't this be just 'click' rather than '^click'? // TODO: shouldn't this be just 'click' rather than '^click'?
testComponent.query(By.css('a')).triggerEventHandler('^click', null); testComponent.query(By.css('a')).triggerEventHandler('click', null);
expect(router.spy('navigateInstruction')).toHaveBeenCalledWith(dummyInstruction); expect(router.spy('navigateInstruction')).toHaveBeenCalledWith(dummyInstruction);
async.done(); async.done();
}); });

View File

@ -9,7 +9,7 @@ import {isPresent} from 'angular2/src/core/facade/lang';
@Component({ @Component({
selector: '[md-button]:not(a), [md-fab]:not(a), [md-raised-button]:not(a)', selector: '[md-button]:not(a), [md-fab]:not(a), [md-raised-button]:not(a)',
host: { host: {
'(^mousedown)': 'onMousedown()', '(mousedown)': 'onMousedown()',
'(focus)': 'onFocus()', '(focus)': 'onFocus()',
'(blur)': 'onBlur()', '(blur)': 'onBlur()',
'[class.md-button-focus]': 'isKeyboardFocused', '[class.md-button-focus]': 'isKeyboardFocused',
@ -50,8 +50,8 @@ export class MdButton {
properties: ['disabled'], properties: ['disabled'],
lifecycle: [LifecycleEvent.OnChanges], lifecycle: [LifecycleEvent.OnChanges],
host: { host: {
'(^click)': 'onClick($event)', '(click)': 'onClick($event)',
'(^mousedown)': 'onMousedown()', '(mousedown)': 'onMousedown()',
'(focus)': 'onFocus()', '(focus)': 'onFocus()',
'(blur)': 'onBlur()', '(blur)': 'onBlur()',
'[tabIndex]': 'tabIndex', '[tabIndex]': 'tabIndex',

View File

@ -1,6 +1,6 @@
<style>@import "package:angular2_material/src/components/checkbox/checkbox.css";</style> <style>@import "package:angular2_material/src/components/checkbox/checkbox.css";</style>
<div (^click)="toggle($event)"> <div (click)="toggle($event)">
<div class="md-checkbox-container"> <div class="md-checkbox-container">
<div class="md-checkbox-icon"></div> <div class="md-checkbox-icon"></div>
</div> </div>

View File

@ -206,7 +206,7 @@ export class MdDialogConfig {
host: { host: {
'class': 'md-dialog', 'class': 'md-dialog',
'tabindex': '0', 'tabindex': '0',
'(body:^keydown)': 'documentKeypress($event)', '(body:keydown)': 'documentKeypress($event)',
}, },
}) })
@View({ @View({

View File

@ -4,7 +4,7 @@
<label role="radio" class="md-radio-root" <label role="radio" class="md-radio-root"
[class.md-radio-checked]="checked" [class.md-radio-checked]="checked"
(^click)="select($event)"> (click)="select($event)">
<!-- The actual `radio` part of the control. --> <!-- The actual `radio` part of the control. -->
<div class="md-radio-container"> <div class="md-radio-container">
<div class="md-radio-off"></div> <div class="md-radio-off"></div>

View File

@ -41,7 +41,7 @@ var _uniqueIdCounter: number = 0;
'[attr.aria-disabled]': 'disabled', '[attr.aria-disabled]': 'disabled',
'[attr.aria-activedescendant]': 'activedescendant', '[attr.aria-activedescendant]': 'activedescendant',
// TODO(jelbourn): Remove ^ when event retargeting is fixed. // TODO(jelbourn): Remove ^ when event retargeting is fixed.
'(^keydown)': 'onKeydown($event)', '(keydown)': 'onKeydown($event)',
'[tabindex]': 'tabindex', '[tabindex]': 'tabindex',
} }
}) })

View File

@ -1,6 +1,6 @@
<style>@import "package:angular2_material/src/components/switcher/switch.css";</style> <style>@import "package:angular2_material/src/components/switcher/switch.css";</style>
<div (^click)="toggle($event)"> <div (click)="toggle($event)">
<div class="md-switch-container"> <div class="md-switch-container">
<div class="md-switch-bar"></div> <div class="md-switch-bar"></div>
<div class="md-switch-thumb-container"> <div class="md-switch-thumb-container">

View File

@ -9,10 +9,12 @@ describe('md-dialog', function() {
it('should open a dialog', function() { it('should open a dialog', function() {
var openButton = element(by.id('open')); var openButton = element(by.id('open'));
openButton.click(); openButton.click();
browser.sleep(500);
expect(element(by.css('.md-dialog')).isPresent()).toEqual(true);
var dialog = element(by.css('.md-dialog')); var dialog = element(by.css('.md-dialog'));
browser.sleep(500);
expect(dialog.isPresent()).toEqual(true);
dialog.sendKeys(protractor.Key.ESCAPE); dialog.sendKeys(protractor.Key.ESCAPE);
expect(element(by.css('.md-dialog')).isPresent()).toEqual(false); expect(element(by.css('.md-dialog')).isPresent()).toEqual(false);

View File

@ -34,7 +34,7 @@
</p> </p>
<section> <section>
<form (^submit)="submit('form submit')"> <form (submit)="submit('form submit')">
<button md-button>SUBMIT</button> <button md-button>SUBMIT</button>
<button>Native button</button> <button>Native button</button>
</form> </form>
@ -45,32 +45,32 @@
<section> <section>
<span class="label">Regular button</span> <span class="label">Regular button</span>
<button md-button (^click)="click('button')">BUTTON</button> <button md-button (click)="click('button')">BUTTON</button>
<button md-button class="md-primary" (^click)="click('primary')">PRIMARY</button> <button md-button class="md-primary" (click)="click('primary')">PRIMARY</button>
<button md-button disabled="disabled" (^click)="click('disabled')">DISABLED</button> <button md-button disabled="disabled" (click)="click('disabled')">DISABLED</button>
<button md-button class="md-accent" (^click)="click('accent')">ACCENT</button> <button md-button class="md-accent" (click)="click('accent')">ACCENT</button>
<button md-button class="md-warn" (^click)="click('warn')">WARN</button> <button md-button class="md-warn" (click)="click('warn')">WARN</button>
<button md-button class="custom" (^click)="click('custom')">CUSTOM</button> <button md-button class="custom" (click)="click('custom')">CUSTOM</button>
</section> </section>
<section> <section>
<span class="label">Raised button</span> <span class="label">Raised button</span>
<button md-raised-button (^click)="click('raised')">BUTTON</button> <button md-raised-button (click)="click('raised')">BUTTON</button>
<button md-raised-button class="md-primary" (^click)="click('raised primary')">PRIMARY</button> <button md-raised-button class="md-primary" (click)="click('raised primary')">PRIMARY</button>
<button md-raised-button disabled="disabled" (^click)="click('raised disabled')">DISABLED</button> <button md-raised-button disabled="disabled" (click)="click('raised disabled')">DISABLED</button>
<button md-raised-button class="md-accent" (^click)="click('raised accent')">ACCENT</button> <button md-raised-button class="md-accent" (click)="click('raised accent')">ACCENT</button>
<button md-raised-button class="md-warn" (^click)="click('raised warn')">WARN</button> <button md-raised-button class="md-warn" (click)="click('raised warn')">WARN</button>
<button md-raised-button class="custom" (^click)="click('custom raised')">CUSTOM</button> <button md-raised-button class="custom" (click)="click('custom raised')">CUSTOM</button>
</section> </section>
<section> <section>
<span class="label">Fab button</span> <span class="label">Fab button</span>
<button md-fab (^click)="click('fab')">BTN</button> <button md-fab (click)="click('fab')">BTN</button>
<button md-fab class="md-primary" (^click)="click('fab primary')">PRMY</button> <button md-fab class="md-primary" (click)="click('fab primary')">PRMY</button>
<button md-fab disabled="disabled" (^click)="click('fab disabled')">DIS</button> <button md-fab disabled="disabled" (click)="click('fab disabled')">DIS</button>
<button md-fab class="md-accent" (^click)="click('fab accent')">ACC</button> <button md-fab class="md-accent" (click)="click('fab accent')">ACC</button>
<button md-fab class="md-warn" (^click)="click('fab warn')">WRN</button> <button md-fab class="md-warn" (click)="click('fab warn')">WRN</button>
<button md-fab class="custom" (^click)="click('custom fab')">CSTM</button> <button md-fab class="custom" (click)="click('custom fab')">CSTM</button>
</section> </section>
<section> <section>
<span class="label">Anchor / hyperlink</span> <span class="label">Anchor / hyperlink</span>
@ -81,8 +81,8 @@
<section dir="rtl"> <section dir="rtl">
<span class="label" dir="ltr">Right-to-left</span> <span class="label" dir="ltr">Right-to-left</span>
<button md-button (^click)="click('Hebrew button')">לחצן</button> <button md-button (click)="click('Hebrew button')">לחצן</button>
<button md-raised-button (^click)="click('Hebrew raised button')">העלה</button> <button md-raised-button (click)="click('Hebrew raised button')">העלה</button>
<a md-button href="http://translate.google.com">עוגן</a> <a md-button href="http://translate.google.com">עוגן</a>
</section> </section>

View File

@ -1,9 +1,9 @@
<div md-theme="default"> <div md-theme="default">
<h2>Checkbox demo</h2> <h2>Checkbox demo</h2>
<md-checkbox (^click)="increment()">Normal checkbox</md-checkbox> <md-checkbox (click)="increment()">Normal checkbox</md-checkbox>
<md-checkbox class="md-primary" (^click)="increment()">Primary checkbox</md-checkbox> <md-checkbox class="md-primary" (click)="increment()">Primary checkbox</md-checkbox>
<md-checkbox disabled (^click)="increment()">Disabled checkbox</md-checkbox> <md-checkbox disabled (click)="increment()">Disabled checkbox</md-checkbox>
<p>Toggle count: {{toggleCount}}</p> <p>Toggle count: {{toggleCount}}</p>
</div> </div>

View File

@ -21,10 +21,10 @@
<hr> <hr>
<h3>Standalone</h3> <h3>Standalone</h3>
<md-radio-button name="element" (^click)="onIndividualClick()">Earth</md-radio-button> <md-radio-button name="element" (click)="onIndividualClick()">Earth</md-radio-button>
<md-radio-button name="element" (^click)="onIndividualClick()">Fire</md-radio-button> <md-radio-button name="element" (click)="onIndividualClick()">Fire</md-radio-button>
<md-radio-button name="element" (^click)="onIndividualClick()" disabled>Wind (disabled)</md-radio-button> <md-radio-button name="element" (click)="onIndividualClick()" disabled>Wind (disabled)</md-radio-button>
<md-radio-button name="element" (^click)="onIndividualClick()">Heart</md-radio-button> <md-radio-button name="element" (click)="onIndividualClick()">Heart</md-radio-button>
<p>individual radio value change count: {{individualValueChanges}}</p> <p>individual radio value change count: {{individualValueChanges}}</p>

View File

@ -1,9 +1,9 @@
<div md-theme="default"> <div md-theme="default">
<h2>NgSwitch demo</h2> <h2>NgSwitch demo</h2>
<md-switch (^click)="increment()">Normal switch</md-switch> <md-switch (click)="increment()">Normal switch</md-switch>
<md-switch class="md-primary" (^click)="increment()">Primary switch</md-switch> <md-switch class="md-primary" (click)="increment()">Primary switch</md-switch>
<md-switch disabled (^click)="increment()">Disabled switch</md-switch> <md-switch disabled (click)="increment()">Disabled switch</md-switch>
<p>Toggle count: {{toggleCount}}</p> <p>Toggle count: {{toggleCount}}</p>
</div> </div>