Introduced parent and parents keywords for binding.

This commit is contained in:
Nedyalko Nikolov
2015-06-12 09:15:34 +03:00
parent a934fd5cd7
commit 440e5968f2
4 changed files with 200 additions and 7 deletions

View File

@@ -465,6 +465,39 @@ export var test_getBindableOptionsFromStringTwoParamsNamedFormat = function () {
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
}
export var test_getBindingOptionsFromStringWithFunctionWitnMoreParams = function () {
var bindingExpression = "bindProperty, converter(bindProperty, param1)";
var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assertEqual(bindOptions.sourceProperty, "bindProperty");
TKUnit.assertEqual(bindOptions.targetProperty, "targetBindProperty");
TKUnit.assertEqual(bindOptions.expression, "converter(bindProperty, param1)");
TKUnit.assertEqual(bindOptions.twoWay, true);
}
export var test_getBindingOptionsFromStringWithFunctionArrayParams = function () {
var bindingExpression = "bindProperty, converter(bindProperty, [param1, param2])";
var bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assertEqual(bindOptions.sourceProperty, "bindProperty");
TKUnit.assertEqual(bindOptions.targetProperty, "targetBindProperty");
TKUnit.assertEqual(bindOptions.expression, "converter(bindProperty, [param1, param2])");
TKUnit.assertEqual(bindOptions.twoWay, true);
}
export var test_bindingToNestedPropertyWithValueSyntax = function () {
var bindingSource = new observable.Observable();
bindingSource.set("testProperty", "testValue");
var testElement = new bindable.Bindable();
testElement.bind({
sourceProperty: "$value.testProperty",
targetProperty: "targetPropertyName"
}, bindingSource);
TKUnit.assertEqual(testElement.get("targetPropertyName"), "testValue");
}
export var test_TwoElementsBindingToSameBindingContext = function () {
var testFunc = function (page: pageModule.Page) {
var upperStackLabel = <labelModule.Label>(page.getViewById("upperStackLabel"));

View File

@@ -530,6 +530,56 @@ export function test_BindingListViewToASimpleArrayWithExpression() {
helper.buildUIAndRunTest(listView, testAction);
}
export function test_bindingToParentObject() {
var listView = new listViewModule.ListView();
var expectedValue = "parentTestValue";
function testAction(views: Array<viewModule.View>) {
var listViewModel = new observable.Observable();
listViewModel.set("items", [1, 2, 3]);
listViewModel.set("parentTestProp", expectedValue);
listView.bindingContext = listViewModel;
listView.bind({ sourceProperty: "items", targetProperty: "items" });
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ sourceProperty = $parents[ListView].parentTestProp }}\" />";
TKUnit.wait(ASYNC);
var firstNativeElementText = getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = getTextFromNativeElementAt(listView, 2);
TKUnit.assertEqual(firstNativeElementText, expectedValue, "first element text");
TKUnit.assertEqual(secondNativeElementText, expectedValue, "second element text");
TKUnit.assertEqual(thirdNativeElementText, expectedValue, "third element text");
}
helper.buildUIAndRunTest(listView, testAction);
}
export function test_bindingToParentObjectWithSpacesInIndexer() {
var listView = new listViewModule.ListView();
var expectedValue = "parentTestValue";
function testAction(views: Array<viewModule.View>) {
var listViewModel = new observable.Observable();
listViewModel.set("items", [1, 2, 3]);
listViewModel.set("parentTestProp", expectedValue);
listView.bindingContext = listViewModel;
listView.bind({ sourceProperty: "items", targetProperty: "items" });
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ sourceProperty = $parents[ ListView ].parentTestProp }}\" />";
TKUnit.wait(ASYNC);
var firstNativeElementText = getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = getTextFromNativeElementAt(listView, 2);
TKUnit.assertEqual(firstNativeElementText, expectedValue, "first element text");
TKUnit.assertEqual(secondNativeElementText, expectedValue, "second element text");
TKUnit.assertEqual(thirdNativeElementText, expectedValue, "third element text");
}
helper.buildUIAndRunTest(listView, testAction);
}
export function test_no_memory_leak_when_items_is_regular_array() {
var createFunc = function (): listViewModule.ListView {
var listView = new listViewModule.ListView();

View File

@@ -7,6 +7,9 @@ export module bindingConstants {
export var twoWay = "twoWay";
export var source = "source";
export var bindingValueKey = "$value";
export var parentValueKey = "$parent";
export var parentsValueKey = "$parents";
export var newPropertyValueKey = "$newPropertyValue";
};
var hasEqualSignRegex = /=+/;
@@ -87,9 +90,30 @@ function extractPropertyNameFromExpression(expression: string): string {
}
}
function getParamsArray(value: string) {
var result = [];
var i;
var skipComma = 0;
var indexReached = 0;
for (i = 0; i < value.length; i++) {
if (value[i] === '(' || value[i] === '[') {
skipComma++;
}
if (value[i] === ')' || value[i] === ']') {
skipComma--;
}
if (value[i] === ',' && skipComma === 0) {
result.push(value.substr(indexReached, i - indexReached));
indexReached = i + 1;
}
}
result.push(value.substr(indexReached));
return result;
}
export function getBindingOptions(name: string, value: string): any {
var namedParams = [];
var params = value.split(",");
var params = getParamsArray(value);
if (!areNamedParams(params)) {
if (params.length === 1) {
namedParams.push(bindingConstants.sourceProperty + " = " + extractPropertyNameFromExpression(params[0].trim()));
@@ -100,7 +124,7 @@ export function getBindingOptions(name: string, value: string): any {
namedParams.push(bindingConstants.twoWay + " = true");
}
else {
namedParams.push(bindingConstants.sourceProperty + " = " + extractPropertyNameFromExpression(params[0].trim()));
namedParams.push(bindingConstants.sourceProperty + " = " + 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);

View File

@@ -21,6 +21,9 @@ function onBindingContextChanged(data: dependencyObservable.PropertyChangeData)
}
var contextKey = "context";
var paramsRegex = /\[\s*(['"])*(\w*)\1\s*\]/;
var parentsRegex = /\$parents\s*\[\s*(['"]*)\w*\1\s*\]/g;
var bc = bindingBuilder.bindingConstants;
export class Bindable extends dependencyObservable.DependencyObservable implements definition.Bindable {
@@ -197,6 +200,14 @@ export class Binding {
}
}
private prepareExpressionForUpdate(): string {
var escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g;
var escapedSourceProperty = this.options.sourceProperty.replace(escapeRegex, '\\$&');
var expRegex = new RegExp(escapedSourceProperty, 'g');
var resultExp = this.options.expression.replace(expRegex, bc.newPropertyValueKey);
return resultExp;
}
public updateTwoWay(value: any) {
if (this.updating) {
return;
@@ -204,7 +215,8 @@ export class Binding {
if (this.options.twoWay) {
if (this.options.expression) {
var changedModel = {};
changedModel[bindingBuilder.bindingConstants.bindingValueKey] = value;
changedModel[bc.bindingValueKey] = value;
changedModel[bc.newPropertyValueKey] = value;
var sourcePropertyName = "";
if (this.sourceOptions) {
sourcePropertyName = this.sourceOptions.property;
@@ -215,7 +227,9 @@ export class Binding {
if (sourcePropertyName !== "") {
changedModel[sourcePropertyName] = value;
}
var expressionValue = this._getExpressionValue(this.options.expression, true, changedModel);
var updateExpression = this.prepareExpressionForUpdate();
this.prepareContextForExpression(changedModel, updateExpression);
var expressionValue = this._getExpressionValue(updateExpression, true, changedModel);
if (expressionValue instanceof Error) {
trace.write((<Error>expressionValue).message, trace.categories.Binding, trace.messageType.error);
}
@@ -241,6 +255,8 @@ export class Binding {
}
}
this.prepareContextForExpression(context, expression);
model[contextKey] = context;
return exp.getValue(model, isBackConvert, changedModel);
}
@@ -254,6 +270,7 @@ export class Binding {
public onSourcePropertyChanged(data: observable.PropertyChangeData) {
if (this.options.expression) {
//this.prepareContextForExpression(this.source.get(), this.options.expression);
var expressionValue = this._getExpressionValue(this.options.expression, false, undefined);
if (expressionValue instanceof Error) {
trace.write((<Error>expressionValue).message, trace.categories.Binding, trace.messageType.error);
@@ -266,10 +283,34 @@ export class Binding {
}
}
private prepareContextForExpression(model, expression) {
var parentViewAndIndex;
var parentView;
if (expression.indexOf(bc.parentValueKey) > -1) {
parentView = this.getParentView(this.target.get(), bc.parentValueKey).view;
if (parentView) {
model[bc.parentValueKey] = parentView.bindingContext;
}
}
var parentsArray = expression.match(parentsRegex);
if (parentsArray) {
var i;
for (i = 0; i < parentsArray.length; i++) {
parentViewAndIndex = this.getParentView(this.target.get(), parentsArray[i]);
if (parentViewAndIndex.view) {
model[bc.parentsValueKey] = model[bc.parentsValueKey] || {};
model[bc.parentsValueKey][parentViewAndIndex.index] = parentViewAndIndex.view.bindingContext;
}
}
}
}
private getSourceProperty() {
if (this.options.expression) {
var changedModel = {};
changedModel[bindingBuilder.bindingConstants.bindingValueKey] = this.source.get();
changedModel[bc.bindingValueKey] = this.source.get();
//this.prepareContextForExpression(this.source.get(), this.options.expression);
var expressionValue = this._getExpressionValue(this.options.expression, false, changedModel);
if (expressionValue instanceof Error) {
trace.write((<Error>expressionValue).message, trace.categories.Binding, trace.messageType.error);
@@ -287,7 +328,7 @@ export class Binding {
if (this.sourceOptions) {
var sourceOptionsInstance = this.sourceOptions.instance.get();
if (this.sourceOptions.property === bindingBuilder.bindingConstants.bindingValueKey) {
if (this.sourceOptions.property === bc.bindingValueKey) {
value = sourceOptionsInstance;
}
else if (sourceOptionsInstance instanceof observable.Observable) {
@@ -325,10 +366,45 @@ export class Binding {
this.updateOptions(this.sourceOptions, value);
}
private getParentView(target, property) {
if (!target || !(target instanceof viewModule.View)) {
return {view: null, index: null};
}
var result;
if (property === bc.parentValueKey) {
result = target.parent;
}
if (property.indexOf(bc.parentsValueKey) === 0) {
result = target.parent;
var indexParams = paramsRegex.exec(property);
var index;
if (indexParams && indexParams.length > 1) {
index = indexParams[2];
}
if (!isNaN(index)) {
var indexAsInt = parseInt(index);
while (indexAsInt > 0) {
result = result.parent;
indexAsInt--;
}
}
else {
while (result && result.typeName !== index) {
result = result.parent;
}
}
}
return { view: result, index: index };
}
private resolveOptions(obj: WeakRef<any>, property: string): { instance: any; property: any } {
var options;
if (property === bindingBuilder.bindingConstants.bindingValueKey) {
if (property === bc.bindingValueKey) {
options = {
instance: obj,
property: property
@@ -343,6 +419,16 @@ export class Binding {
var currentObject = obj.get();
for (i = 0; i < properties.length - 1; i++) {
if (properties[i] === bc.bindingValueKey) {
continue;
}
if (properties[i] === bc.parentValueKey || properties[i].indexOf(bc.parentsValueKey) === 0) {
var parentView = this.getParentView(this.target.get(), properties[i]).view;
if (parentView) {
currentObject = parentView.bindingContext;
}
continue;
}
currentObject = currentObject[properties[i]];
}