From 2c4c3c90e26aeb4f8dcd32bca1b91da1012d8b7f Mon Sep 17 00:00:00 2001 From: Nedyalko Nikolov Date: Mon, 18 Apr 2016 17:27:56 +0300 Subject: [PATCH] Fix for using same name properties in binding. --- apps/tests/ui/bindable-tests.ts | 125 ++++++++++++++++++++++++++++++++ ui/core/bindable.ts | 53 +++++++++----- 2 files changed, 158 insertions(+), 20 deletions(-) diff --git a/apps/tests/ui/bindable-tests.ts b/apps/tests/ui/bindable-tests.ts index 338078102..8e726b554 100644 --- a/apps/tests/ui/bindable-tests.ts +++ b/apps/tests/ui/bindable-tests.ts @@ -997,4 +997,129 @@ export function test_$ValueSupportWithinExpression() { model.set("anyColor", "red"); TKUnit.assertEqual(bindableObj.get("test"), "red", "When anyColor is red test property should be red too."); +} + +class DummyNestedClass extends observable.Observable { + private _secondsobject: number; + public get secondsobject(): number { + return this._secondsobject; + } + public set secondsobject(value: number) { + if (this._secondsobject !== value) { + this._secondsobject = value; + this.notifyPropertyChange('secondsobject', value); + } + } +} + +class DummyClassWithSamePropertyNames extends observable.Observable { + private _seconds: number; + private _secondsobject: DummyNestedClass; + public get seconds(): number { + return this._seconds; + } + public set seconds(value: number) { + if (this._seconds !== value) { + this._seconds = value; + this.notifyPropertyChange('seconds', value); + } + } + + public get secondsobject(): DummyNestedClass { + return this._secondsobject; + } + public set secondsobject(value: DummyNestedClass) { + if (this._secondsobject !== value) { + this._secondsobject = value; + this.notifyPropertyChange('secondsobject', value); + } + } +} + +class DummyModel extends observable.Observable { + private _item: DummyClassWithSamePropertyNames; + public get item(): DummyClassWithSamePropertyNames { + return this._item; + } + + public set item(value: DummyClassWithSamePropertyNames) { + if (this._item !== value) { + this._item = value; + this.notifyPropertyChange("item", value); + } + } +} + +export function test_BindingToPropertiesWithSameNames() { + var model = new DummyModel(); + model.item = new DummyClassWithSamePropertyNames(); + model.item.seconds = 1; + var secondsobject = new DummyNestedClass(); + secondsobject.secondsobject = 1; + model.item.secondsobject = secondsobject; + + var target1 = new bindable.Bindable(); + target1.bind({ + sourceProperty: "item.seconds", + targetProperty: "targetProperty", + twoWay: true + }, model); + + var target2 = new bindable.Bindable(); + target2.bind({ + sourceProperty: "item.secondsobject.secondsobject", + targetProperty: "targetProp", + twoWay: true + }, model); + + model.item.set("seconds", model.item.seconds + 1); + var newValue = (model).item.secondsobject.secondsobject + 1; + model.item.secondsobject.set("secondsobject", newValue); + + TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds")); + TKUnit.assertEqual(target2.get("targetProp"), newValue); + + // calling this two times in order to ensure that adding and removing weak event listeners is working fine. + newValue = model.item.secondsobject.secondsobject + 1; + model.item.secondsobject.set("secondsobject", newValue); + + TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds")); + TKUnit.assertEqual(target2.get("targetProp"), newValue); +} + +export function test_BindingToPropertiesWithSameNamesSecondCase() { + var model = new DummyModel(); + model.item = new DummyClassWithSamePropertyNames(); + model.item.seconds = 1; + var secondsobject = new DummyNestedClass(); + secondsobject.secondsobject = 1; + model.item.secondsobject = secondsobject; + + var target1 = new bindable.Bindable(); + target1.bind({ + sourceProperty: "item.seconds", + targetProperty: "targetProperty", + twoWay: true + }, model); + + var target2 = new bindable.Bindable(); + target2.bind({ + sourceProperty: "item.secondsobject.secondsobject", + targetProperty: "targetProp", + twoWay: true + }, model); + + model.item.set("seconds", model.item.seconds + 1); + var newValue = model.item.secondsobject.secondsobject + 1; + model.item.set("secondsobject",{secondsobject: newValue}); + + TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds")); + TKUnit.assertEqual(target2.get("targetProp"), newValue); + + // calling this two times in order to ensure that adding and removing weak event listeners is working fine. + newValue = model.item.secondsobject.secondsobject + 1; + model.item.set("secondsobject",{secondsobject: newValue}); + + TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds")); + TKUnit.assertEqual(target2.get("targetProp"), newValue); } \ No newline at end of file diff --git a/ui/core/bindable.ts b/ui/core/bindable.ts index 21f9c762f..c2ccae927 100644 --- a/ui/core/bindable.ts +++ b/ui/core/bindable.ts @@ -280,13 +280,14 @@ export class Binding { return result; } - private addPropertyChangeListeners(source: WeakRef, sourceProperty: Array) { + private addPropertyChangeListeners(source: WeakRef, sourceProperty: Array, parentProperies?: string) { var objectsAndProperties = this.resolveObjectsAndProperties(source.get(), sourceProperty) var objectsAndPropertiesLength = objectsAndProperties.length; if (objectsAndPropertiesLength > 0) { var i; + var prop = parentProperies || ""; for (i = 0; i < objectsAndPropertiesLength; i++) { - var prop = objectsAndProperties[i].property; + prop += "$" + objectsAndProperties[i].property; var currentObject = objectsAndProperties[i].instance; if (!this.propertyChangeListeners[prop] && currentObject instanceof observable.Observable) { weakEvents.addWeakEventListener( @@ -406,6 +407,18 @@ export class Binding { } public onSourcePropertyChanged(data: observable.PropertyChangeData) { + var sourceProps = Binding.getProperties(this.options.sourceProperty); + var sourcePropsLength = sourceProps.length; + var changedPropertyIndex = sourceProps.indexOf(data.propertyName); + var parentProps = ""; + if (changedPropertyIndex > -1) { + parentProps = "$" + sourceProps.slice(0, changedPropertyIndex + 1).join("$"); + while (this.propertyChangeListeners[parentProps] !== data.object) { + changedPropertyIndex += sourceProps.slice(changedPropertyIndex + 1).indexOf(data.propertyName) + 1; + parentProps = "$" + sourceProps.slice(0, changedPropertyIndex + 1).join("$"); + } + } + if (this.options.expression) { var expressionValue = this._getExpressionValue(this.options.expression, false, undefined); if (expressionValue instanceof Error) { @@ -415,14 +428,12 @@ export class Binding { this.updateTarget(expressionValue); } } else { - var propIndex = this.getSourceProperties().indexOf(data.propertyName); - if (propIndex > -1) { - var props = this.getSourceProperties().slice(propIndex + 1); + if (changedPropertyIndex > -1) { + var props = sourceProps.slice(changedPropertyIndex + 1); var propsLength = props.length; if (propsLength > 0) { var value = data.value; - var i; - for (i = 0; i < propsLength; i++) { + for (let i = 0; i < propsLength; i++) { value = value[props[i]]; } this.updateTarget(value); @@ -433,27 +444,29 @@ export class Binding { } } - var sourceProps = Binding.getProperties(this.options.sourceProperty); - var sourcePropsLength = sourceProps.length; - var changedPropertyIndex = sourceProps.indexOf(data.propertyName); if (changedPropertyIndex > -1) { - var probablyChangedObject = this.propertyChangeListeners[sourceProps[changedPropertyIndex + 1]]; + var probablyChangedObject = this.propertyChangeListeners[parentProps]; if (probablyChangedObject && probablyChangedObject !== data.object[sourceProps[changedPropertyIndex]]) { // remove all weakevent listeners after change, because changed object replaces object that is hooked for // propertyChange event - for (i = sourcePropsLength - 1; i > changedPropertyIndex; i--) { - weakEvents.removeWeakEventListener( - this.propertyChangeListeners[sourceProps[i]], - observable.Observable.propertyChangeEvent, - this.onSourcePropertyChanged, - this); - delete this.propertyChangeListeners[sourceProps[i]]; + for (let i = sourcePropsLength - 1; i > changedPropertyIndex; i--) { + var prop = "$" + sourceProps.slice(0, i + 1).join("$"); + if (this.propertyChangeListeners[prop]) { + weakEvents.removeWeakEventListener( + this.propertyChangeListeners[prop], + observable.Observable.propertyChangeEvent, + this.onSourcePropertyChanged, + this); + delete this.propertyChangeListeners[prop]; + } } - //var newProps = this.options.sourceProperty.substr(this.options.sourceProperty.indexOf(data.propertyName) + data.propertyName.length + 1); var newProps = sourceProps.slice(changedPropertyIndex + 1); // add new weakevent listeners - this.addPropertyChangeListeners(new WeakRef(data.object[sourceProps[changedPropertyIndex]]), newProps); + var newObject = data.object[sourceProps[changedPropertyIndex]] + if (typeof newObject === 'object') { + this.addPropertyChangeListeners(new WeakRef(data.object[sourceProps[changedPropertyIndex]]), newProps, parentProps); + } } } }