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

Commit

Permalink
feat(compiler): bind- syntax
Browse files Browse the repository at this point in the history
Adds support for bind- syntax which allows directive users to
give an expression for any mapping.

For #650
Closes #957
  • Loading branch information
jbdeboer committed Apr 30, 2014
1 parent d5492d6 commit a2e2901
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 37 deletions.
103 changes: 66 additions & 37 deletions lib/core_dom/element_binder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,72 @@ class ElementBinder {
bool get hasDirectivesOrEvents =>
_usableDirectiveRefs.isNotEmpty || onEvents.isNotEmpty;

_createAttrMappings(controller, scope, DirectiveRef ref, nodeAttrs, formatters, tasks) {
ref.mappings.forEach((MappingParts p) {
_bindTwoWay(tasks, expression, scope, dstPathFn, controller, formatters, dstExpression) {
var taskId = tasks.registerTask();
Expression expressionFn = _parser(expression);

var viewOutbound = false;
var viewInbound = false;
scope.watch(expression, (inboundValue, _) {
if (!viewInbound) {
viewOutbound = true;
scope.rootScope.runAsync(() => viewOutbound = false);
var value = dstPathFn.assign(controller, inboundValue);
tasks.completeTask(taskId);
return value;
}
}, formatters: formatters);
if (expressionFn.isAssignable) {
scope.watch(dstExpression, (outboundValue, _) {
if (!viewOutbound) {
viewInbound = true;
scope.rootScope.runAsync(() => viewInbound = false);
expressionFn.assign(scope.context, outboundValue);
tasks.completeTask(taskId);
}
}, context: controller, formatters: formatters);
}
}

_bindOneWay(tasks, expression, scope, dstPathFn, controller, formatters) {
var taskId = tasks.registerTask();

Expression attrExprFn = _parser(expression);
scope.watch(expression, (v, _) {
dstPathFn.assign(controller, v);
tasks.completeTask(taskId);
}, formatters: formatters);
}

_bindCallback(dstPathFn, controller, expression, scope) {
dstPathFn.assign(controller, _parser(expression).bind(scope.context, ScopeLocals.wrapper));
}

_createAttrMappings(controller, scope, List<MappingParts> mappings, nodeAttrs, formatters, tasks) {
mappings.forEach((MappingParts p) {
var attrName = p.attrName;
var dstExpression = p.dstExpression;
if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);

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

// Check if there is a bind attribute for this mapping.
var bindAttr = bindAttrs["bind-${p.attrName}"];
if (bindAttr != null) {
if (p.mode == '<=>') {
_bindTwoWay(tasks, bindAttr, scope, dstPathFn,
controller, formatters, dstExpression);
} else if(p.mode == '&') {
_bindCallback(dstPathFn, controller, bindAttr, scope);
} else {
_bindOneWay(tasks, bindAttr, scope, dstPathFn, controller, formatters);
}
return;
}

switch (p.mode) {
case '@': // string
var taskId = tasks.registerTask();
Expand All @@ -107,41 +161,14 @@ class ElementBinder {
case '<=>': // two-way
if (nodeAttrs[attrName] == null) return;

var taskId = tasks.registerTask();
String expression = nodeAttrs[attrName];
Expression expressionFn = _parser(expression);
var viewOutbound = false;
var viewInbound = false;
scope.watch(expression, (inboundValue, _) {
if (!viewInbound) {
viewOutbound = true;
scope.rootScope.runAsync(() => viewOutbound = false);
var value = dstPathFn.assign(controller, inboundValue);
tasks.completeTask(taskId);
return value;
}
}, formatters: formatters);
if (expressionFn.isAssignable) {
scope.watch(dstExpression, (outboundValue, _) {
if (!viewOutbound) {
viewInbound = true;
scope.rootScope.runAsync(() => viewInbound = false);
expressionFn.assign(scope.context, outboundValue);
tasks.completeTask(taskId);
}
}, context: controller, formatters: formatters);
}
_bindTwoWay(tasks, nodeAttrs[attrName], scope, dstPathFn,
controller, formatters, dstExpression);
break;

case '=>': // one-way
if (nodeAttrs[attrName] == null) return;
var taskId = tasks.registerTask();

Expression attrExprFn = _parser(nodeAttrs[attrName]);
scope.watch(nodeAttrs[attrName], (v, _) {
dstPathFn.assign(controller, v);
tasks.completeTask(taskId);
}, formatters: formatters);
_bindOneWay(tasks, nodeAttrs[attrName], scope,
dstPathFn, controller, formatters);
break;

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

case '&': // callback
dstPathFn.assign(controller,
_parser(nodeAttrs[attrName]).bind(scope.context, ScopeLocals.wrapper));
_bindCallback(dstPathFn, controller, nodeAttrs[attrName], scope);
break;
}
});
Expand All @@ -182,7 +208,10 @@ class ElementBinder {
if (scope.isAttached) controller.attach();
} : null);

_createAttrMappings(controller, scope, ref, nodeAttrs, formatters, tasks);
if (ref.mappings.isNotEmpty) {
if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);
_createAttrMappings(controller, scope, ref.mappings, nodeAttrs, formatters, tasks);
}

if (controller is AttachAware) {
var taskId = tasks.registerTask();
Expand Down
36 changes: 36 additions & 0 deletions test/core_dom/compiler_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,42 @@ void main() {
});


describe("bind-", () {
beforeEachModule((Module module) {
module
..type(IoComponent);
});

it('should support bind- syntax', () {
var element = _.compile('<div ng-bind bind-ng-bind="name"></div>');

_.rootScope.context['name'] = 'angular';

expect(element.text).toEqual('');
_.rootScope.apply();
expect(element.text).toEqual('angular');
});

it('should work with attrs, one-way, two-way and callbacks', async(() {
_.compile('<div><io bind-attr="\'A\'" bind-expr="name" bind-ondone="done=true"></io></div>');

_.rootScope.context['name'] = 'misko';
microLeap();
_.rootScope.apply();
var component = _.rootScope.context['ioComponent'];
expect(component.scope.context['name']).toEqual(null);
expect(component.scope.context['attr']).toEqual('A');
expect(component.scope.context['expr']).toEqual('misko');
component.scope.context['expr'] = 'angular';
_.rootScope.apply();
expect(_.rootScope.context['name']).toEqual('angular');
expect(_.rootScope.context['done']).toEqual(null);
component.scope.context['ondone']();
expect(_.rootScope.context['done']).toEqual(true);
}));
});


describe("interpolation", () {
it('should interpolate attribute nodes', () {
var element = _.compile('<div test="{{name}}"></div>');
Expand Down

0 comments on commit a2e2901

Please sign in to comment.