mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Added support for property change with same object instance (via WrappedValue).
This commit is contained in:
@@ -490,4 +490,52 @@ export function test_ObservablesCreatedWithJSON_shouldNotEmitTwoTimesPropertyCha
|
|||||||
testObservable.set("property1", 2);
|
testObservable.set("property1", 2);
|
||||||
|
|
||||||
TKUnit.assertEqual(propertyChangeCounter, 1, "PropertyChange event should be fired only once for a single change.");
|
TKUnit.assertEqual(propertyChangeCounter, 1, "PropertyChange event should be fired only once for a single change.");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_ObservableShouldEmitPropertyChangeWithSameObjectUsingWrappedValue() {
|
||||||
|
var testArray = [1];
|
||||||
|
var testObservable = new observable.Observable({ "property1": testArray});
|
||||||
|
var propertyChangeCounter = 0;
|
||||||
|
var propertyChangeHandler = function (args) {
|
||||||
|
propertyChangeCounter++;
|
||||||
|
}
|
||||||
|
testObservable.on(observable.Observable.propertyChangeEvent, propertyChangeHandler);
|
||||||
|
testArray.push(2);
|
||||||
|
|
||||||
|
testObservable.set("property1", testArray);
|
||||||
|
|
||||||
|
TKUnit.assertEqual(propertyChangeCounter, 0, "PropertyChange event should not be fired when the same object instance is passed.");
|
||||||
|
|
||||||
|
testObservable.set("property1", observable.WrappedValue.wrap(testArray));
|
||||||
|
|
||||||
|
TKUnit.assertEqual(propertyChangeCounter, 1, "PropertyChange event should be fired only once for a single change.");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_CorrectEventArgsWhenWrappedValueIsUsed() {
|
||||||
|
let testArray = [1];
|
||||||
|
let testObservable = new observable.Observable({ "property1": testArray});
|
||||||
|
let actualArgsValue = 0;
|
||||||
|
let propertyChangeHandler = function (args) {
|
||||||
|
actualArgsValue = args.value;
|
||||||
|
|
||||||
|
}
|
||||||
|
testObservable.on(observable.Observable.propertyChangeEvent, propertyChangeHandler);
|
||||||
|
testArray.push(2);
|
||||||
|
|
||||||
|
let wrappedArray = observable.WrappedValue.wrap(testArray);
|
||||||
|
|
||||||
|
testObservable.set("property1", wrappedArray);
|
||||||
|
|
||||||
|
TKUnit.assertEqual(actualArgsValue, wrappedArray, "PropertyChange event should be fired with correct value in arguments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_CorrectPropertyValueAfterUsingWrappedValue() {
|
||||||
|
let testArray = [1];
|
||||||
|
let testObservable = new observable.Observable({ "property1": testArray});
|
||||||
|
|
||||||
|
let wrappedArray = observable.WrappedValue.wrap(testArray);
|
||||||
|
|
||||||
|
testObservable.set("property1", wrappedArray);
|
||||||
|
|
||||||
|
TKUnit.assertEqual(testObservable.get("property1"), testArray, "WrappedValue is used only to execute property change logic and unwrapped value should be used as proeprty value.");
|
||||||
}
|
}
|
||||||
31
data/observable/observable.d.ts
vendored
31
data/observable/observable.d.ts
vendored
@@ -29,6 +29,37 @@ declare module "data/observable" {
|
|||||||
*/
|
*/
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class that is used to fire property change even when real object is the same.
|
||||||
|
* By default property change will not be fired for a same object.
|
||||||
|
* By wrapping object into a WrappedValue instance `same object restriction` will be passed.
|
||||||
|
*/
|
||||||
|
class WrappedValue {
|
||||||
|
/**
|
||||||
|
* Property which holds the real value.
|
||||||
|
*/
|
||||||
|
wrapped: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of WrappedValue object.
|
||||||
|
* @param value - the real value which should be wrapped.
|
||||||
|
*/
|
||||||
|
constructor(value: any);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the real value of previously wrappedValue.
|
||||||
|
* @param value - Value that should be unwraped. If there is no wrappedValue property of the value object then value will be returned.
|
||||||
|
*/
|
||||||
|
static unwrap(value: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of WrappedValue. The actual instance is get from a WrappedValues pool.
|
||||||
|
* @param value - Value that should be wrapped.
|
||||||
|
*/
|
||||||
|
static wrap(value: any): WrappedValue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable is used when you want to be notified when a change occurs. Use on/off methods to add/remove listener.
|
* Observable is used when you want to be notified when a change occurs. Use on/off methods to add/remove listener.
|
||||||
|
|||||||
@@ -6,6 +6,45 @@ interface ListenerEntry {
|
|||||||
thisArg: any;
|
thisArg: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _wrappedIndex = 0;
|
||||||
|
|
||||||
|
export class WrappedValue implements definition.WrappedValue {
|
||||||
|
private _wrapped: any;
|
||||||
|
|
||||||
|
public get wrapped(): any {
|
||||||
|
return this._wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set wrapped(value) {
|
||||||
|
this._wrapped = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(value: any) {
|
||||||
|
this._wrapped = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unwrap(value: any) {
|
||||||
|
if (value && value.wrapped) {
|
||||||
|
return value.wrapped;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static wrap(value: any) {
|
||||||
|
var w = _wrappedValues[_wrappedIndex++ % 5];
|
||||||
|
w.wrapped = value;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _wrappedValues = [
|
||||||
|
new WrappedValue(null),
|
||||||
|
new WrappedValue(null),
|
||||||
|
new WrappedValue(null),
|
||||||
|
new WrappedValue(null),
|
||||||
|
new WrappedValue(null)
|
||||||
|
]
|
||||||
|
|
||||||
export class Observable implements definition.Observable {
|
export class Observable implements definition.Observable {
|
||||||
public static propertyChangeEvent = "propertyChange";
|
public static propertyChangeEvent = "propertyChange";
|
||||||
private _map: Map<string, Object>;
|
private _map: Map<string, Object>;
|
||||||
@@ -126,7 +165,8 @@ export class Observable implements definition.Observable {
|
|||||||
|
|
||||||
public _setCore(data: definition.PropertyChangeData) {
|
public _setCore(data: definition.PropertyChangeData) {
|
||||||
this.disableNotifications[data.propertyName] = true;
|
this.disableNotifications[data.propertyName] = true;
|
||||||
this[data.propertyName] = data.value;
|
let newValue = WrappedValue.unwrap(data.value);
|
||||||
|
this[data.propertyName] = newValue;
|
||||||
delete this.disableNotifications[data.propertyName];
|
delete this.disableNotifications[data.propertyName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import definition = require("ui/core/dependency-observable");
|
import definition = require("ui/core/dependency-observable");
|
||||||
import observable = require("data/observable");
|
import {Observable, WrappedValue} from "data/observable";
|
||||||
import types = require("utils/types");
|
import types = require("utils/types");
|
||||||
|
|
||||||
// use private variables in the scope of the module rather than static members of the class since a member is still accessible through JavaScript and may be changed.
|
// use private variables in the scope of the module rather than static members of the class since a member is still accessible through JavaScript and may be changed.
|
||||||
@@ -263,7 +263,7 @@ export class PropertyEntry implements definition.PropertyEntry {
|
|||||||
|
|
||||||
var defaultValueForPropertyPerType: Map<string, any> = new Map<string, any>();
|
var defaultValueForPropertyPerType: Map<string, any> = new Map<string, any>();
|
||||||
|
|
||||||
export class DependencyObservable extends observable.Observable {
|
export class DependencyObservable extends Observable {
|
||||||
private _propertyEntries = {};
|
private _propertyEntries = {};
|
||||||
|
|
||||||
public set(name: string, value: any) {
|
public set(name: string, value: any) {
|
||||||
@@ -285,8 +285,9 @@ export class DependencyObservable extends observable.Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public _setValue(property: Property, value: any, source?: number) {
|
public _setValue(property: Property, value: any, source?: number) {
|
||||||
if (!property.isValidValue(value)) {
|
let realValue = WrappedValue.unwrap(value);
|
||||||
throw new Error("Invalid value " + value + " for property " + property.name);
|
if (!property.isValidValue(realValue)) {
|
||||||
|
throw new Error("Invalid value " + realValue + " for property " + property.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.isUndefined(source)) {
|
if (types.isUndefined(source)) {
|
||||||
@@ -350,17 +351,18 @@ export class DependencyObservable extends observable.Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
|
public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
|
||||||
|
let realNewValue = WrappedValue.unwrap(newValue);
|
||||||
if (property.metadata.onValueChanged) {
|
if (property.metadata.onValueChanged) {
|
||||||
property.metadata.onValueChanged({
|
property.metadata.onValueChanged({
|
||||||
object: this,
|
object: this,
|
||||||
property: property,
|
property: property,
|
||||||
eventName: observable.Observable.propertyChangeEvent,
|
eventName: Observable.propertyChangeEvent,
|
||||||
newValue: newValue,
|
newValue: realNewValue,
|
||||||
oldValue: oldValue
|
oldValue: oldValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasListeners(observable.Observable.propertyChangeEvent)) {
|
if (this.hasListeners(Observable.propertyChangeEvent)) {
|
||||||
var changeData = super._createPropertyChangeData(property.name, newValue);
|
var changeData = super._createPropertyChangeData(property.name, newValue);
|
||||||
this.notify(changeData);
|
this.notify(changeData);
|
||||||
}
|
}
|
||||||
@@ -371,7 +373,7 @@ export class DependencyObservable extends observable.Observable {
|
|||||||
eventName: eventName,
|
eventName: eventName,
|
||||||
propertyName: property.name,
|
propertyName: property.name,
|
||||||
object: this,
|
object: this,
|
||||||
value: newValue
|
value: realNewValue
|
||||||
}
|
}
|
||||||
this.notify(ngChangedData);
|
this.notify(ngChangedData);
|
||||||
}
|
}
|
||||||
@@ -399,10 +401,10 @@ export class DependencyObservable extends observable.Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _setValueInternal(property: Property, value: any, source: number) {
|
private _setValueInternal(property: Property, value: any, source: number) {
|
||||||
|
let realValue = WrappedValue.unwrap(value);
|
||||||
// Convert the value to the real property type in case it is coming as a string from CSS or XML.
|
// Convert the value to the real property type in case it is coming as a string from CSS or XML.
|
||||||
if (types.isString(value) && property.valueConverter) {
|
if (types.isString(realValue) && property.valueConverter) {
|
||||||
value = property.valueConverter(value);
|
realValue = property.valueConverter(realValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry: PropertyEntry = this._propertyEntries[property.id];
|
var entry: PropertyEntry = this._propertyEntries[property.id];
|
||||||
@@ -415,21 +417,21 @@ export class DependencyObservable extends observable.Observable {
|
|||||||
|
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case ValueSource.Css:
|
case ValueSource.Css:
|
||||||
entry.cssValue = value;
|
entry.cssValue = realValue;
|
||||||
break;
|
break;
|
||||||
case ValueSource.Inherited:
|
case ValueSource.Inherited:
|
||||||
entry.inheritedValue = value;
|
entry.inheritedValue = realValue;
|
||||||
break;
|
break;
|
||||||
case ValueSource.Local:
|
case ValueSource.Local:
|
||||||
entry.localValue = value;
|
entry.localValue = realValue;
|
||||||
break;
|
break;
|
||||||
case ValueSource.VisualState:
|
case ValueSource.VisualState:
|
||||||
entry.visualStateValue = value;
|
entry.visualStateValue = realValue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var comparer: (x: any, y: any) => boolean = property.metadata.equalityComparer || this._defaultComparer;
|
var comparer: (x: any, y: any) => boolean = property.metadata.equalityComparer || this._defaultComparer;
|
||||||
if (!comparer(currentValue, entry.effectiveValue)) {
|
if ((value && value.wrapped) || !comparer(currentValue, entry.effectiveValue)) {
|
||||||
this._onPropertyChanged(property, currentValue, entry.effectiveValue);
|
this._onPropertyChanged(property, currentValue, entry.effectiveValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,4 +439,4 @@ export class DependencyObservable extends observable.Observable {
|
|||||||
private _defaultComparer(x: any, y: any): boolean {
|
private _defaultComparer(x: any, y: any): boolean {
|
||||||
return x === y;
|
return x === y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user