mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
21
tns-core-modules/data/observable/observable.d.ts
vendored
21
tns-core-modules/data/observable/observable.d.ts
vendored
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
tns-core-modules/ui/definitions.d.ts
vendored
5
tns-core-modules/ui/definitions.d.ts
vendored
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user