Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 4d17c11

Browse files
committed
feat(compiler): bind- syntax
Adds support for bind- syntax which allows directive users to give an expression for any mapping. For #650 Closes #957
1 parent ce2e45e commit 4d17c11

File tree

2 files changed

+102
-37
lines changed

2 files changed

+102
-37
lines changed

lib/core_dom/element_binder.dart

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,72 @@ class ElementBinder {
8383
bool get hasDirectivesOrEvents =>
8484
_usableDirectiveRefs.isNotEmpty || onEvents.isNotEmpty;
8585

86-
_createAttrMappings(controller, scope, DirectiveRef ref, nodeAttrs, formatters, tasks) {
87-
ref.mappings.forEach((MappingParts p) {
86+
_bindTwoWay(tasks, expression, scope, dstPathFn, controller, formatters, dstExpression) {
87+
var taskId = tasks.registerTask();
88+
Expression expressionFn = _parser(expression);
89+
90+
var viewOutbound = false;
91+
var viewInbound = false;
92+
scope.watch(expression, (inboundValue, _) {
93+
if (!viewInbound) {
94+
viewOutbound = true;
95+
scope.rootScope.runAsync(() => viewOutbound = false);
96+
var value = dstPathFn.assign(controller, inboundValue);
97+
tasks.completeTask(taskId);
98+
return value;
99+
}
100+
}, formatters: formatters);
101+
if (expressionFn.isAssignable) {
102+
scope.watch(dstExpression, (outboundValue, _) {
103+
if (!viewOutbound) {
104+
viewInbound = true;
105+
scope.rootScope.runAsync(() => viewInbound = false);
106+
expressionFn.assign(scope.context, outboundValue);
107+
tasks.completeTask(taskId);
108+
}
109+
}, context: controller, formatters: formatters);
110+
}
111+
}
112+
113+
_bindOneWay(tasks, expression, scope, dstPathFn, controller, formatters) {
114+
var taskId = tasks.registerTask();
115+
116+
Expression attrExprFn = _parser(expression);
117+
scope.watch(expression, (v, _) {
118+
dstPathFn.assign(controller, v);
119+
tasks.completeTask(taskId);
120+
}, formatters: formatters);
121+
}
122+
123+
_bindCallback(dstPathFn, controller, expression, scope) {
124+
dstPathFn.assign(controller, _parser(expression).bind(scope.context, ScopeLocals.wrapper));
125+
}
126+
127+
_createAttrMappings(controller, scope, List<MappingParts> mappings, nodeAttrs, formatters, tasks) {
128+
mappings.forEach((MappingParts p) {
88129
var attrName = p.attrName;
89130
var dstExpression = p.dstExpression;
90-
if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);
91131

92132
Expression dstPathFn = _parser(dstExpression);
93133
if (!dstPathFn.isAssignable) {
94134
throw "Expression '$dstExpression' is not assignable in mapping '${p.originalValue}' "
95135
"for attribute '$attrName'.";
96136
}
97137

138+
// Check if there is a bind attribute for this mapping.
139+
var bindAttr = bindAttrs["bind-${p.attrName}"];
140+
if (bindAttr != null) {
141+
if (p.mode == '<=>') {
142+
_bindTwoWay(tasks, bindAttr, scope, dstPathFn,
143+
controller, formatters, dstExpression);
144+
} else if(p.mode == '&') {
145+
_bindCallback(dstPathFn, controller, bindAttr, scope);
146+
} else {
147+
_bindOneWay(tasks, bindAttr, scope, dstPathFn, controller, formatters);
148+
}
149+
return;
150+
}
151+
98152
switch (p.mode) {
99153
case '@': // string
100154
var taskId = tasks.registerTask();
@@ -107,41 +161,14 @@ class ElementBinder {
107161
case '<=>': // two-way
108162
if (nodeAttrs[attrName] == null) return;
109163

110-
var taskId = tasks.registerTask();
111-
String expression = nodeAttrs[attrName];
112-
Expression expressionFn = _parser(expression);
113-
var viewOutbound = false;
114-
var viewInbound = false;
115-
scope.watch(expression, (inboundValue, _) {
116-
if (!viewInbound) {
117-
viewOutbound = true;
118-
scope.rootScope.runAsync(() => viewOutbound = false);
119-
var value = dstPathFn.assign(controller, inboundValue);
120-
tasks.completeTask(taskId);
121-
return value;
122-
}
123-
}, formatters: formatters);
124-
if (expressionFn.isAssignable) {
125-
scope.watch(dstExpression, (outboundValue, _) {
126-
if (!viewOutbound) {
127-
viewInbound = true;
128-
scope.rootScope.runAsync(() => viewInbound = false);
129-
expressionFn.assign(scope.context, outboundValue);
130-
tasks.completeTask(taskId);
131-
}
132-
}, context: controller, formatters: formatters);
133-
}
164+
_bindTwoWay(tasks, nodeAttrs[attrName], scope, dstPathFn,
165+
controller, formatters, dstExpression);
134166
break;
135167

136168
case '=>': // one-way
137169
if (nodeAttrs[attrName] == null) return;
138-
var taskId = tasks.registerTask();
139-
140-
Expression attrExprFn = _parser(nodeAttrs[attrName]);
141-
scope.watch(nodeAttrs[attrName], (v, _) {
142-
dstPathFn.assign(controller, v);
143-
tasks.completeTask(taskId);
144-
}, formatters: formatters);
170+
_bindOneWay(tasks, nodeAttrs[attrName], scope,
171+
dstPathFn, controller, formatters);
145172
break;
146173

147174
case '=>!': // one-way, one-time
@@ -157,8 +184,7 @@ class ElementBinder {
157184
break;
158185

159186
case '&': // callback
160-
dstPathFn.assign(controller,
161-
_parser(nodeAttrs[attrName]).bind(scope.context, ScopeLocals.wrapper));
187+
_bindCallback(dstPathFn, controller, nodeAttrs[attrName], scope);
162188
break;
163189
}
164190
});
@@ -182,7 +208,10 @@ class ElementBinder {
182208
if (scope.isAttached) controller.attach();
183209
} : null);
184210

185-
_createAttrMappings(controller, scope, ref, nodeAttrs, formatters, tasks);
211+
if (ref.mappings.isNotEmpty) {
212+
if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);
213+
_createAttrMappings(controller, scope, ref.mappings, nodeAttrs, formatters, tasks);
214+
}
186215

187216
if (controller is AttachAware) {
188217
var taskId = tasks.registerTask();

test/core_dom/compiler_spec.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,42 @@ void main() {
193193
});
194194

195195

196+
describe("bind-", () {
197+
beforeEachModule((Module module) {
198+
module
199+
..type(IoComponent);
200+
});
201+
202+
it('should support bind- syntax', () {
203+
var element = _.compile('<div ng-bind bind-ng-bind="name"></div>');
204+
205+
_.rootScope.context['name'] = 'angular';
206+
207+
expect(element.text).toEqual('');
208+
_.rootScope.apply();
209+
expect(element.text).toEqual('angular');
210+
});
211+
212+
it('should work with attrs, one-way, two-way and callbacks', async(() {
213+
_.compile('<div><io bind-attr="\'A\'" bind-expr="name" bind-ondone="done=true"></io></div>');
214+
215+
_.rootScope.context['name'] = 'misko';
216+
microLeap();
217+
_.rootScope.apply();
218+
var component = _.rootScope.context['ioComponent'];
219+
expect(component.scope.context['name']).toEqual(null);
220+
expect(component.scope.context['attr']).toEqual('A');
221+
expect(component.scope.context['expr']).toEqual('misko');
222+
component.scope.context['expr'] = 'angular';
223+
_.rootScope.apply();
224+
expect(_.rootScope.context['name']).toEqual('angular');
225+
expect(_.rootScope.context['done']).toEqual(null);
226+
component.scope.context['ondone']();
227+
expect(_.rootScope.context['done']).toEqual(true);
228+
}));
229+
});
230+
231+
196232
describe("interpolation", () {
197233
it('should interpolate attribute nodes', () {
198234
var element = _.compile('<div test="{{name}}"></div>');

0 commit comments

Comments
 (0)