Skip to content

Commit fc5b7ed

Browse files
committed
feat(compiler): support on- and []
1 parent c6846f1 commit fc5b7ed

File tree

9 files changed

+97
-12
lines changed

9 files changed

+97
-12
lines changed

modules/core/src/compiler/element_binder.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {ProtoElementInjector} from './element_injector';
22
import {FIELD} from 'facade/lang';
3+
import {MapWrapper} from 'facade/collection';
34
import {AnnotatedType} from './annotated_type';
45
// Comment out as dartanalyzer does not look into @FIELD
56
// import {List} from 'facade/collection';
@@ -11,12 +12,15 @@ export class ElementBinder {
1112
@FIELD('final templateDirective:AnnotatedType')
1213
@FIELD('final textNodeIndices:List<int>')
1314
@FIELD('hasElementPropertyBindings:bool')
14-
constructor(protoElementInjector: ProtoElementInjector, componentDirective:AnnotatedType, templateDirective:AnnotatedType) {
15+
constructor(
16+
protoElementInjector: ProtoElementInjector, componentDirective:AnnotatedType, templateDirective:AnnotatedType) {
1517
this.protoElementInjector = protoElementInjector;
1618
this.componentDirective = componentDirective;
1719
this.templateDirective = templateDirective;
20+
// updated later when events are bound
21+
this.events = null;
1822
// updated later when text nodes are bound
19-
this.textNodeIndices = [];
23+
this.textNodeIndices = null;
2024
// updated later when element properties are bound
2125
this.hasElementPropertyBindings = false;
2226
// updated later, so we are able to resolve cycles

modules/core/src/compiler/pipeline/compile_element.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class CompileElement {
2020
this._classList = null;
2121
this.textNodeBindings = null;
2222
this.propertyBindings = null;
23+
this.eventBindings = null;
2324
this.variableBindings = null;
2425
this.decoratorDirectives = null;
2526
this.templateDirective = null;
@@ -84,6 +85,13 @@ export class CompileElement {
8485
MapWrapper.set(this.variableBindings, contextName, templateName);
8586
}
8687

88+
addEventBinding(eventName:string, expression:ASTWithSource) {
89+
if (isBlank(this.eventBindings)) {
90+
this.eventBindings = MapWrapper.create();
91+
}
92+
MapWrapper.set(this.eventBindings, eventName, expression);
93+
}
94+
8795
addDirective(directive:AnnotatedType) {
8896
var annotation = directive.annotation;
8997
if (annotation instanceof Decorator) {

modules/core/src/compiler/pipeline/element_binder_builder.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {CompileControl} from './compile_control';
3333
* - CompileElement#inheritedProtoElementInjector
3434
* - CompileElement#textNodeBindings
3535
* - CompileElement#propertyBindings
36+
* - CompileElement#eventBindings
3637
* - CompileElement#decoratorDirectives
3738
* - CompileElement#componentDirective
3839
* - CompileElement#templateDirective
@@ -60,6 +61,9 @@ export class ElementBinderBuilder extends CompileStep {
6061
if (isPresent(current.propertyBindings)) {
6162
this._bindElementProperties(protoView, current);
6263
}
64+
if (isPresent(current.eventBindings)) {
65+
this._bindEvents(protoView, current);
66+
}
6367
this._bindDirectiveProperties(this._collectDirectives(current), current);
6468
} else if (isPresent(parent)) {
6569
elementBinder = parent.inheritedElementBinder;
@@ -79,6 +83,12 @@ export class ElementBinderBuilder extends CompileStep {
7983
});
8084
}
8185

86+
_bindEvents(protoView, compileElement) {
87+
MapWrapper.forEach(compileElement.eventBindings, (expression, eventName) => {
88+
protoView.bindEvent(eventName, expression.ast);
89+
});
90+
}
91+
8292
_collectDirectives(compileElement) {
8393
var directives;
8494
if (isPresent(compileElement.decoratorDirectives)) {

modules/core/src/compiler/pipeline/element_binding_marker.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const NG_BINDING_CLASS = 'ng-binding';
1919
* - CompileElement#textNodeBindings
2020
* - CompileElement#propertyBindings
2121
* - CompileElement#variableBindings
22+
* - CompileElement#eventBindings
2223
* - CompileElement#decoratorDirectives
2324
* - CompileElement#componentDirective
2425
* - CompileElement#templateDirective
@@ -29,6 +30,7 @@ export class ElementBindingMarker extends CompileStep {
2930
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
3031
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
3132
(isPresent(current.variableBindings) && MapWrapper.size(current.variableBindings)>0) ||
33+
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>0) ||
3234
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
3335
isPresent(current.templateDirective) ||
3436
isPresent(current.componentDirective);

modules/core/src/compiler/pipeline/property_binding_parser.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import {CompileControl} from './compile_control';
1212
import {interpolationToExpression} from './text_interpolation_parser';
1313

1414
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
15-
var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(let))-(.+))|\\[([^\\]]+)\\]');
15+
var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(let)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\]]+)\\)');
1616

1717
/**
1818
* Parses the property bindings on a single element.
1919
*
2020
* Fills:
2121
* - CompileElement#propertyBindings
22+
* - CompileElement#eventBindings
23+
* - CompileElement#variableBindings
2224
*/
2325
export class PropertyBindingParser extends CompileStep {
2426
constructor(parser:Parser) {
@@ -32,18 +34,24 @@ export class PropertyBindingParser extends CompileStep {
3234
if (isPresent(bindParts)) {
3335
if (isPresent(bindParts[1])) {
3436
// match: bind-prop
35-
current.addPropertyBinding(bindParts[3], this._parser.parseBinding(attrValue));
37+
current.addPropertyBinding(bindParts[4], this._parser.parseBinding(attrValue));
3638
} else if (isPresent(bindParts[2])) {
3739
// match: let-prop
3840
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
3941
// only be present on <template> elements any more!
4042
if (!(current.element instanceof TemplateElement)) {
4143
throw new BaseException('let-* is only allowed on <template> elements!');
4244
}
43-
current.addVariableBinding(bindParts[3], attrValue);
44-
} else if (isPresent(bindParts[4])) {
45+
current.addVariableBinding(bindParts[4], attrValue);
46+
} else if (isPresent(bindParts[3])) {
47+
// match: on-prop
48+
current.addEventBinding(bindParts[4], this._parser.parseAction(attrValue));
49+
} else if (isPresent(bindParts[5])) {
4550
// match: [prop]
46-
current.addPropertyBinding(bindParts[4], this._parser.parseBinding(attrValue));
51+
current.addPropertyBinding(bindParts[5], this._parser.parseBinding(attrValue));
52+
} else if (isPresent(bindParts[6])) {
53+
// match: (prop)
54+
current.addEventBinding(bindParts[6], this._parser.parseBinding(attrValue));
4755
}
4856
} else {
4957
var expression = interpolationToExpression(attrValue);

modules/core/src/compiler/view.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ export class ProtoView {
141141
*/
142142
bindTextNode(indexInParent:int, expression:AST) {
143143
var elBinder = this.elementBinders[this.elementBinders.length-1];
144+
if (isBlank(elBinder.textNodeIndices)) {
145+
elBinder.textNodeIndices = ListWrapper.create();
146+
}
144147
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
145148
this.protoWatchGroup.watch(expression, this.textNodesWithBindingCount++);
146149
}
@@ -162,6 +165,17 @@ export class ProtoView {
162165
);
163166
}
164167

168+
/**
169+
* Adds an event binding for the last created ElementBinder via bindElement
170+
*/
171+
bindEvent(eventName:string, expression:AST) {
172+
var elBinder = this.elementBinders[this.elementBinders.length-1];
173+
if (isBlank(elBinder.events)) {
174+
elBinder.events = MapWrapper.create();
175+
}
176+
MapWrapper.set(elBinder.events, eventName, expression);
177+
}
178+
165179
/**
166180
* Adds a directive property binding for the last created ElementBinder via bindElement
167181
*/
@@ -228,9 +242,11 @@ export class ProtoView {
228242
}
229243

230244
static _collectTextNodes(allTextNodes, element, indices) {
231-
var childNodes = DOM.templateAwareRoot(element).childNodes;
232-
for (var i = 0; i < indices.length; ++i) {
233-
ListWrapper.push(allTextNodes, childNodes[indices[i]]);
245+
if (isPresent(indices)) {
246+
var childNodes = DOM.templateAwareRoot(element).childNodes;
247+
for (var i = 0; i < indices.length; ++i) {
248+
ListWrapper.push(allTextNodes, childNodes[indices[i]]);
249+
}
234250
}
235251
}
236252

modules/core/test/compiler/pipeline/element_binder_builder_spec.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function main() {
2727
describe('ElementBinderBuilder', () => {
2828
var evalContext, view, changeDetector;
2929

30-
function createPipeline({textNodeBindings, propertyBindings, directives, protoElementInjector
30+
function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector
3131
}={}) {
3232
var reflector = new Reflector();
3333
var closureMap = new ClosureMap();
@@ -55,6 +55,12 @@ export function main() {
5555
}
5656
hasBinding = true;
5757
}
58+
if (isPresent(current.element.getAttribute('event-binding'))) {
59+
MapWrapper.forEach(eventBindings, (v,k) => {
60+
current.addEventBinding(k, parser.parseAction(v));
61+
});
62+
hasBinding = true;
63+
}
5864
if (isPresent(protoElementInjector)) {
5965
current.inheritedProtoElementInjector = protoElementInjector;
6066
}
@@ -172,6 +178,18 @@ export function main() {
172178
expect(DOM.getProperty(view.nodes[0], 'elprop2')).toEqual('b');
173179
});
174180

181+
it('should bind events', () => {
182+
var eventBindings = MapWrapper.createFromStringMap({
183+
'event1': '1+1'
184+
});
185+
var pipeline = createPipeline({eventBindings: eventBindings});
186+
var results = pipeline.process(createElement('<div viewroot event-binding></div>'));
187+
var pv = results[0].inheritedProtoView;
188+
189+
var ast = MapWrapper.get(pv.elementBinders[0].events, 'event1');
190+
expect(ast.eval(null)).toBe(2);
191+
});
192+
175193
it('should bind directive properties', () => {
176194
var propertyBindings = MapWrapper.createFromStringMap({
177195
'boundprop1': 'prop1',

modules/core/test/compiler/pipeline/element_binding_marker_spec.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {Component} from 'core/annotations/component';
1616
export function main() {
1717
describe('ElementBindingMarker', () => {
1818

19-
function createPipeline({textNodeBindings, propertyBindings, variableBindings, directives}={}) {
19+
function createPipeline({textNodeBindings, propertyBindings, variableBindings, eventBindings, directives}={}) {
2020
var reflector = new Reflector();
2121
return new CompilePipeline([
2222
new MockStep((parent, current, control) => {
@@ -29,6 +29,9 @@ export function main() {
2929
if (isPresent(variableBindings)) {
3030
current.variableBindings = variableBindings;
3131
}
32+
if (isPresent(eventBindings)) {
33+
current.eventBindings = eventBindings;
34+
}
3235
if (isPresent(directives)) {
3336
for (var i=0; i<directives.length; i++) {
3437
current.addDirective(reflector.annotatedType(directives[i]));
@@ -62,6 +65,12 @@ export function main() {
6265
assertBinding(results[0], true);
6366
});
6467

68+
it('should mark elements with event bindings', () => {
69+
var eventBindings = MapWrapper.createFromStringMap({'click': 'expr'});
70+
var results = createPipeline({eventBindings: eventBindings}).process(createElement('<div></div>'));
71+
assertBinding(results[0], true);
72+
});
73+
6574
it('should mark elements with decorator directives', () => {
6675
var results = createPipeline({
6776
directives: [SomeDecoratorDirective]

modules/core/test/compiler/pipeline/property_binding_parser_spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ export function main() {
4141
createPipeline().process(createElement('<div let-a="b"></div>'))
4242
}).toThrowError('let-* is only allowed on <template> elements!');
4343
});
44+
45+
it('should detect () syntax', () => {
46+
var results = createPipeline().process(createElement('<div (click)="b()"></div>'));
47+
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('b()');
48+
});
49+
50+
it('should detect on- syntax', () => {
51+
var results = createPipeline().process(createElement('<div on-click="b()"></div>'));
52+
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('b()');
53+
});
4454
});
4555
}
4656

0 commit comments

Comments
 (0)