mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Introduced parent and parents keywords for binding.
This commit is contained in:
@@ -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"));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user