Fix binding support (#3489)

* Fix binding support

* fix tslint
This commit is contained in:
Hristo Hristov
2017-01-13 18:08:18 +02:00
committed by GitHub
parent 7f21eb06ac
commit 8cec512397
14 changed files with 492 additions and 503 deletions

View File

@@ -1,5 +1,5 @@
// >> observable-require
import * as observable from "data/observable";
import { Observable, fromObject, fromObjectRecursive, PropertyChangeData, EventData, WrappedValue } from "data/observable";
// << observable-require
import * as dependencyObservable from "ui/core/dependency-observable";
@@ -9,7 +9,7 @@ import * as proxy from "ui/core/proxy";
import { ObservableArray } from "data/observable-array";
var TESTED_NAME = "tested";
class TestObservable extends observable.Observable {
class TestObservable extends Observable {
public test() {
this._emit(TESTED_NAME);
}
@@ -22,7 +22,7 @@ export var test_Observable_Constructor = function () {
Age: 34,
Married: true
};
var person = new observable.Observable(json);
var person = fromObject(json);
var name = person.get("Name");
var age = person.get("Age");
var married = person.get("Married");
@@ -35,11 +35,11 @@ export var test_Observable_Constructor = function () {
export var tests_DummyTestForCodeSnippet = function () {
// >> observable-property-change
var person = new observable.Observable();
var person = new Observable();
person.set("Name", "John");
person.set("Age", 34);
person.set("Married", true);
person.addEventListener(observable.Observable.propertyChangeEvent, function (pcd: observable.PropertyChangeData) {
person.addEventListener(Observable.propertyChangeEvent, function (pcd: PropertyChangeData) {
//console.log(pcd.eventName.toString() + " " + pcd.propertyName.toString() + " " + pcd.value.toString());
});
person.set("Age", 35);
@@ -51,7 +51,7 @@ export var tests_DummyTestForCodeSnippet = function () {
}
export var test_Observable_Members = function () {
var obj = new observable.Observable();
var obj = new Observable();
TKUnit.assert(types.isDefined(obj.addEventListener), "Observable.addEventListener not defined");
TKUnit.assert(types.isDefined(obj._createPropertyChangeData), "Observable.createPropertyChangeData not defined");
TKUnit.assert(types.isDefined(obj._emit), "Observable.emit not defined");
@@ -62,19 +62,18 @@ export var test_Observable_Members = function () {
TKUnit.assert(types.isDefined(obj.on), "Observable.on not defined");
TKUnit.assert(types.isDefined(obj.removeEventListener), "Observable.removeEventListener not defined");
TKUnit.assert(types.isDefined(obj.set), "Observable.set not defined");
TKUnit.assert(types.isDefined(obj.typeName), "Observable.typeName not defined");
}
export var test_Observable_UpdateAnotherPropertyWithinChangedCallback = function () {
var obj = new observable.Observable();
var obj = new Observable();
var changedCallback = function (pcd: observable.PropertyChangeData) {
var changedCallback = function (pcd: PropertyChangeData) {
if (pcd.propertyName === "name") {
pcd.object.set("test", "Changed test");
}
}
obj.addEventListener(observable.Observable.propertyChangeEvent, changedCallback);
obj.addEventListener(Observable.propertyChangeEvent, changedCallback);
obj.set("name", "Initial name");
obj.set("test", "Initial test");
@@ -129,18 +128,18 @@ export var test_DependencyObservable_UpdateAnotherPropertyWithinChangedCallback
}
export var test_Observable_addEventListener_SingleEvent = function () {
var obj = new observable.Observable();
var obj = new Observable();
var receivedCount = 0;
var callback = function (data: observable.PropertyChangeData) {
var callback = function (data: PropertyChangeData) {
receivedCount++;
TKUnit.assert(data.eventName === observable.Observable.propertyChangeEvent, "Expected event name " + observable.Observable.propertyChangeEvent);
TKUnit.assert(data.eventName === Observable.propertyChangeEvent, "Expected event name " + Observable.propertyChangeEvent);
TKUnit.assert(data.object === obj, "PropertyChangeData.object value not valid.");
TKUnit.assert(data.propertyName === "testName", "PropertyChangeData.propertyName value not valid.");
TKUnit.assert(data.value === 1, "PropertyChangeData.value value not valid.");
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback);
obj.addEventListener(Observable.propertyChangeEvent, callback);
obj.set("testName", 1);
TKUnit.assert(receivedCount === 1, "PropertyChanged event not raised properly.");
}
@@ -149,13 +148,13 @@ export var test_Observable_addEventListener_MultipleEvents = function () {
var obj = new TestObservable();
var receivedCount = 0;
var callback = function (data: observable.EventData) {
var callback = function (data: EventData) {
receivedCount++;
TKUnit.assert(data.object === obj, "EventData.object value not valid.");
if (data.eventName === observable.Observable.propertyChangeEvent) {
var propertyData = <observable.PropertyChangeData>data;
TKUnit.assert(propertyData.eventName === observable.Observable.propertyChangeEvent, "Expected event name " + observable.Observable.propertyChangeEvent);
if (data.eventName === Observable.propertyChangeEvent) {
var propertyData = <PropertyChangeData>data;
TKUnit.assert(propertyData.eventName === Observable.propertyChangeEvent, "Expected event name " + Observable.propertyChangeEvent);
TKUnit.assert(propertyData.propertyName === "testName", "PropertyChangeData.propertyName value not valid.");
TKUnit.assert(propertyData.value === 1, "PropertyChangeData.value value not valid.");
} else {
@@ -163,7 +162,7 @@ export var test_Observable_addEventListener_MultipleEvents = function () {
}
}
var events = observable.Observable.propertyChangeEvent + "," + TESTED_NAME;
var events = Observable.propertyChangeEvent + "," + TESTED_NAME;
obj.addEventListener(events, callback);
obj.set("testName", 1);
obj.test();
@@ -174,13 +173,13 @@ export var test_Observable_addEventListener_MultipleEvents_ShouldTrim = function
var obj = new TestObservable();
var receivedCount = 0;
var callback = function (data: observable.EventData) {
var callback = function (data: EventData) {
receivedCount++;
}
var events = observable.Observable.propertyChangeEvent + " , " + TESTED_NAME;
var events = Observable.propertyChangeEvent + " , " + TESTED_NAME;
obj.addEventListener(events, callback);
TKUnit.assert(obj.hasListeners(observable.Observable.propertyChangeEvent), "Observable.addEventListener for multiple events should trim each event name.");
TKUnit.assert(obj.hasListeners(Observable.propertyChangeEvent), "Observable.addEventListener for multiple events should trim each event name.");
TKUnit.assert(obj.hasListeners(TESTED_NAME), "Observable.addEventListener for multiple events should trim each event name.");
obj.set("testName", 1);
@@ -193,16 +192,16 @@ export var test_Observable_addEventListener_MultipleCallbacks = function () {
var obj = new TestObservable();
var receivedCount = 0;
var callback1 = function (data: observable.EventData) {
var callback1 = function (data: EventData) {
receivedCount++;
}
var callback2 = function (data: observable.EventData) {
var callback2 = function (data: EventData) {
receivedCount++;
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback1);
obj.addEventListener(observable.Observable.propertyChangeEvent, callback2);
obj.addEventListener(Observable.propertyChangeEvent, callback1);
obj.addEventListener(Observable.propertyChangeEvent, callback2);
obj.set("testName", 1);
TKUnit.assert(receivedCount === 2, "The propertyChanged notification should be raised twice.");
@@ -212,15 +211,15 @@ export var test_Observable_addEventListener_MultipleCallbacks_MultipleEvents = f
var obj = new TestObservable();
var receivedCount = 0;
var callback1 = function (data: observable.EventData) {
var callback1 = function (data: EventData) {
receivedCount++;
}
var callback2 = function (data: observable.EventData) {
var callback2 = function (data: EventData) {
receivedCount++;
}
var events = observable.Observable.propertyChangeEvent + " , " + TESTED_NAME;
var events = Observable.propertyChangeEvent + " , " + TESTED_NAME;
obj.addEventListener(events, callback1);
obj.addEventListener(events, callback2);
@@ -231,47 +230,47 @@ export var test_Observable_addEventListener_MultipleCallbacks_MultipleEvents = f
}
export var test_Observable_removeEventListener_SingleEvent_SingleCallback = function () {
var obj = new observable.Observable();
var obj = new Observable();
var receivedCount = 0;
var callback = function (data: observable.EventData) {
var callback = function (data: EventData) {
receivedCount++;
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback);
obj.addEventListener(Observable.propertyChangeEvent, callback);
obj.set("testName", 1);
obj.removeEventListener(observable.Observable.propertyChangeEvent, callback);
TKUnit.assert(!obj.hasListeners(observable.Observable.propertyChangeEvent), "Observable.removeEventListener not working properly.");
obj.removeEventListener(Observable.propertyChangeEvent, callback);
TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), "Observable.removeEventListener not working properly.");
obj.set("testName", 2);
TKUnit.assert(receivedCount === 1, "Observable.removeEventListener not working properly.");
}
export var test_Observable_removeEventListener_SingleEvent_MultipleCallbacks = function () {
var obj = new observable.Observable();
var obj = new Observable();
var receivedCount = 0;
var callback1 = function (data: observable.EventData) {
var callback1 = function (data: EventData) {
receivedCount++;
}
var callback2 = function (data: observable.EventData) {
var callback2 = function (data: EventData) {
receivedCount++;
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback1);
obj.addEventListener(observable.Observable.propertyChangeEvent, callback2);
obj.addEventListener(Observable.propertyChangeEvent, callback1);
obj.addEventListener(Observable.propertyChangeEvent, callback2);
obj.set("testName", 1);
obj.removeEventListener(observable.Observable.propertyChangeEvent, callback1);
TKUnit.assert(obj.hasListeners(observable.Observable.propertyChangeEvent), "Observable.removeEventListener not working properly with multiple listeners.");
obj.removeEventListener(Observable.propertyChangeEvent, callback1);
TKUnit.assert(obj.hasListeners(Observable.propertyChangeEvent), "Observable.removeEventListener not working properly with multiple listeners.");
obj.set("testName", 2);
TKUnit.assert(receivedCount === 3, "Observable.removeEventListener not working properly with multiple listeners.");
obj.removeEventListener(observable.Observable.propertyChangeEvent, callback2);
TKUnit.assert(!obj.hasListeners(observable.Observable.propertyChangeEvent), "Observable.removeEventListener not working properly with multiple listeners.");
obj.removeEventListener(Observable.propertyChangeEvent, callback2);
TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), "Observable.removeEventListener not working properly with multiple listeners.");
obj.set("testName", 3);
TKUnit.assert(receivedCount === 3, "Observable.removeEventListener not working properly with multiple listeners.");
@@ -281,11 +280,11 @@ export var test_Observable_removeEventListener_MutlipleEvents_SingleCallback = f
var obj = new TestObservable();
var receivedCount = 0;
var callback = function (data: observable.EventData) {
var callback = function (data: EventData) {
receivedCount++;
}
var events = observable.Observable.propertyChangeEvent + " , " + TESTED_NAME;
var events = Observable.propertyChangeEvent + " , " + TESTED_NAME;
obj.addEventListener(events, callback);
obj.set("testName", 1);
@@ -293,7 +292,7 @@ export var test_Observable_removeEventListener_MutlipleEvents_SingleCallback = f
obj.removeEventListener(events, callback);
TKUnit.assert(!obj.hasListeners(observable.Observable.propertyChangeEvent), "Expected result for hasObservers is false");
TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), "Expected result for hasObservers is false");
TKUnit.assert(!obj.hasListeners(TESTED_NAME), "Expected result for hasObservers is false.");
obj.set("testName", 2);
@@ -306,21 +305,21 @@ export var test_Observable_removeEventListener_SingleEvent_NoCallbackSpecified =
var obj = new TestObservable();
var receivedCount = 0;
var callback1 = function (data: observable.EventData) {
var callback1 = function (data: EventData) {
receivedCount++;
}
var callback2 = function (data: observable.EventData) {
var callback2 = function (data: EventData) {
receivedCount++;
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback1);
obj.addEventListener(observable.Observable.propertyChangeEvent, callback2);
obj.addEventListener(Observable.propertyChangeEvent, callback1);
obj.addEventListener(Observable.propertyChangeEvent, callback2);
obj.set("testName", 1);
obj.removeEventListener(observable.Observable.propertyChangeEvent);
obj.removeEventListener(Observable.propertyChangeEvent);
TKUnit.assert(!obj.hasListeners(observable.Observable.propertyChangeEvent), "Expected result for hasObservers is false.");
TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), "Expected result for hasObservers is false.");
obj.set("testName", 2);
TKUnit.assert(receivedCount === 2, "Expected receive count is 2");
@@ -330,18 +329,18 @@ export var test_Observable_WhenCreatedWithJSON_PropertyChangedWithDotNotation_Ra
var json = {
count: 5
};
var obj = new observable.Observable(json);
var obj = fromObject(json);
var receivedCount = 0;
var callback = function (data: observable.PropertyChangeData) {
var callback = function (data: PropertyChangeData) {
receivedCount++;
TKUnit.assert(data.eventName === observable.Observable.propertyChangeEvent, "Expected event name " + observable.Observable.propertyChangeEvent);
TKUnit.assert(data.eventName === Observable.propertyChangeEvent, "Expected event name " + Observable.propertyChangeEvent);
TKUnit.assert(data.object === obj, "PropertyChangeData.object value not valid.");
TKUnit.assert(data.propertyName === "count", "PropertyChangeData.propertyName value not valid.");
TKUnit.assert(data.value === 6, "PropertyChangeData.value value not valid.");
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback);
obj.addEventListener(Observable.propertyChangeEvent, callback);
(<any>obj).count++;
@@ -352,18 +351,18 @@ export var test_Observable_WhenCreatedWithJSON_PropertyChangedWithBracketsNotati
var json = {
count: 5
};
var obj = new observable.Observable(json);
var obj = fromObject(json);
var receivedCount = 0;
var callback = function (data: observable.PropertyChangeData) {
var callback = function (data: PropertyChangeData) {
receivedCount++;
TKUnit.assert(data.eventName === observable.Observable.propertyChangeEvent, "Expected event name " + observable.Observable.propertyChangeEvent);
TKUnit.assert(data.eventName === Observable.propertyChangeEvent, "Expected event name " + Observable.propertyChangeEvent);
TKUnit.assert(data.object === obj, "PropertyChangeData.object value not valid.");
TKUnit.assert(data.propertyName === "count", "PropertyChangeData.propertyName value not valid.");
TKUnit.assert(data.value === 6, "PropertyChangeData.value value not valid.");
}
obj.addEventListener(observable.Observable.propertyChangeEvent, callback);
obj.addEventListener(Observable.propertyChangeEvent, callback);
obj["count"]++;
@@ -371,25 +370,25 @@ export var test_Observable_WhenCreatedWithJSON_PropertyChangedWithBracketsNotati
}
export var test_AddingTwoEventHandlersAndRemovingWithinHandlerShouldRaiseAllEvents = function () {
var observableInstance = new observable.Observable();
var observableInstance = new Observable();
var firstHandlerCalled = false;
var secondHandlerCalled = false;
var firstHandler = function (args) {
observableInstance.off(observable.Observable.propertyChangeEvent, firstHandler, firstObserver);
observableInstance.off(Observable.propertyChangeEvent, firstHandler, firstObserver);
firstHandlerCalled = true;
}
var secondHandler = function (args) {
observableInstance.off(observable.Observable.propertyChangeEvent, secondHandler, secondObserver);
observableInstance.off(Observable.propertyChangeEvent, secondHandler, secondObserver);
secondHandlerCalled = true;
}
var firstObserver = new observable.Observable();
var secondObserver = new observable.Observable();
var firstObserver = new Observable();
var secondObserver = new Observable();
observableInstance.on(observable.Observable.propertyChangeEvent, firstHandler, firstObserver);
observableInstance.on(observable.Observable.propertyChangeEvent, secondHandler, secondObserver);
observableInstance.on(Observable.propertyChangeEvent, firstHandler, firstObserver);
observableInstance.on(Observable.propertyChangeEvent, secondHandler, secondObserver);
observableInstance.set("someProperty", "some value");
@@ -400,8 +399,8 @@ export var test_AddingTwoEventHandlersAndRemovingWithinHandlerShouldRaiseAllEven
export var test_ObservableCreatedWithJSON_shouldDistinguishSeparateObjects = function () {
var obj1 = { val: 1 };
var obj2 = { val: 2 };
var observable1 = new observable.Observable(obj1);
var observable2 = new observable.Observable(obj2);
var observable1 = fromObject(obj1);
var observable2 = fromObject(obj2);
var val1 = observable1.get("val");
var val2 = observable2.get("val");
@@ -409,14 +408,14 @@ export var test_ObservableCreatedWithJSON_shouldDistinguishSeparateObjects = fun
var propName1;
var newValue1;
observable1.on(observable.Observable.propertyChangeEvent, (data: observable.PropertyChangeData) => {
observable1.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => {
propName1 = data.propertyName;
newValue1 = data.value;
});
var propName2;
var newValue2;
observable2.on(observable.Observable.propertyChangeEvent, (data: observable.PropertyChangeData) => {
observable2.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => {
propName2 = data.propertyName;
newValue2 = data.value;
});
@@ -435,8 +434,8 @@ export var test_ObservableCreatedWithJSON_shouldDistinguishSeparateObjects = fun
};
export var test_ObservablesCreatedWithJSON_shouldNotInterfereWithOneAnother = function () {
var observable1 = new observable.Observable({ property1: 1 });
var observable2 = new observable.Observable({ property2: 2 });
var observable1 = fromObject({ property1: 1 });
var observable2 = fromObject({ property2: 2 });
TKUnit.assert(observable1.get("property1") === 1, `Expected: 1; Actual: ${observable1.get("property1")}`);
TKUnit.assert(observable1.get("property2") === undefined, `Expected: undefined; Actual: ${observable1.get("property2")}`);
@@ -446,14 +445,14 @@ export var test_ObservablesCreatedWithJSON_shouldNotInterfereWithOneAnother = fu
var propName1;
var newValue1;
observable1.on(observable.Observable.propertyChangeEvent, (data: observable.PropertyChangeData) => {
observable1.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => {
propName1 = data.propertyName;
newValue1 = data.value;
});
var propName2;
var newValue2;
observable2.on(observable.Observable.propertyChangeEvent, (data: observable.PropertyChangeData) => {
observable2.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => {
propName2 = data.propertyName;
newValue2 = data.value;
});
@@ -468,12 +467,12 @@ export var test_ObservablesCreatedWithJSON_shouldNotInterfereWithOneAnother = fu
};
export function test_ObservablesCreatedWithJSON_shouldNotEmitTwoTimesPropertyChangeEvent() {
var testObservable = new observable.Observable({ "property1": 1 });
var testObservable = fromObject({ "property1": 1 });
var propertyChangeCounter = 0;
var propertyChangeHandler = function (args) {
propertyChangeCounter++;
}
testObservable.on(observable.Observable.propertyChangeEvent, propertyChangeHandler);
testObservable.on(Observable.propertyChangeEvent, propertyChangeHandler);
testObservable.set("property1", 2);
TKUnit.assertEqual(propertyChangeCounter, 1, "PropertyChange event should be fired only once for a single change.");
@@ -481,46 +480,46 @@ export function test_ObservablesCreatedWithJSON_shouldNotEmitTwoTimesPropertyCha
export function test_ObservableShouldEmitPropertyChangeWithSameObjectUsingWrappedValue() {
var testArray = [1];
var testObservable = new observable.Observable({ "property1": testArray});
var testObservable = fromObject({ "property1": testArray });
var propertyChangeCounter = 0;
var propertyChangeHandler = function (args) {
propertyChangeCounter++;
}
testObservable.on(observable.Observable.propertyChangeEvent, propertyChangeHandler);
testObservable.on(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));
testObservable.set("property1", 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 testObservable = fromObject({ "property1": testArray });
let actualArgsValue;
let propertyChangeHandler = function (args) {
actualArgsValue = args.value;
}
testObservable.on(observable.Observable.propertyChangeEvent, propertyChangeHandler);
testObservable.on(Observable.propertyChangeEvent, propertyChangeHandler);
testArray.push(2);
let wrappedArray = observable.WrappedValue.wrap(testArray);
let wrappedArray = WrappedValue.wrap(testArray);
testObservable.set("property1", wrappedArray);
TKUnit.assertEqual(actualArgsValue, wrappedArray, "PropertyChange event should be fired with correct value in arguments.");
TKUnit.assertEqual(actualArgsValue, testArray, "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 testObservable = fromObject({ "property1": testArray });
let wrappedArray = observable.WrappedValue.wrap(testArray);
let wrappedArray = WrappedValue.wrap(testArray);
testObservable.set("property1", wrappedArray);
@@ -529,7 +528,7 @@ export function test_CorrectPropertyValueAfterUsingWrappedValue() {
export function test_NestedObservablesWithObservableArrayShouldNotCrash() {
let someObservableArray = new ObservableArray<any>();
let testObservable = observable.fromObjectRecursive({
let testObservable = fromObjectRecursive({
firstProp: "test string",
secondProp: someObservableArray
});
@@ -537,7 +536,7 @@ export function test_NestedObservablesWithObservableArrayShouldNotCrash() {
}
export function test_NestedObservableWithNullShouldNotCrash() {
let testObservable = observable.fromObjectRecursive({
let testObservable = fromObjectRecursive({
someProperty: null
});
TKUnit.assert(testObservable !== undefined);

View File

@@ -44,7 +44,7 @@ allTests["TIMER"] = require("./timer-tests");
allTests["COLOR"] = require("./color-tests");
allTests["DEPENDENCY-OBSERVABLE"] = require("./ui/dependency-observable-tests");
// allTests["BINDABLE"] = require("./ui/bindable-tests");
allTests["BINDABLE"] = require("./ui/bindable-tests");
allTests["BINDING-EXPRESSIONS"] = require("./ui/binding-expressions-tests");
allTests["XML-PARSER"] = require("./xml-parser-tests/xml-parser-tests");
allTests["FORMATTEDSTRING"] = require("./text/formatted-string-tests");

View File

@@ -6,7 +6,7 @@ import { Label } from "ui/label";
import { Button } from "ui/button";
import { Page } from "ui/page";
import { View } from "ui/core/view";
import { Observable } from "data/observable";
import { fromObject } from "data/observable";
// >> actionbar-common-require
import * as actionBarModule from "ui/action-bar";
@@ -164,7 +164,7 @@ export function test_ActionBarItemBindingToEvent() {
const firstHandler = function () { firstHandlerCallCounter++; };
const secondHandler = function () { secondHandlerCallCounter++; };
page.bindingContext = new Observable({ "test": firstHandler });
page.bindingContext = fromObject({ "test": firstHandler });
const actionBarItem = page.actionBar.actionItems.getItemAt(0);

View File

@@ -1,4 +1,4 @@
import { Observable, fromObjectRecursive } from "data/observable";
import { Observable, fromObject, fromObjectRecursive } from "data/observable";
import { ViewBase } from "ui/core/view-base";
import { BindingOptions } from "ui/core/bindable";
import * as TKUnit from "../TKUnit";
@@ -25,7 +25,7 @@ import { TextField } from "ui/text-field";
// </snippet>
export function test_Bindable_Members() {
const obj = new ViewBase();
const obj = new Label();
TKUnit.assert(types.isDefined(obj.bind), "Bindable.bind not defined");
TKUnit.assert(types.isDefined(obj.unbind), "Bindable.unbind not defined");
};
@@ -36,17 +36,17 @@ export function test_Bindable_Bind_ToTarget_OneWay() {
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "test"
targetProperty: "text"
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options, model);
TKUnit.assert(obj.get("test") === "John", "Expected result after binding is [test value] === 'John'");
TKUnit.assert(obj.get("text") === "John", "Expected result after binding is [test value] === 'John'");
model.set("name", "Changed");
TKUnit.assert(obj.get("test") === "Changed", "Expected result after binding is [test value] === 'Changed'");
TKUnit.assert(obj.get("text") === "Changed", "Expected result after binding is [test value] === 'Changed'");
};
export function test_Bindable_Bind_ToTarget_TwoWay() {
@@ -55,19 +55,19 @@ export function test_Bindable_Bind_ToTarget_TwoWay() {
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "test",
targetProperty: "text",
twoWay: true
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options, model);
obj.set("test", "Changed");
obj.set("text", "Changed");
TKUnit.assertEqual(model.get("name"), "Changed", "Two-way binding not updating the source when target is changed.");
model.set("name", "John");
TKUnit.assertEqual(obj.get("test"), "John", "Two-way binding not updating the target when source is changed.");
TKUnit.assertEqual(obj.get("text"), "John", "Two-way binding not updating the target when source is changed.");
};
export function test_Bindable_Bind_ToBindingContext_OneWay() {
@@ -76,15 +76,15 @@ export function test_Bindable_Bind_ToBindingContext_OneWay() {
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "test"
targetProperty: "text"
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options);
obj.set("test", "local");
obj.set("text", "local");
obj.bindingContext = model;
TKUnit.assert(obj.get("test") === "John", "Binding to a context does not update the target property.");
TKUnit.assert(obj.get("text") === "John", "Binding to a context does not update the target property.");
};
export function test_Bindable_Bind_ToBindingContext_TwoWay() {
@@ -93,19 +93,19 @@ export function test_Bindable_Bind_ToBindingContext_TwoWay() {
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "test",
targetProperty: "text",
twoWay: true
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options);
obj.set("test", "local");
obj.set("text", "local");
obj.bindingContext = model;
TKUnit.assertEqual(obj.get("test"), "John", "Binding to a context does not update the target property.");
TKUnit.assertEqual(obj.get("text"), "John", "Binding to a context does not update the target property.");
obj.set("test", "local");
obj.set("text", "local");
TKUnit.assertEqual(model.get("name"), "local", "Two-way binding to a context does not update the source property.");
};
@@ -115,20 +115,20 @@ export function test_Bindable_Unbind() {
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "test"
targetProperty: "text"
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options, model);
model.set("name", "John");
TKUnit.assert(obj.get("test") === "John", "Binding does not updates target property.");
TKUnit.assert(obj.get("text") === "John", "Binding does not updates target property.");
obj.unbind("test");
obj.unbind("text");
model.set("name", "Changed");
TKUnit.assert(obj.get("test") === "John", "Unbind does not remove binding.");
TKUnit.assert(obj.get("text") === "John", "Unbind does not remove binding.");
};
export function test_bind_NoSource_WillUse_BindingContext() {
@@ -233,7 +233,7 @@ export function test_OneBindableToBindMoreThanOneProperty_ToSameSource() {
const firstPropertyOptions: BindingOptions = {
sourceProperty: "name",
targetProperty: "test"
targetProperty: "text"
};
const secondPropertyOptions: BindingOptions = {
@@ -241,14 +241,14 @@ export function test_OneBindableToBindMoreThanOneProperty_ToSameSource() {
targetProperty: "targetProperty"
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(firstPropertyOptions, model);
obj.bind(secondPropertyOptions, model);
model.set("name", "John");
model.set("sourceProperty", "testValue");
TKUnit.assertEqual(obj.get("test"), "John", "Binding does not updates target property.");
TKUnit.assertEqual(obj.get("text"), "John", "Binding does not updates target property.");
TKUnit.assertEqual(obj.get("targetProperty"), "testValue", "Binding does not updates target property1.");
}
@@ -260,10 +260,10 @@ export function test_MoreThanOneBindables_BindToASameSourceAndProperty() {
targetProperty: "targetProperty"
};
const obj1 = new ViewBase();
const obj1 = new Label();
obj1.bind(bindingOptions, model);
const obj2 = new ViewBase();
const obj2 = new Label();
obj2.bind(bindingOptions, model);
model.set("sourceProperty", "testValue");
@@ -285,7 +285,7 @@ class TestClass extends ViewBase {
}
};
export function test_WhenBindingSetsInvalidValue_NoExptionIsThrown() {
export function test_WhenBindingSetsInvalidValue_NoExceptionIsThrown() {
const model = new Observable();
const options: BindingOptions = {
@@ -357,27 +357,27 @@ export function test_binding_bindingContext_setBindingFirst() {
};
export function test_Bindable_BindingContext_Number_DoesNotThrow() {
const obj = new ViewBase();
const obj = new Label();
obj.bindingContext = 42;
};
export function test_Bindable_BindingContext_Boolean_DoesNotThrow() {
const obj = new ViewBase();
const obj = new Label();
obj.bindingContext = true;
};
export function test_Bindable_BindingContext_String_DoesNotThrow() {
const options: BindingOptions = {
sourceProperty: "length",
targetProperty: "test"
targetProperty: "text"
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options);
obj.set("test", "local");
obj.set("text", "local");
obj.bindingContext = "string";
TKUnit.assert(obj.get("test") === 6, "Expected: 6; Actual: " + obj.get("test"));
TKUnit.assert(obj.get("text") === 6, "Expected: 6; Actual: " + obj.get("text"));
};
export function test_getBindableOptionsFromStringFullFormat() {
@@ -483,7 +483,7 @@ export function test_bindingToNestedPropertyWithValueSyntax() {
const bindingSource = new Observable();
bindingSource.set("testProperty", "testValue");
const testElement = new ViewBase();
const testElement = new Label();
testElement.bind({
sourceProperty: "$value.testProperty",
targetProperty: "targetPropertyName"
@@ -499,8 +499,8 @@ export function test_TwoElementsBindingToSameBindingContext() {
const label1 = <Label>(page.getViewById("label1"));
const label2 = <Label>(page.getViewById("label2"));
TKUnit.assertEqual(upperStackLabel.text, label1.text);
TKUnit.assertEqual(upperStackLabel.text, label2.text);
TKUnit.assertEqual(upperStackLabel.text, label1.text, "label1");
TKUnit.assertEqual(upperStackLabel.text, label2.text, "label2");
};
const moduleName = __dirname.substr(fs.knownFolders.currentApp().path.length);
helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage"), null, testFunc);
@@ -662,28 +662,28 @@ export function test_UpdatingNestedPropertyViaBinding() {
viewModel.set("parentView", parentViewModel);
parentViewModel.set("name", expectedValue1);
const testElement: ViewBase = new ViewBase();
const testElement = new Label();
testElement.bind({
sourceProperty: "parentView.name",
targetProperty: "targetName",
targetProperty: "text",
twoWay: true
}, viewModel);
const testElement2: ViewBase = new ViewBase();
const testElement2 = new Label();
testElement2.bind({
sourceProperty: "parentView.name",
targetProperty: "targetProperty",
targetProperty: "text",
twoWay: true
}, viewModel);
TKUnit.assertEqual(testElement.get("targetName"), expectedValue1);
TKUnit.assertEqual(testElement.get("text"), expectedValue1);
testElement.set("targetName", expectedValue2);
testElement.set("text", expectedValue2);
TKUnit.assertEqual(parentViewModel.get("name"), expectedValue2);
TKUnit.assertEqual(testElement2.get("targetProperty"), expectedValue2);
TKUnit.assertEqual(testElement2.get("text"), expectedValue2);
}
class Person extends Observable {
@@ -754,7 +754,7 @@ export function test_NestedPropertiesBinding() {
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedValue, "Default First Name", "Default Last Name"));
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
@@ -783,7 +783,7 @@ export function test_WrongNestedPropertiesBinding() {
};
trace.addWriter(traceWriter);
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "activity.",
targetProperty: "targetProperty",
@@ -801,14 +801,14 @@ export function test_NestedPropertiesBindingTwoTargets() {
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new ViewBase();
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
@@ -836,14 +836,14 @@ export function test_NestedPropertiesBindingTwoTargetsAndSecondChange() {
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new ViewBase();
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
@@ -881,14 +881,14 @@ export function test_NestedPropertiesBindingTwoTargetsAndRegularChange() {
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new ViewBase();
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
@@ -925,14 +925,14 @@ export function test_NestedPropertiesBindingTwoTargetsAndReplacingSomeNestedObje
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new ViewBase();
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
@@ -965,7 +965,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndReplacingSomeNestedObje
export function test_NullSourcePropertyShouldNotCrash() {
const expectedValue = "Expected Value";
const target = new ViewBase();
const target = new Label();
const convFunc = function (value) {
return value + "Converted";
};
@@ -1024,7 +1024,7 @@ export function test_BindingHitsGetterTooManyTimes() {
const model = new Dummy();
model.dummyProperty = "OPA";
const bindableObj = new ViewBase();
const bindableObj = new Label();
bindableObj.bind({ sourceProperty: "dummyProperty", targetProperty: "dummyTarget" }, model);
@@ -1032,53 +1032,53 @@ export function test_BindingHitsGetterTooManyTimes() {
};
export function test_SupportFunctionsInExpressions() {
const model = new Observable({
const model = fromObject({
"anyColor": "red",
"isVisible": function () {
return this.get("anyColor") === "red";
}
});
const bindableObj = new ViewBase();
const bindableObj = new Label();
bindableObj.bind({
"sourceProperty": "$value",
"targetProperty": "test",
"targetProperty": "text",
"expression": "isVisible() ? 'visible' : 'collapsed'"
}, model);
model.set("anyColor", "blue");
TKUnit.assertEqual(bindableObj.get("test"), "collapsed", "When anyColor is blue test property should be collapsed.");
TKUnit.assertEqual(bindableObj.get("text"), "collapsed", "When anyColor is blue test property should be collapsed.");
model.set("anyColor", "red");
TKUnit.assertEqual(bindableObj.get("test"), "visible", "When anyColor is red test property should be visible.");
TKUnit.assertEqual(bindableObj.get("text"), "visible", "When anyColor is red test property should be visible.");
}
export function test_$ValueSupportWithinExpression() {
const model = new Observable({
const model = fromObject({
"anyColor": "red",
"isVisible": function () {
return this.get("anyColor") === "red";
}
});
const bindableObj = new ViewBase();
const bindableObj = new Label();
bindableObj.bind({
"sourceProperty": "$value",
"targetProperty": "test",
"targetProperty": "text",
"expression": "$value.anyColor === 'red' ? 'red' : 'blue'"
}, model);
model.set("anyColor", "blue");
TKUnit.assertEqual(bindableObj.get("test"), "blue", "When anyColor is blue test property should be blue too.");
TKUnit.assertEqual(bindableObj.get("text"), "blue", "When anyColor is blue test property should be blue too.");
model.set("anyColor", "red");
TKUnit.assertEqual(bindableObj.get("test"), "red", "When anyColor is red test property should be red too.");
TKUnit.assertEqual(bindableObj.get("text"), "red", "When anyColor is red test property should be red too.");
TKUnit.assertTrue(model['$value'] === undefined, "We should not add $value to binding context.");
}
@@ -1141,14 +1141,14 @@ export function test_BindingToPropertiesWithSameNames() {
secondsobject.secondsobject = 1;
model.item.secondsobject = secondsobject;
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "item.seconds",
targetProperty: "targetProperty",
twoWay: true
}, model);
const target2 = new ViewBase();
const target2 = new Label();
target2.bind({
sourceProperty: "item.secondsobject.secondsobject",
targetProperty: "targetProp",
@@ -1178,14 +1178,14 @@ export function test_BindingToPropertiesWithSameNamesSecondCase() {
secondsobject.secondsobject = 1;
model.item.secondsobject = secondsobject;
const target1 = new ViewBase();
const target1 = new Label();
target1.bind({
sourceProperty: "item.seconds",
targetProperty: "targetProperty",
twoWay: true
}, model);
const target2 = new ViewBase();
const target2 = new Label();
target2.bind({
sourceProperty: "item.secondsobject.secondsobject",
targetProperty: "targetProp",
@@ -1239,10 +1239,10 @@ export function test_BindingToRelatedProps() {
model.prop1 = false;
model.prop2 = "Alabala";
let target1 = new ViewBase();
let target1 = new Label();
target1.bind({ sourceProperty: 'prop1', targetProperty: 'targetProp1' }, model);
let target2 = new ViewBase();
let target2 = new Label();
target2.bind({ sourceProperty: 'prop2', targetProperty: 'targetProp2' }, model);
model.prop2 = "Tralala";
@@ -1253,11 +1253,11 @@ export function test_BindingToRelatedProps() {
export function test_only_Bindable_BindingContext_Null_DoesNotThrow() {
const options: BindingOptions = {
sourceProperty: "a.b",
targetProperty: "test"
targetProperty: "text"
};
const obj = new ViewBase();
const obj = new Label();
obj.bind(options);
obj.bindingContext = new Observable({ a: "b" });
obj.bindingContext = fromObject({ a: "b" });
obj.bindingContext = null;
}
@@ -1271,15 +1271,15 @@ export function test_Observable_from_nested_json_binds_correctly() {
}
});
const obj = new ViewBase();
const obj = new Label();
obj.bind({
sourceProperty: "firstObject.secondObject.dummyProperty",
targetProperty: "test"
targetProperty: "text"
}, model);
model.get("firstObject").get("secondObject").set("dummyProperty", expectedValue);
TKUnit.assertEqual(obj.get("test"), expectedValue);
TKUnit.assertEqual(obj.get("text"), expectedValue);
}
export function test_Observable_from_nested_json_binds_correctly_when_upper_object_is_changed() {
@@ -1292,15 +1292,15 @@ export function test_Observable_from_nested_json_binds_correctly_when_upper_obje
}
});
const obj = new ViewBase();
const obj = new Label();
obj.bind({
sourceProperty: "firstObject.secondObject.dummyProperty",
targetProperty: "test"
targetProperty: "text"
}, model);
model.get("firstObject").set("secondObject", new Observable({ "dummyProperty": expectedValue }));
model.get("firstObject").set("secondObject", fromObject({ "dummyProperty": expectedValue }));
TKUnit.assertEqual(obj.get("test"), expectedValue);
TKUnit.assertEqual(obj.get("text"), expectedValue);
}
export function test_BindingToBindingContextProperty_ShouldUseNewContext() {

View File

@@ -70,16 +70,6 @@ declare module "data/observable" {
*/
public static propertyChangeEvent: string;
/**
* [Deprecated please use static functions fromJSON or fromJSONRecursive instead] Creates an Observable instance and sets its properties according to the supplied JSON object.
*/
constructor(json?: any);
/**
* Gets the name of the constructor function for this instance. E.g. for a Button class this will return "Button".
*/
typeName: string;
/**
* A basic method signature to hook an event listener (shortcut alias to the addEventListener method).
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change").
@@ -145,7 +135,6 @@ declare module "data/observable" {
/**
* This method is intended to be overriden by inheritors to provide additional implementation.
*/
_setCore(data: PropertyChangeData);
_createPropertyChangeData(name: string, value: any): PropertyChangeData;
_emit(eventNames: string);
//@endprivate

View File

@@ -1,43 +1,28 @@
import * as types from "utils/types";
import * as definition from "data/observable";
import { Observable as ObservableDefinition, WrappedValue as WrappedValueDefinition, EventData, PropertyChangeData } from "data/observable";
interface ListenerEntry {
callback: (data: definition.EventData) => void;
callback: (data: EventData) => void;
thisArg: any;
}
var _wrappedIndex = 0;
let _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;
export class WrappedValue implements WrappedValueDefinition {
constructor(public wrapped: any) {
}
public static unwrap(value: any) {
if (value && value.wrapped) {
return value.wrapped;
}
return value;
return (value && value.wrapped) ? value.wrapped : value;
}
public static wrap(value: any) {
var w = _wrappedValues[_wrappedIndex++ % 5];
const w = _wrappedValues[_wrappedIndex++ % 5];
w.wrapped = value;
return w;
}
}
var _wrappedValues = [
let _wrappedValues = [
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null),
@@ -45,37 +30,26 @@ var _wrappedValues = [
new WrappedValue(null)
]
export class Observable implements definition.Observable {
export class Observable implements ObservableDefinition {
public static propertyChangeEvent = "propertyChange";
_map: Map<string, Object>;
private _observers = {};
constructor(source?: any) {
if (source) {
addPropertiesFromObject(this, source);
}
public get(name: string): any {
return this[name];
}
_defineNewProperty(propertyName: string): void {
Object.defineProperty(this, propertyName, {
get: function () {
return this._map.get(propertyName);
},
set: function (value) {
this._map.set(propertyName, value);
this.notify(this._createPropertyChangeData(propertyName, value));
},
enumerable: true,
configurable: true
});
public set(name: string, value: any) {
// TODO: Parameter validation
if (this[name] === value) {
return;
}
get typeName(): string {
return types.getClass(this);
const newValue = WrappedValue.unwrap(value);
this[name] = newValue;
this.notifyPropertyChange(name, newValue);
}
public on(eventNames: string, callback: (data: definition.EventData) => void, thisArg?: any) {
public on(eventNames: string, callback: (data: EventData) => void, thisArg?: any) {
this.addEventListener(eventNames, callback, thisArg);
}
@@ -83,18 +57,19 @@ export class Observable implements definition.Observable {
this.removeEventListener(eventNames, callback, thisArg);
}
public addEventListener(eventNames: string, callback: (data: definition.EventData) => void, thisArg?: any) {
if (!types.isString(eventNames)) {
public addEventListener(eventNames: string, callback: (data: EventData) => void, thisArg?: Object) {
if (typeof eventNames !== "string") {
throw new TypeError("Events name(s) must be string.");
}
types.verifyCallback(callback);
if (typeof callback !== "function") {
throw new TypeError("callback must be function.");
}
var events: Array<string> = eventNames.split(",");
for (var i = 0, l = events.length; i < l; i++) {
var event = events[i].trim();
var list = this._getEventList(event, true);
const events = eventNames.split(",");
for (let i = 0, l = events.length; i < l; i++) {
const event = events[i].trim();
const list = this._getEventList(event, true);
// TODO: Performance optimization - if we do not have the thisArg specified, do not wrap the callback in additional object (ObserveEntry)
list.push({
callback: callback,
@@ -103,19 +78,22 @@ export class Observable implements definition.Observable {
}
}
public removeEventListener(eventNames: string, callback?: any, thisArg?: any) {
if (!types.isString(eventNames)) {
public removeEventListener(eventNames: string, callback?: any, thisArg?: Object) {
if (typeof eventNames !== "string") {
throw new TypeError("Events name(s) must be string.");
}
var events: Array<string> = eventNames.split(",");
if (callback && typeof callback !== "function") {
throw new TypeError("callback must be function.");
}
for (var i = 0, l = events.length; i < l; i++) {
var event = events[i].trim();
const events = eventNames.split(",");
for (let i = 0, l = events.length; i < l; i++) {
const event = events[i].trim();
if (callback) {
var list = this._getEventList(event, false);
const list = this._getEventList(event, false);
if (list) {
var index = this._indexOfListener(list, callback, thisArg);
const index = this._indexOfListener(list, callback, thisArg);
if (index >= 0) {
list.splice(index, 1);
}
@@ -131,54 +109,14 @@ export class Observable implements definition.Observable {
}
}
public notifyPropertyChange(propertyName: string, newValue: any) {
this.notify(this._createPropertyChangeData(propertyName, newValue));
}
public set(name: string, value: any) {
// TODO: Parameter validation
if (this[name] === value) {
return;
}
// create data for the change
var data = this._createPropertyChangeData(name, value);
this._setCore(data);
this.notify(data);
// TODO: Maybe we need to update source object used in the constructor as well?
}
public get(name: string): any {
return this[name];
}
//private disableNotifications = false;
private disableNotifications = {};
public _setCore(data: definition.PropertyChangeData) {
this.disableNotifications[data.propertyName] = true;
let newValue = WrappedValue.unwrap(data.value);
this[data.propertyName] = newValue;
delete this.disableNotifications[data.propertyName];
}
public notify<T extends definition.EventData>(data: T) {
if (this.disableNotifications[(<any>data).propertyName]) {
return;
}
var observers = this._getEventList(data.eventName);
public notify<T extends EventData>(data: T) {
const observers = this._getEventList(data.eventName);
if (!observers) {
return;
}
var i;
var entry: ListenerEntry;
var observersLength = observers.length;
for (i = observersLength - 1; i >= 0; i--) {
entry = observers[i];
for (let i = observers.length - 1; i >= 0; i--) {
let entry = observers[i];
if (entry.thisArg) {
entry.callback.apply(entry.thisArg, [data]);
} else {
@@ -187,11 +125,15 @@ export class Observable implements definition.Observable {
}
}
public notifyPropertyChange(name: string, newValue: any) {
this.notify(this._createPropertyChangeData(name, newValue));
}
public hasListeners(eventName: string) {
return eventName in this._observers;
}
public _createPropertyChangeData(name: string, value: any): definition.PropertyChangeData {
public _createPropertyChangeData(name: string, value: any): PropertyChangeData {
return {
eventName: Observable.propertyChangeEvent,
propertyName: name,
@@ -201,10 +143,10 @@ export class Observable implements definition.Observable {
}
public _emit(eventNames: string) {
var events: Array<string> = eventNames.split(",");
const events = eventNames.split(",");
for (var i = 0, l = events.length; i < l; i++) {
var event = events[i].trim();
for (let i = 0, l = events.length; i < l; i++) {
const event = events[i].trim();
this.notify({ eventName: event, object: this });
}
}
@@ -214,7 +156,7 @@ export class Observable implements definition.Observable {
throw new TypeError("EventName must be valid string.");
}
var list = <Array<ListenerEntry>>this._observers[eventName];
let list = <Array<ListenerEntry>>this._observers[eventName];
if (!list && createIfNeeded) {
list = [];
this._observers[eventName] = list;
@@ -223,12 +165,9 @@ export class Observable implements definition.Observable {
return list;
}
private _indexOfListener(list: Array<ListenerEntry>, callback: (data: definition.EventData) => void, thisArg?: any): number {
var i;
var entry: ListenerEntry;
for (i = 0; i < list.length; i++) {
entry = list[i];
private _indexOfListener(list: Array<ListenerEntry>, callback: (data: EventData) => void, thisArg?: any): number {
for (let i = 0; i < list.length; i++) {
const entry = list[i];
if (thisArg) {
if (entry.callback === callback && entry.thisArg === thisArg) {
return i;
@@ -243,36 +182,59 @@ export class Observable implements definition.Observable {
return -1;
}
}
public toString(): string {
return this.typeName;
class ObservableFromObject extends Observable {
public _map: Map<string, Object> = new Map<string, Object>();
public set(name: string, value: any) {
const currentValue = this._map.get(name);
if (currentValue === value) {
return;
}
const newValue = WrappedValue.unwrap(value);
this._map.set(name, newValue);
this.notifyPropertyChange(name, newValue);
}
}
function addPropertiesFromObject(observable: Observable, source: any, recursive?: boolean) {
let isRecursive = recursive || false;
observable._map = new Map<string, Object>();
function defineNewProperty(target: ObservableFromObject, propertyName: string): void {
Object.defineProperty(target, propertyName, {
get: function () {
return target._map.get(propertyName);
},
set: function (value) {
target.set(propertyName, value);
},
enumerable: true,
configurable: true
});
}
function addPropertiesFromObject(observable: ObservableFromObject, source: any, recursive: boolean = false) {
let isRecursive = recursive;
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
if (isRecursive) {
if (!Array.isArray(source[prop]) && source[prop] && typeof source[prop] === 'object' && types.getClass(source[prop]) !== 'ObservableArray') {
if (!Array.isArray(source[prop]) && source[prop] && typeof source[prop] === 'object' && !(source[prop] instanceof Observable)) {
source[prop] = fromObjectRecursive(source[prop]);
}
}
observable._defineNewProperty(prop);
defineNewProperty(observable, prop);
observable.set(prop, source[prop]);
}
}
}
export function fromObject(source: any): Observable {
let observable = new Observable();
let observable = new ObservableFromObject();
addPropertiesFromObject(observable, source, false);
return observable;
}
export function fromObjectRecursive(source: any): Observable {
let observable = new Observable();
let observable = new ObservableFromObject();
addPropertiesFromObject(observable, source, true);
return observable;
}

View File

@@ -1,5 +1,4 @@
import { ActivityIndicatorBase, busyProperty, colorProperty } from "./activity-indicator-common";
import { ios } from "utils/utils";
import { Color } from "color";
export * from "./activity-indicator-common";

View File

@@ -1,27 +1,15 @@
import * as definition from "ui/core/bindable";
import { Observable, PropertyChangeData } from "data/observable";
import { unsetValue, DependencyObservable, Property } from "ui/core/dependency-observable";
import { unsetValue, DependencyObservable } from "ui/core/dependency-observable";
import { addWeakEventListener, removeWeakEventListener } from "ui/core/weak-event-listener";
import types = require("utils/types");
import bindingBuilder = require("../builder/binding-builder");
import { ViewBase, isEventOrGesture, bindingContextProperty } from "ui/core/view-base";
import * as application from "application";
import * as polymerExpressions from "js-libs/polymer-expressions";
import * as specialProperties from "ui/builder/special-properties";
import * as utils from "utils/utils";
import { enabled as traceEnabled, write as traceWrite, categories as traceCategories, messageType as traceMessageType } from "trace";
// let bindingContextProperty = new Property(
// "bindingContext",
// "Bindable",
// new PropertyMetadata(undefined, PropertyMetadataSettings.Inheritable, onBindingContextChanged)
// );
// function onBindingContextChanged(data: DependencyPropertyChangeData) {
// let bindable = <Bindable>data.object;
// bindable._onBindingContextChanged(data.oldValue, data.newValue);
// }
import { write as traceWrite, categories as traceCategories, messageType as traceMessageType } from "trace";
let contextKey = "context";
// this regex is used to get parameters inside [] for example:
@@ -75,72 +63,72 @@ export class Bindable extends DependencyObservable implements definition.Bindabl
}
}
public _updateTwoWayBinding(propertyName: string, value: any) {
let binding: Binding = this.bindings.get(propertyName);
if (binding) {
binding.updateTwoWay(value);
}
}
public _setCore(data: PropertyChangeData) {
super._setCore(data);
this._updateTwoWayBinding(data.propertyName, data.value);
}
public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
if (traceEnabled) {
traceWrite(`${this}._onPropertyChanged(${property.name}, ${oldValue}, ${newValue})`, traceCategories.Binding);
}
super._onPropertyChanged(property, oldValue, newValue);
// if (this instanceof viewModule.View) {
// if (property.inheritable && (<viewModule.View>(<any>this))._isInheritedChange() === true) {
// return;
// public _updateTwoWayBinding(propertyName: string, value: any) {
// let binding: Binding = this.bindings.get(propertyName);
// if (binding) {
// binding.updateTwoWay(value);
// }
// }
let binding = this.bindings.get(property.name);
if (binding && !binding.updating) {
if (binding.options.twoWay) {
if (traceEnabled) {
traceWrite(`${this}._updateTwoWayBinding(${property.name}, ${newValue});` + property.name, traceCategories.Binding);
}
this._updateTwoWayBinding(property.name, newValue);
}
else {
if (traceEnabled) {
traceWrite(`${this}.unbind(${property.name});`, traceCategories.Binding);
}
this.unbind(property.name);
}
}
// public _setCore(data: PropertyChangeData) {
// super._setCore(data);
// this._updateTwoWayBinding(data.propertyName, data.value);
// }
// public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
// if (traceEnabled) {
// traceWrite(`${this}._onPropertyChanged(${property.name}, ${oldValue}, ${newValue})`, traceCategories.Binding);
// }
// super._onPropertyChanged(property, oldValue, newValue);
// // if (this instanceof viewModule.View) {
// // if (property.inheritable && (<viewModule.View>(<any>this))._isInheritedChange() === true) {
// // return;
// // }
// // }
// let binding = this.bindings.get(property.name);
// if (binding && !binding.updating) {
// if (binding.options.twoWay) {
// if (traceEnabled) {
// traceWrite(`${this}._updateTwoWayBinding(${property.name}, ${newValue});` + property.name, traceCategories.Binding);
// }
// this._updateTwoWayBinding(property.name, newValue);
// }
// else {
// if (traceEnabled) {
// traceWrite(`${this}.unbind(${property.name});`, traceCategories.Binding);
// }
// this.unbind(property.name);
// }
// }
// }
// public _onBindingContextChanged(oldValue: any, newValue: any) {
// let bindingContextBinding = this.bindings.get("bindingContext");
// if (bindingContextBinding) {
// if (!bindingContextBinding.updating) {
// bindingContextBinding.bind(newValue);
// }
// }
// let bindingContextSource = this.bindingContext;
// this.bindings.forEach((binding, index, bindings) => {
// if (!binding.updating && binding.sourceIsBindingContext && binding.options.targetProperty !== "bindingContext") {
// if (traceEnabled) {
// traceWrite(`Binding ${binding.target.get()}.${binding.options.targetProperty} to new context ${bindingContextSource}`, traceCategories.Binding);
// }
// if (!types.isNullOrUndefined(bindingContextSource)) {
// binding.bind(bindingContextSource);
// } else {
// binding.clearBinding();
// }
// }
// });
// }
}
public _onBindingContextChanged(oldValue: any, newValue: any) {
let bindingContextBinding = this.bindings.get("bindingContext");
if (bindingContextBinding) {
if (!bindingContextBinding.updating) {
bindingContextBinding.bind(newValue);
}
}
let bindingContextSource = this.bindingContext;
this.bindings.forEach((binding, index, bindings) => {
if (!binding.updating && binding.sourceIsBindingContext && binding.options.targetProperty !== "bindingContext") {
if (traceEnabled) {
traceWrite(`Binding ${binding.target.get()}.${binding.options.targetProperty} to new context ${bindingContextSource}`, traceCategories.Binding);
}
if (!types.isNullOrUndefined(bindingContextSource)) {
binding.bind(bindingContextSource);
} else {
binding.clearBinding();
}
}
});
}
}
let emptyArray = [];
const emptyArray = [];
function getProperties(property: string): Array<string> {
let result: Array<string> = emptyArray;
if (property) {
@@ -188,13 +176,24 @@ export class Binding {
if (!this.targetOptions) {
throw new Error(`Invalid property: ${options.targetProperty} for target: ${target}`);
}
if (options.twoWay) {
const target = this.targetOptions.instance.get();
if (target instanceof Observable) {
target.on(`${this.targetOptions.property}Change`, this.onTargetPropertyChanged, this);
}
}
}
private onTargetPropertyChanged(data: PropertyChangeData): void {
this.updateTwoWay(data.value);
}
public loadedHandlerVisualTreeBinding(args) {
let target = args.object;
target.off("loaded", this.loadedHandlerVisualTreeBinding, this);
if (!types.isNullOrUndefined(target.bindingContext)) {
this.bind(target.bindingContext);
this.update(target.bindingContext);
}
};
@@ -237,25 +236,39 @@ export class Binding {
}
private bindingContextChanged(data: PropertyChangeData): void {
let target = this.target.get();
const target = this.targetOptions.instance.get();
if (!target) {
this.unbind();
return;
}
if (data.value) {
this.bind(data.value);
this.update(data.value);
} else {
// TODO: Is this correct?
// What should happen when bindingContext is null/undefined?
this.clearBinding();
}
// TODO: if oneWay - call target.unbind();
}
public bind(source: Object): void {
public bind(source: any): void {
const target = this.targetOptions.instance.get();
if (this.sourceIsBindingContext && target instanceof Observable && this.targetOptions.property !== "bindingContext") {
target.on("bindingContextChange", this.bindingContextChanged, this);
}
this.update(source);
}
private update(source: any): void {
this.clearSource();
source = this.sourceAsObject(source);
if (!types.isNullOrUndefined(source)) {
// TODO: if oneWay - call target.unbind();
this.source = new WeakRef(source);
this.sourceOptions = this.resolveOptions(source, this.sourceProperties);
@@ -263,14 +276,36 @@ export class Binding {
this.updateTarget(sourceValue);
this.addPropertyChangeListeners(this.source, this.sourceProperties);
} else if (!this.sourceIsBindingContext) {
// TODO: if oneWay - call target.unbind();
let sourceValue = this.getSourcePropertyValue();
this.updateTarget(sourceValue ? sourceValue : source);
} else if (this.sourceIsBindingContext && (source === undefined || source === null)) {
this.target.get().off("bindingContextChange", this.bindingContextChanged, this);
this.target.get().on("bindingContextChange", this.bindingContextChanged, this);
}
}
public unbind() {
const target = this.targetOptions.instance.get();
if (target instanceof Observable) {
if (this.options.twoWay) {
target.off(`${this.targetOptions.property}Change`, this.onTargetPropertyChanged, this);
}
if (this.sourceIsBindingContext && this.targetOptions.property !== "bindingContext") {
target.off("bindingContextChange", this.bindingContextChanged, this);
}
}
if (this.targetOptions) {
this.targetOptions = undefined;
}
this.sourceProperties = undefined;
if (!this.source) {
return;
}
this.clearSource();
}
// Consider returning single {} instead of array for performance.
private resolveObjectsAndProperties(source: Object, properties: Array<string>): Array<{ instance: Object; property: string }> {
let result = [];
@@ -330,24 +365,6 @@ export class Binding {
}
}
public unbind() {
if (!this.source) {
return;
}
this.clearSource();
let target = this.target.get();
if (target) {
target.off(`${bindingContextProperty}Change`, this.bindingContextChanged, this);
}
if (this.targetOptions) {
this.targetOptions = undefined;
}
this.sourceProperties = undefined;
}
private prepareExpressionForUpdate(): string {
// this regex is used to create a valid RegExp object from a string that has some special regex symbols like [,(,$ and so on.
// Basically this method replaces all matches of 'source property' in expression with '$newPropertyValue'.
@@ -361,7 +378,7 @@ export class Binding {
return resultExp;
}
public updateTwoWay(value: any) {
private updateTwoWay(value: any) {
if (this.updating || !this.options.twoWay) {
return;
}
@@ -432,8 +449,8 @@ export class Binding {
}
public onSourcePropertyChanged(data: PropertyChangeData) {
let sourceProps = this.sourceProperties;
let sourcePropsLength = sourceProps.length;
const sourceProps = this.sourceProperties;
const sourcePropsLength = sourceProps.length;
let changedPropertyIndex = sourceProps.indexOf(data.propertyName);
let parentProps = "";
if (changedPropertyIndex > -1) {
@@ -445,16 +462,16 @@ export class Binding {
}
if (this.options.expression) {
let expressionValue = this._getExpressionValue(this.options.expression, false, undefined);
const expressionValue = this._getExpressionValue(this.options.expression, false, undefined);
if (expressionValue instanceof Error) {
traceWrite((<Error>expressionValue).message, traceCategories.Binding, traceMessageType.error);
traceWrite(expressionValue.message, traceCategories.Binding, traceMessageType.error);
} else {
this.updateTarget(expressionValue);
}
} else {
if (changedPropertyIndex > -1) {
let props = sourceProps.slice(changedPropertyIndex + 1);
let propsLength = props.length;
const props = sourceProps.slice(changedPropertyIndex + 1);
const propsLength = props.length;
if (propsLength > 0) {
let value = data.value;
for (let i = 0; i < propsLength; i++) {
@@ -468,15 +485,15 @@ export class Binding {
}
}
// we need to do this only if nested objects are used as source and some middle object is changed.
// we need to do this only if nested objects are used as source and some middle object has changed.
if (changedPropertyIndex > -1 && changedPropertyIndex < sourcePropsLength - 1) {
var probablyChangedObject = this.propertyChangeListeners.get(parentProps);
const probablyChangedObject = this.propertyChangeListeners.get(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 (let i = sourcePropsLength - 1; i > changedPropertyIndex; i--) {
let prop = "$" + sourceProps.slice(0, i + 1).join("$");
const prop = "$" + sourceProps.slice(0, i + 1).join("$");
if (this.propertyChangeListeners.has(prop)) {
removeWeakEventListener(
this.propertyChangeListeners.get(prop),
@@ -487,9 +504,9 @@ export class Binding {
}
}
let newProps = sourceProps.slice(changedPropertyIndex + 1);
const newProps = sourceProps.slice(changedPropertyIndex + 1);
// add new weak event listeners
var newObject = data.object[sourceProps[changedPropertyIndex]]
const newObject = data.object[sourceProps[changedPropertyIndex]]
if (!types.isNullOrUndefined(newObject) && typeof newObject === 'object') {
this.addPropertyChangeListeners(new WeakRef(newObject), newProps, parentProps);
}
@@ -559,7 +576,7 @@ export class Binding {
public clearBinding() {
this.clearSource();
this.updateTarget(undefined);
this.updateTarget(unsetValue);
}
private updateTarget(value: any) {
@@ -638,25 +655,19 @@ export class Binding {
this.updating = true;
try {
if (optionsInstance instanceof ViewBase &&
const isView = optionsInstance instanceof ViewBase
if (isView &&
isEventOrGesture(options.property, <any>optionsInstance) &&
types.isFunction(value)) {
// calling off method with null as handler will remove all handlers for options.property event
optionsInstance.off(options.property, null, optionsInstance.bindingContext);
optionsInstance.on(options.property, value, optionsInstance.bindingContext);
} else {
let specialSetter = specialProperties.getSpecialPropertySetter(options.property);
if (specialSetter) {
specialSetter(optionsInstance, value);
} else {
if (optionsInstance instanceof Observable) {
} else if (!isView && optionsInstance instanceof Observable) {
optionsInstance.set(options.property, value);
} else {
optionsInstance[options.property] = value;
}
}
}
}
catch (ex) {
traceWrite("Binding error while setting property " + options.property + " of " + optionsInstance + ": " + ex,
traceCategories.Binding,

View File

@@ -286,8 +286,7 @@ export class DependencyObservable extends Observable implements DependencyObserv
let propName = property.name;
if (this.hasListeners(Observable.propertyChangeEvent)) {
let changeData = super._createPropertyChangeData(propName, newValue);
this.notify(changeData);
this.notifyPropertyChange(propName, newValue);
}
let eventName = property.nameEvent;
@@ -325,10 +324,6 @@ export class DependencyObservable extends Observable implements DependencyObserv
}
}
public toString(): string {
return this.typeName;
}
private _setValueInternal(property: Property, value: any, source: number) {
if (value === unsetValue) {
this._resetValue(property, source);

View File

@@ -322,8 +322,6 @@ export class InheritedProperty<T extends ViewBase, U> extends Property<T, U> imp
const key = this.key;
const defaultValue = options.defaultValue;
const eventName = name + "Change";
const sourceKey = Symbol(name + ":valueSourceKey");
this.sourceKey = sourceKey;
@@ -360,16 +358,6 @@ export class InheritedProperty<T extends ViewBase, U> extends Property<T, U> imp
that[sourceKey] = newValueSource;
if (currentValue !== newValue) {
if (this.hasListeners(eventName)) {
this.notify({
eventName: eventName,
propertyName: name,
object: this,
value: unboxedValue
});
}
const reset = newValueSource === ValueSource.Default;
that.eachChild((child) => {
const childValueSource = child[sourceKey] || ValueSource.Default;
@@ -379,7 +367,7 @@ export class InheritedProperty<T extends ViewBase, U> extends Property<T, U> imp
}
} else {
if (childValueSource <= ValueSource.Inherited) {
setInheritedValue.call(child, child.parent[key]);
setInheritedValue.call(child, newValue);
}
}
return true;

View File

@@ -1,5 +1,5 @@
import { ViewBase as ViewBaseDefinition } from "ui/core/view-base";
import { Observable, EventData } from "data/observable";
import { Observable, EventData, PropertyChangeData } from "data/observable";
import { Property, InheritedProperty, Style, clearInheritedProperties, propagateInheritedProperties, resetCSSProperties, applyNativeSetters, resetStyleProperties } from "./properties";
import { Binding, BindingOptions, Bindable } from "ui/core/bindable";
import { isIOS, isAndroid } from "platform";
@@ -9,6 +9,9 @@ import { KeyframeAnimation } from "ui/animation/keyframe-animation";
import { enabled as traceEnabled, write as traceWrite, categories as traceCategories, notifyEvent as traceNotifyEvent, isCategorySet } from "trace";
// TODO: Remove this import!
import * as types from "utils/types";
import * as ssm from "ui/styling/style-scope";
let styleScopeModule: typeof ssm;
function ensureStyleScopeModule() {
@@ -46,6 +49,8 @@ export function getEventOrGestureName(name: string): string {
return name.indexOf("on") === 0 ? name.substr(2, name.length - 2) : name;
}
// TODO: Make this instance function so that we dont need public statc tapEvent = "tap"
// in controls. They will just override this one and provide their own event support.
export function isEventOrGesture(name: string, view: ViewBaseDefinition): boolean {
if (typeof name === "string") {
let eventOrGestureName = getEventOrGestureName(name);
@@ -127,6 +132,10 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
this._style = new Style(this);
}
get typeName(): string {
return types.getClass(this);
}
get style(): Style {
return this._style;
}
@@ -348,39 +357,63 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
}
}
private bindings = new Map<string, Binding>();
public bind(options: BindingOptions, source: Object = defaultBindingSource): void {
let binding: Binding = this.bindings.get(options.targetProperty);
if (binding) {
binding.unbind();
private bindingContextChanged(data: PropertyChangeData): void {
this.bindings.get("bindingContext").bind(data.value);
}
binding = new Binding(this, options);
this.bindings.set(options.targetProperty, binding);
private bindings: Map<string, Binding>;
private shouldAddHandlerToParentBindingContextChanged: boolean;
private bindingContextBoundToParentBindingContextChanged: boolean;
public bind(options: BindingOptions, source: Object = defaultBindingSource): void {
const targetProperty = options.targetProperty;
this.unbind(targetProperty);
if (!this.bindings) {
this.bindings = new Map<string, Binding>();
}
const binding = new Binding(this, options);
this.bindings.set(targetProperty, binding);
let bindingSource = source;
if (bindingSource === defaultBindingSource) {
bindingSource = this.bindingContext;
binding.sourceIsBindingContext = true;
if (targetProperty === "bindingContext") {
this.bindingContextBoundToParentBindingContextChanged = true;
const parent = this.parent;
if (parent) {
parent.on("bindingContextChange", this.bindingContextChanged, this);
} else {
this.shouldAddHandlerToParentBindingContextChanged = true;
}
}
}
// if (!types.isNullOrUndefined(bindingSource)) {
binding.bind(bindingSource);
// }
}
public unbind(property: string): void {
let binding: Binding = this.bindings.get(property);
if (binding) {
binding.unbind();
this.bindings.delete(property);
}
const bindings = this.bindings;
if (!bindings) {
return;
}
public _updateTwoWayBinding(propertyName: string, value: any) {
let binding: Binding = this.bindings.get(propertyName);
const binding = bindings.get(property);
if (binding) {
binding.updateTwoWay(value);
binding.unbind();
bindings.delete(property);
if (binding.sourceIsBindingContext) {
if (property === "bindingContext") {
this.shouldAddHandlerToParentBindingContextChanged = false;
this.bindingContextBoundToParentBindingContextChanged = false;
const parent = this.parent;
if (parent) {
parent.off("bindingContextChange", this.bindingContextChanged, this);
}
}
}
}
}
@@ -593,8 +626,14 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
public _parentChanged(oldParent: ViewBase): void {
//Overridden
if (oldParent) {
// Move these method in property class.
clearInheritedProperties(this);
if (this.bindingContextBoundToParentBindingContextChanged) {
oldParent.parent.off("bindingContextChange", this.bindingContextChanged, this);
}
} else if (this.shouldAddHandlerToParentBindingContextChanged) {
const parent = this.parent;
parent.on("bindingContextChange", this.bindingContextChanged, this);
this.bindings.get("bindingContext").bind(parent.bindingContext);
}
}

View File

@@ -54,6 +54,11 @@ declare module "ui/core/view-base" {
public nativeView: any;
public bindingContext: any;
/**
* Gets the name of the constructor function for this instance. E.g. for a Button class this will return "Button".
*/
public typeName: string;
/**
* Gets the parent view. This property is read-only.
*/

View File

@@ -52,7 +52,7 @@ export class Progress extends ProgressBase {
}
}
get [backgroundColorProperty.native](): UIColor {
get [backgroundColorProperty.native](): number {
return null;
}
set [backgroundColorProperty.native](value: Color) {
@@ -73,7 +73,7 @@ export class Progress extends ProgressBase {
}
}
get [backgroundInternalProperty.native](): UIColor {
get [backgroundInternalProperty.native](): number {
return null;
}
set [backgroundInternalProperty.native](value: Color) {

View File

@@ -117,6 +117,8 @@ export class ProxyViewContainer extends LayoutBase implements ProxyViewContainer
* Register/unregister existing children with the parent layout.
*/
public _parentChanged(oldParent: View): void {
// call super in order to execute base logic like clear inherited properties, etc.
super._parentChanged(oldParent);
const addingToParent = this.parent && !oldParent;
const newLayout = <LayoutBase>this.parent;
const oldLayout = <LayoutBase>oldParent;