From a2e29016c8fe7dbee9b3ed4975f3200b1e500f03 Mon Sep 17 00:00:00 2001 From: James deBoer Date: Thu, 24 Apr 2014 18:25:35 -0700 Subject: [PATCH] feat(compiler): bind- syntax Adds support for bind- syntax which allows directive users to give an expression for any mapping. For #650 Closes #957 --- lib/core_dom/element_binder.dart | 103 ++++++++++++++++++++----------- test/core_dom/compiler_spec.dart | 36 +++++++++++ 2 files changed, 102 insertions(+), 37 deletions(-) diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index dd01377ac..8e5af496c 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -83,11 +83,51 @@ 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 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) { @@ -95,6 +135,20 @@ class ElementBinder { "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(); @@ -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 @@ -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; } }); @@ -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(); diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index f86eea9aa..ada640ce3 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -193,6 +193,42 @@ void main() { }); + describe("bind-", () { + beforeEachModule((Module module) { + module + ..type(IoComponent); + }); + + it('should support bind- syntax', () { + var element = _.compile('
'); + + _.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('
'); + + _.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('
');