feat(events): support preventdefault

Fixes 
Closes 
This commit is contained in:
Marc Laval 2015-04-16 18:03:15 +02:00
parent aabe83cf63
commit 883e1c1541
5 changed files with 61 additions and 9 deletions
modules/angular2
docs/core
src
core
annotations
compiler
render/dom/view
test/core/compiler

@ -514,7 +514,8 @@ Where:
* `some-element` Any element which can generate DOM events (or has an angular directive which generates the event).
* `some-event` (escaped with `()` or `bind-`) is the name of the event `some-event`. In this case the
dash-case is converted into camel-case `someEvent`.
* `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.
By default, angular only listens to the element on the event, and ignores events which bubble. To listen to bubbled
events (as in the case of clicking on any child) use the bubble option (`(^event)` or `on-bubble-event`) as shown

@ -391,6 +391,7 @@ export class Directive extends Injectable {
*
* - `event1`: the DOM event that the directive listens to.
* - `statement`: the statement to execute when the event occurs.
* If the evalutation of the statement returns `false`, then `preventDefault`is applied on the DOM event.
*
* To listen to global events, a target must be added to the event name.
* The target can be `window`, `document` or `body`.

@ -131,17 +131,17 @@ export class AppView {
}
// implementation of EventDispatcher#dispatchEvent
dispatchEvent(
elementIndex:number, eventName:string, locals:Map<string, any>
):void {
// returns false if preventDefault must be applied to the DOM event
dispatchEvent(elementIndex:number, eventName:string, locals:Map<string, any>): boolean {
// Most of the time the event will be fired only when the view is in the live document.
// However, in a rare circumstance the view might get dehydrated, in between the event
// queuing up and firing.
var allowDefaultBehavior = true;
if (this.hydrated()) {
var elBinder = this.proto.elementBinders[elementIndex];
if (isBlank(elBinder.hostListeners)) return;
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
var eventMap = elBinder.hostListeners[eventName];
if (isBlank(eventMap)) return;
if (isBlank(eventMap)) return allowDefaultBehavior;
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
var context;
if (directiveIndex === -1) {
@ -149,9 +149,13 @@ export class AppView {
} else {
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
}
expr.eval(context, new Locals(this.locals, locals));
var result = expr.eval(context, new Locals(this.locals, locals));
if (isPresent(result)) {
allowDefaultBehavior = allowDefaultBehavior && result;
}
});
}
return allowDefaultBehavior;
}
}

@ -84,7 +84,8 @@ export class RenderView {
this._eventDispatcher = dispatcher;
}
dispatchEvent(elementIndex, eventName, event) {
dispatchEvent(elementIndex, eventName, event): boolean {
var allowDefaultBehavior = true;
if (isPresent(this._eventDispatcher)) {
var evalLocals = MapWrapper.create();
MapWrapper.set(evalLocals, '$event', event);
@ -92,7 +93,11 @@ export class RenderView {
// out of action expressions
// var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
// this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
allowDefaultBehavior = this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
if (!allowDefaultBehavior) {
event.preventDefault();
}
}
return allowDefaultBehavior;
}
}

@ -591,6 +591,23 @@ export function main() {
});
}));
it('should support preventing default on render events', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
template: '<input type="checkbox" listenerprevent></input><input type="checkbox" listenernoprevent></input>',
directives: [DecoratorListeningDomEventPrevent, DecoratorListeningDomEventNoPrevent]
}));
tb.createView(MyComp, {context: ctx}).then((view) => {
expect(DOM.getChecked(view.rootNodes[0])).toBeFalsy();
expect(DOM.getChecked(view.rootNodes[1])).toBeFalsy();
DOM.dispatchEvent(view.rootNodes[0], DOM.createMouseEvent('click'));
DOM.dispatchEvent(view.rootNodes[1], DOM.createMouseEvent('click'));
expect(DOM.getChecked(view.rootNodes[0])).toBeFalsy();
expect(DOM.getChecked(view.rootNodes[1])).toBeTruthy();
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>',
@ -1162,6 +1179,30 @@ class DecoratorListeningDomEventOther {
}
}
@Decorator({
selector: '[listenerprevent]',
hostListeners: {
'click': 'onEvent($event)'
}
})
class DecoratorListeningDomEventPrevent {
onEvent(event) {
return false;
}
}
@Decorator({
selector: '[listenernoprevent]',
hostListeners: {
'click': 'onEvent($event)'
}
})
class DecoratorListeningDomEventNoPrevent {
onEvent(event) {
return true;
}
}
@Component({
selector: '[id]',
properties: {'id': 'id'}