Skip to content

Commit 883e1c1

Browse files
committed
feat(events): support preventdefault
Fixes angular#1039 Closes angular#1397
1 parent aabe83c commit 883e1c1

File tree

5 files changed

+61
-9
lines changed

5 files changed

+61
-9
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,8 @@ Where:
514514
* `some-element` Any element which can generate DOM events (or has an angular directive which generates the event).
515515
* `some-event` (escaped with `()` or `bind-`) is the name of the event `some-event`. In this case the
516516
dash-case is converted into camel-case `someEvent`.
517-
* `statement` is a valid statement (as defined in section below).
517+
* `statement` is a valid statement (as defined in section below).
518+
If the execution of the statement returns `false`, then `preventDefault`is applied on the DOM event.
518519

519520
By default, angular only listens to the element on the event, and ignores events which bubble. To listen to bubbled
520521
events (as in the case of clicking on any child) use the bubble option (`(^event)` or `on-bubble-event`) as shown

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ export class Directive extends Injectable {
391391
*
392392
* - `event1`: the DOM event that the directive listens to.
393393
* - `statement`: the statement to execute when the event occurs.
394+
* If the evalutation of the statement returns `false`, then `preventDefault`is applied on the DOM event.
394395
*
395396
* To listen to global events, a target must be added to the event name.
396397
* The target can be `window`, `document` or `body`.

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,27 +131,31 @@ export class AppView {
131131
}
132132

133133
// implementation of EventDispatcher#dispatchEvent
134-
dispatchEvent(
135-
elementIndex:number, eventName:string, locals:Map<string, any>
136-
):void {
134+
// returns false if preventDefault must be applied to the DOM event
135+
dispatchEvent(elementIndex:number, eventName:string, locals:Map<string, any>): boolean {
137136
// Most of the time the event will be fired only when the view is in the live document.
138137
// However, in a rare circumstance the view might get dehydrated, in between the event
139138
// queuing up and firing.
139+
var allowDefaultBehavior = true;
140140
if (this.hydrated()) {
141141
var elBinder = this.proto.elementBinders[elementIndex];
142-
if (isBlank(elBinder.hostListeners)) return;
142+
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
143143
var eventMap = elBinder.hostListeners[eventName];
144-
if (isBlank(eventMap)) return;
144+
if (isBlank(eventMap)) return allowDefaultBehavior;
145145
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
146146
var context;
147147
if (directiveIndex === -1) {
148148
context = this.context;
149149
} else {
150150
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
151151
}
152-
expr.eval(context, new Locals(this.locals, locals));
152+
var result = expr.eval(context, new Locals(this.locals, locals));
153+
if (isPresent(result)) {
154+
allowDefaultBehavior = allowDefaultBehavior && result;
155+
}
153156
});
154157
}
158+
return allowDefaultBehavior;
155159
}
156160
}
157161

modules/angular2/src/render/dom/view/view.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,20 @@ export class RenderView {
8484
this._eventDispatcher = dispatcher;
8585
}
8686

87-
dispatchEvent(elementIndex, eventName, event) {
87+
dispatchEvent(elementIndex, eventName, event): boolean {
88+
var allowDefaultBehavior = true;
8889
if (isPresent(this._eventDispatcher)) {
8990
var evalLocals = MapWrapper.create();
9091
MapWrapper.set(evalLocals, '$event', event);
9192
// TODO(tbosch): reenable this when we are parsing element properties
9293
// out of action expressions
9394
// var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
9495
// this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
95-
this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
96+
allowDefaultBehavior = this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
97+
if (!allowDefaultBehavior) {
98+
event.preventDefault();
99+
}
96100
}
101+
return allowDefaultBehavior;
97102
}
98103
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,23 @@ export function main() {
591591
});
592592
}));
593593

594+
it('should support preventing default on render events', inject([TestBed, AsyncTestCompleter], (tb, async) => {
595+
tb.overrideView(MyComp, new View({
596+
template: '<input type="checkbox" listenerprevent></input><input type="checkbox" listenernoprevent></input>',
597+
directives: [DecoratorListeningDomEventPrevent, DecoratorListeningDomEventNoPrevent]
598+
}));
599+
600+
tb.createView(MyComp, {context: ctx}).then((view) => {
601+
expect(DOM.getChecked(view.rootNodes[0])).toBeFalsy();
602+
expect(DOM.getChecked(view.rootNodes[1])).toBeFalsy();
603+
DOM.dispatchEvent(view.rootNodes[0], DOM.createMouseEvent('click'));
604+
DOM.dispatchEvent(view.rootNodes[1], DOM.createMouseEvent('click'));
605+
expect(DOM.getChecked(view.rootNodes[0])).toBeFalsy();
606+
expect(DOM.getChecked(view.rootNodes[1])).toBeTruthy();
607+
async.done();
608+
});
609+
}));
610+
594611
it('should support render global events from multiple directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
595612
tb.overrideView(MyComp, new View({
596613
template: '<div *if="ctxBoolProp" listener listenerother></div>',
@@ -1162,6 +1179,30 @@ class DecoratorListeningDomEventOther {
11621179
}
11631180
}
11641181

1182+
@Decorator({
1183+
selector: '[listenerprevent]',
1184+
hostListeners: {
1185+
'click': 'onEvent($event)'
1186+
}
1187+
})
1188+
class DecoratorListeningDomEventPrevent {
1189+
onEvent(event) {
1190+
return false;
1191+
}
1192+
}
1193+
1194+
@Decorator({
1195+
selector: '[listenernoprevent]',
1196+
hostListeners: {
1197+
'click': 'onEvent($event)'
1198+
}
1199+
})
1200+
class DecoratorListeningDomEventNoPrevent {
1201+
onEvent(event) {
1202+
return true;
1203+
}
1204+
}
1205+
11651206
@Component({
11661207
selector: '[id]',
11671208
properties: {'id': 'id'}

0 commit comments

Comments
 (0)