From b569451bc777e6300576da4850ccb3b9172ed8ea Mon Sep 17 00:00:00 2001 From: Nedyalko Nikolov Date: Tue, 17 Mar 2015 16:01:48 +0200 Subject: [PATCH] Added named parameters support for xml binding definition. --- CrossPlatformModules.csproj | 1 + apps/tests/ui/bindable-tests.ts | 50 +++++++++-- ui/builder/binding-builder.ts | 154 ++++++++++++++++++++++++++++++++ ui/builder/component-builder.ts | 15 ++-- ui/core/bindable.d.ts | 1 - ui/core/bindable.ts | 34 ------- 6 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 ui/builder/binding-builder.ts diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 690a20ed5..43aa68733 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -302,6 +302,7 @@ trace.d.ts + list-picker.d.ts diff --git a/apps/tests/ui/bindable-tests.ts b/apps/tests/ui/bindable-tests.ts index 8cc886578..e02cfe016 100644 --- a/apps/tests/ui/bindable-tests.ts +++ b/apps/tests/ui/bindable-tests.ts @@ -9,6 +9,7 @@ import buttonModule = require("ui/button"); import utils = require("utils/utils"); import pageModule = require("ui/page"); import stackLayoutModule = require("ui/layouts/stack-layout"); +import bindingBuilder = require("ui/builder/binding-builder"); // // For information and examples how to use bindings please refer to special [**Data binding**](../../../../bindings.md) topic. @@ -383,7 +384,7 @@ export var test_Bindable_BindingContext_String_DoesNotThrow = function () { export var test_getBindableOptionsFromStringFullFormat = function () { var bindingExpression = "bindProperty, bindProperty * 2, false"; - var bindOptions = bindable.Bindable._getBindingOptions("targetBindProperty", bindingExpression); + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); @@ -393,7 +394,7 @@ export var test_getBindableOptionsFromStringFullFormat = function () { export var test_getBindableOptionsFromStringShortFormatExpression = function () { var bindingExpression = "bindProperty * 2"; - var bindOptions = bindable.Bindable._getBindingOptions("targetBindProperty", bindingExpression); + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); @@ -403,17 +404,56 @@ export var test_getBindableOptionsFromStringShortFormatExpression = function () export var test_getBindableOptionsFromStringShortFormatProperty = function () { var bindingExpression = "bindProperty"; - var bindOptions = bindable.Bindable._getBindingOptions("targetBindProperty", bindingExpression); + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); - TKUnit.assert(bindOptions.expression === null, "Expected: null, Actual: " + bindOptions.expression); + TKUnit.assert(bindOptions.expression === undefined, "Expected: null, Actual: " + bindOptions.expression); TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay); } export var test_getBindableOptionsFromStringTwoParamsFormat = function () { var bindingExpression = "bindProperty, bindProperty * 2"; - var bindOptions = bindable.Bindable._getBindingOptions("targetBindProperty", bindingExpression); + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); + + TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); + TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); + TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual:" + bindOptions.expression); + TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay); +} + +export var test_getBindableOptionsFromStringFullNamedFormat = function () { + var bindingExpression = "bindProperty, expression = bindProperty * 2, twoWay = false"; + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); + + TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); + TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); + TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual:" + bindOptions.expression); + TKUnit.assert(bindOptions.twoWay === false, "Expected: false, Actual: " + bindOptions.twoWay); +} + +export var test_getBindableOptionsFromStringShortNamedFormatExpression = function () { + var bindingExpression = "sourceProperty = bindProperty, expression = bindProperty * 2"; + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); + + TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); + TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); + TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual: " + bindOptions.expression); + TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay); +} + +export var test_getBindableOptionsFromStringShortNamedFormatProperty = function () { + var bindingExpression = "sourceProperty = bindProperty"; + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); + TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); + TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); + TKUnit.assert(bindOptions.expression === undefined, "Expected: null, Actual: " + bindOptions.expression); + TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay); +} + +export var test_getBindableOptionsFromStringTwoParamsNamedFormat = function () { + var bindingExpression = "bindProperty, expression = bindProperty * 2"; + var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression); TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty); TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty); diff --git a/ui/builder/binding-builder.ts b/ui/builder/binding-builder.ts new file mode 100644 index 000000000..90512c4cc --- /dev/null +++ b/ui/builder/binding-builder.ts @@ -0,0 +1,154 @@ +var expressionSymbolsRegex = /[ \+\-\*%\?:<>=!\|&\(\)\[\]]/; + +export module bindingConstants { + export var sourceProperty = "sourceProperty"; + export var targetProperty = "targetProperty"; + export var expression = "expression"; + export var twoWay = "twoWay"; + export var source = "source"; +}; + +var hasEqualSignRegex = /=+/; +var equalSignComparisionOperatorsRegex = /(==|===|>=|<=|!=|!==)/; +function isNamedParam(value) { + var equalSignIndex = value.search(hasEqualSignRegex); + if (equalSignIndex > -1) { + var equalSignSurround = value.substr(equalSignIndex > 0 ? equalSignIndex - 1 : 0, 3); + if (equalSignSurround.search(equalSignComparisionOperatorsRegex) === -1) { + return true; + } + } + return false; +} + +function areNamedParams(params: Array) { + var i; + for (i = 0; i < params.length; i++) { + if (isNamedParam(params[i])) { + return true; + } + } + return false; +} + +var namedParamConstants = { + propName: "propName", + propValue: "propValue" +} + +function getPropertyNameValuePair(param, knownOptions, callback) { + var nameValuePair = {}; + var propertyName = param.substr(0, param.indexOf("=")).trim(); + var propertyValue = param.substr(param.indexOf("=") + 1).trim(); + if (knownOptions) { + if (!propertyName) { + propertyName = knownOptions.defaultProperty; + } + else { + propertyName = propertyName in knownOptions ? propertyName : null; + } + } + + if (propertyName) { + if (callback) { + nameValuePair = callback(propertyName, propertyValue); + } + else { + nameValuePair[namedParamConstants.propName] = propertyName; + nameValuePair[namedParamConstants.propValue] = propertyValue; + } + return nameValuePair; + } + + return null; +} + +function parseNamedProperties(parameterList, knownOptions, callback) { + var result = {}; + var i; + for (i = 0; i < parameterList.length; i++) { + var nameValuePair = getPropertyNameValuePair(parameterList[i], knownOptions, callback); + if (nameValuePair) { + result[nameValuePair[namedParamConstants.propName]] = nameValuePair[namedParamConstants.propValue]; + } + } + return result; +} + +function extractPropertyNameFromExpression(expression: string): string { + var firstExpressionSymbolIndex = expression.search(expressionSymbolsRegex); + if (firstExpressionSymbolIndex > -1) { + return expression.substr(0, firstExpressionSymbolIndex).trim(); + } + else { + return expression; + } +} + +export function getBindingOptions(name: string, value: string): any { + var namedParams = []; + var params = value.split(","); + if (!areNamedParams(params)) { + if (params.length === 1) { + namedParams.push(bindingConstants.sourceProperty + " = " + extractPropertyNameFromExpression(params[0].trim())); + var expression = params[0].search(expressionSymbolsRegex) > -1 ? params[0].trim() : null; + if (expression) { + namedParams.push(bindingConstants.expression + " = " + expression); + } + namedParams.push(bindingConstants.twoWay + " = true"); + } + else { + namedParams.push(bindingConstants.sourceProperty + " = " + extractPropertyNameFromExpression(params[0].trim())); + namedParams.push(bindingConstants.expression + " = " + params[1].trim()); + var twoWay = params[2] ? params[2].toLowerCase().trim() === "true" : true; + namedParams.push(bindingConstants.twoWay + " = " + twoWay); + } + } + else { + namedParams = params; + } + + var bindingPropertyHandler = function (prop, value) { + var result = {}; + result[namedParamConstants.propName] = prop; + if (prop === bindingConstants.twoWay) { + // create a real boolean value + if (value === "true") { + result[namedParamConstants.propValue] = true; + } + else { + result[namedParamConstants.propValue] = false; + } + } + else { + result[namedParamConstants.propValue] = value; + } + + return result; + }; + + var bindingOptionsParameters = parseNamedProperties(namedParams, xmlBindingProperties, bindingPropertyHandler); + var bindOptions = { + targetProperty: name + }; + + for (var prop in bindingOptionsParameters) { + if (bindingOptionsParameters.hasOwnProperty(prop)) { + bindOptions[prop] = bindingOptionsParameters[prop]; + } + } + + if (bindOptions[bindingConstants.twoWay] === undefined) { + bindOptions[bindingConstants.twoWay] = true; + } + + return bindOptions; +} + +var xmlBindingProperties = { + sourceProperty: true, + expression: true, + twoWay: true, + source: true, + defaultProperty: bindingConstants.sourceProperty +}; \ No newline at end of file diff --git a/ui/builder/component-builder.ts b/ui/builder/component-builder.ts index fa136035a..5a9b646a0 100644 --- a/ui/builder/component-builder.ts +++ b/ui/builder/component-builder.ts @@ -8,6 +8,7 @@ import types = require("utils/types"); import definition = require("ui/builder/component-builder"); import fs = require("file-system"); import gestures = require("ui/gestures"); +import bindingBuilder = require("ui/builder/binding-builder"); var KNOWNEVENTS = "knownEvents"; var UI_PATH = "ui/"; @@ -76,7 +77,13 @@ export function getComponentModule(elementName: string, namespace: string, attri if (isKnownEvent(attr, instanceModule)) { attachEventBinding(instance, attr, attrValue); } else { - instance.bind(getBinding(instance, attr, attrValue)); + var bindOptions = bindingBuilder.getBindingOptions(attr, getBindingExpressionFromAttribute(attrValue)); + instance.bind({ + sourceProperty : bindOptions[bindingBuilder.bindingConstants.sourceProperty], + targetProperty: bindOptions[bindingBuilder.bindingConstants.targetProperty], + expression: bindOptions[bindingBuilder.bindingConstants.expression], + twoWay: bindOptions[bindingBuilder.bindingConstants.twoWay] + }, bindOptions[bindingBuilder.bindingConstants.source]); } } else if (isKnownEvent(attr, instanceModule)) { // Get the event handler from page module exports. @@ -160,10 +167,6 @@ function isKnownEvent(name: string, exports: any): boolean { (KNOWNEVENTS in view && name in view[KNOWNEVENTS]); } -function getBinding(instance: view.View, name: string, value: string): bindable.BindingOptions { - return bindable.Bindable._getBindingOptions(name, getBindingExpressionFromAttribute(value)); -} - function getBindingExpressionFromAttribute(value: string): string { return value.replace("{{", "").replace("}}", "").trim(); } @@ -177,4 +180,4 @@ function isBinding(value: string): boolean { } return isBinding; -} +} \ No newline at end of file diff --git a/ui/core/bindable.d.ts b/ui/core/bindable.d.ts index 87828ff89..3f4666f41 100644 --- a/ui/core/bindable.d.ts +++ b/ui/core/bindable.d.ts @@ -66,7 +66,6 @@ unbind(property: string); //@private - static _getBindingOptions(name: string, bindingExpression: string): BindingOptions; _updateTwoWayBinding(propertyName: string, value: any); _onBindingContextChanged(oldValue: any, newValue: any); //@endprivate diff --git a/ui/core/bindable.ts b/ui/core/bindable.ts index ea6e1a091..0dd0d4eb5 100644 --- a/ui/core/bindable.ts +++ b/ui/core/bindable.ts @@ -6,8 +6,6 @@ import types = require("utils/types"); import trace = require("trace"); import polymerExpressions = require("js-libs/polymer-expressions"); -var expressionSymbolsRegex = /[ \+\-\*%\?:<>=!\|&\(\)\[\]]/; - var bindingContextProperty = new dependencyObservable.Property( "bindingContext", "Bindable", @@ -116,38 +114,6 @@ export class Bindable extends dependencyObservable.DependencyObservable implemen } } } - - private static extractPropertyNameFromExpression(expression: string): string { - var firstExpressionSymbolIndex = expression.search(expressionSymbolsRegex); - if (firstExpressionSymbolIndex > -1) { - return expression.substr(0, firstExpressionSymbolIndex).trim(); - } - else { - return expression; - } - } - - public static _getBindingOptions(name: string, bindingExpression: string): definition.BindingOptions { - var result: definition.BindingOptions; - result = { - targetProperty: name, - sourceProperty: "" - }; - if (types.isString(bindingExpression)) { - var params = bindingExpression.split(","); - if (params.length === 1) { - result.sourceProperty = Bindable.extractPropertyNameFromExpression(params[0].trim()); - result.expression = params[0].search(expressionSymbolsRegex) > -1 ? params[0].trim() : null; - result.twoWay = true; - } - else { - result.sourceProperty = Bindable.extractPropertyNameFromExpression(params[0].trim()); - result.expression = params[1].trim(); - result.twoWay = params[2] ? params[2].toLowerCase().trim() === "true" : true; - } - } - return result; - } } export class Binding {