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

@@ -29,7 +29,7 @@ declare module "data/observable" {
*/
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.
@@ -40,26 +40,26 @@ declare module "data/observable" {
* 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.
@@ -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;
export class WrappedValue implements WrappedValueDefinition {
constructor(public wrapped: any) {
}
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;
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];
}
public set(name: string, value: any) {
// TODO: Parameter validation
if (this[name] === value) {
return;
}
const newValue = WrappedValue.unwrap(value);
this[name] = newValue;
this.notifyPropertyChange(name, newValue);
}
_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
});
}
get typeName(): string {
return types.getClass(this);
}
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 _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 _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 _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);
}
}
}
// 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);
}
}
// public _onBindingContextChanged(oldValue: any, newValue: any) {
// let bindingContextBinding = this.bindings.get("bindingContext");
// if (bindingContextBinding) {
// if (!bindingContextBinding.updating) {
// bindingContextBinding.bind(newValue);
// }
// }
let bindingContextSource = this.bindingContext;
// 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();
}
}
});
}
// 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);
// add new weakevent listeners
var newObject = data.object[sourceProps[changedPropertyIndex]]
const newProps = sourceProps.slice(changedPropertyIndex + 1);
// add new weak event listeners
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,23 +655,17 @@ 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 if (!isView && optionsInstance instanceof Observable) {
optionsInstance.set(options.property, value);
} else {
let specialSetter = specialProperties.getSpecialPropertySetter(options.property);
if (specialSetter) {
specialSetter(optionsInstance, value);
} else {
if (optionsInstance instanceof Observable) {
optionsInstance.set(options.property, value);
} else {
optionsInstance[options.property] = value;
}
}
optionsInstance[options.property] = value;
}
}
catch (ex) {

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;
@@ -946,4 +934,4 @@ export function makeParser<T>(isValid: (value: any) => boolean, def: T): (value:
const lower = value && value.toLowerCase();
return isValid(lower) ? lower : def;
}
}
}

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>();
private bindingContextChanged(data: PropertyChangeData): void {
this.bindings.get("bindingContext").bind(data.value);
}
private bindings: Map<string, Binding>;
private shouldAddHandlerToParentBindingContextChanged: boolean;
private bindingContextBoundToParentBindingContextChanged: boolean;
public bind(options: BindingOptions, source: Object = defaultBindingSource): void {
let binding: Binding = this.bindings.get(options.targetProperty);
if (binding) {
binding.unbind();
const targetProperty = options.targetProperty;
this.unbind(targetProperty);
if (!this.bindings) {
this.bindings = new Map<string, Binding>();
}
binding = new Binding(this, options);
this.bindings.set(options.targetProperty, 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);
const bindings = this.bindings;
if (!bindings) {
return;
}
const binding = bindings.get(property);
if (binding) {
binding.unbind();
this.bindings.delete(property);
}
}
public _updateTwoWayBinding(propertyName: string, value: any) {
let binding: Binding = this.bindings.get(propertyName);
if (binding) {
binding.updateTwoWay(value);
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;