Files
NativeScript/tns-core-modules/ui/core/dependency-observable.ts
Panayot Cankov 299203901e Move platform .d.ts-es to separate plugin folder
Add dev-setup to figure out links

Update development workflow

Update package.json and dts-ios

Update the iOS declarations

grunt is now fine
2016-09-26 11:53:39 +03:00

439 lines
15 KiB
TypeScript

import definition = require("ui/core/dependency-observable");
import {Observable, WrappedValue} from "data/observable";
import types = require("utils/types");
// use private variables in the scope of the module rather than static members of the class since a member is still accessible through JavaScript and may be changed.
var propertyFromKey = {};
var propertyIdCounter = 0;
export let unsetValue = new Object();
function generatePropertyKey(name: string, ownerType: string, validate?: boolean) {
if (validate) {
validateRegisterParameters(name, ownerType);
}
return ownerType + "." + name;
}
function validateRegisterParameters(name: string, ownerType: string) {
if (name == null || name.trim().length === 0) {
throw new Error("Name should not be null or empty string.");
}
if (ownerType == null || ownerType.trim().length === 0) {
throw new Error("OwnerType should not be null or empty string.");
}
}
function getPropertyByNameAndType(name: string, owner: any): Property {
var result;
var key;
var classInfo = types.getClassInfo(owner);
while (classInfo) {
key = generatePropertyKey(name, classInfo.name);
result = propertyFromKey[key];
if (result) {
break;
}
classInfo = classInfo.baseClassInfo;
}
return result;
}
export module PropertyMetadataSettings {
export var None = 0;
export var AffectsLayout = 1;
export var AffectsStyle = 1 << 1;
export var Inheritable = 1 << 2;
}
export module ValueSource {
export var Default = 0;
export var Inherited = 1;
export var Css = 2;
export var Local = 3;
export var VisualState = 4;
}
export class PropertyMetadata implements definition.PropertyMetadata {
public inheritable: boolean;
public affectsStyle: boolean;
public affectsLayout: boolean;
public onValueChanged: definition.PropertyChangedCallback;
public onValidateValue: definition.PropertyValidationCallback;
public equalityComparer: definition.PropertyEqualityComparer;
constructor(
public defaultValue: any,
public options: number = PropertyMetadataSettings.None,
onChanged?: definition.PropertyChangedCallback,
onValidateValue?: definition.PropertyValidationCallback,
equalityComparer?: definition.PropertyEqualityComparer) {
this.defaultValue = defaultValue;
this.options = options;
this.onValueChanged = onChanged;
this.onValidateValue = onValidateValue;
this.equalityComparer = equalityComparer;
this.inheritable = (options & PropertyMetadataSettings.Inheritable) === PropertyMetadataSettings.Inheritable;
this.affectsStyle = (options & PropertyMetadataSettings.AffectsStyle) === PropertyMetadataSettings.AffectsStyle;
this.affectsLayout = (options & PropertyMetadataSettings.AffectsLayout) === PropertyMetadataSettings.AffectsLayout;
}
}
export class Property implements definition.Property {
public key: string;
public id: number;
public defaultValue;
public equalityComparer;
public inheritable;
public affectsStyle;
public affectsLayout;
public nameEvent: string;
public onValidateValue;
public onValueChanged: definition.PropertyChangedCallback;
public valueConverter: (value: string) => any
constructor(public name: string, public ownerType: string, public metadata: PropertyMetadata, valueConverter?: (value: string) => any) {
// register key
this.key = generatePropertyKey(name, ownerType, true);
if (propertyFromKey[this.key]) {
throw new Error("Property " + name + " already registered for type " + ownerType + ".");
}
propertyFromKey[this.key] = this;
if (!metadata || !(metadata instanceof PropertyMetadata)) {
throw new Error("Expected valid PropertyMetadata instance.");
}
this.name = name;
this.nameEvent = name + "Change";
this.ownerType = ownerType;
this.metadata = metadata;
// generate a unique numeric id for each property (faster lookup than a string key)
this.id = propertyIdCounter++;
this.valueConverter = valueConverter;
this.defaultValue = metadata.defaultValue;
this.onValueChanged = metadata.onValueChanged;
this.onValidateValue = metadata.onValidateValue;
this.equalityComparer = metadata.equalityComparer || ((x, y) => x === y);
this.inheritable = metadata.inheritable;
this.affectsStyle = metadata.affectsStyle;
this.affectsLayout = metadata.affectsLayout;
}
public defaultValueGetter: (instance: definition.DependencyObservable) => definition.NativeValueResult;
}
export class PropertyEntry implements definition.PropertyEntry {
public valueSource: number = ValueSource.Default;
public inheritedValue: any;
public cssValue: any;
public localValue: any;
public effectiveValue: any;
public visualStateValue: any;
constructor(public property: Property) {
}
public resetValue() {
this.valueSource = ValueSource.Default;
this.inheritedValue = this.cssValue = this.localValue = this.visualStateValue = this.effectiveValue = undefined;
}
}
export class DependencyObservable extends Observable implements definition.DependencyObservable {
private _propertyEntries = {};
public set(name: string, value: any) {
var property = getPropertyByNameAndType(name, this);
if (property) {
this._setValueInternal(property, value, ValueSource.Local);
} else {
super.set(name, value);
}
}
public get(name: string): any {
var property = getPropertyByNameAndType(name, this);
if (property) {
return this._getValue(property);
} else {
return super.get(name);
}
}
public _setValue(property: Property, value: any, source?: number) {
this._setValueInternal(property, value, source || ValueSource.Local);
}
public _getValueSource(property: Property): number {
var entry: PropertyEntry = this._propertyEntries[property.id];
if (entry) {
return entry.valueSource;
}
return ValueSource.Default;
}
public _getValue(property: Property): any {
var entry: PropertyEntry = this._propertyEntries[property.id];
if (entry) {
return entry.effectiveValue;
}
else {
return this._getDefaultValue(property);
}
}
private _getDefaultValue(property: Property): any {
if (property.defaultValueGetter) {
// we check for cached properties only for these which have 'defaultValueGetter' defined;
// When DependencyProperties are removed from Style - fix this check.
let defaultValueResult = property.defaultValueGetter(this);
let defaultValue = defaultValueResult.result;
if (defaultValueResult.cacheable) {
let entry = new PropertyEntry(property);
entry.effectiveValue = defaultValue;
this._propertyEntries[property.id] = entry;
}
return defaultValue;
}
return property.defaultValue;
}
public _resetValues(valueSource: number): void {
for (let i = 0, keys = Object.keys(this._propertyEntries); i < keys.length; i++) {
let key = keys[i];
let entry: PropertyEntry = this._propertyEntries[key];
this._resetValueInternal(entry.property, entry, valueSource);
}
}
public _resetValue(property: Property, valueSource: number = ValueSource.Local): void {
let entry: PropertyEntry = this._propertyEntries[property.id];
if (!entry) {
return;
}
this._resetValueInternal(property, entry, valueSource);
}
private _resetValueInternal(property: Property, entry: PropertyEntry, valueSource: number): void {
switch (valueSource) {
case ValueSource.Inherited:
entry.inheritedValue = undefined;
break;
case ValueSource.Css:
entry.cssValue = undefined;
break;
case ValueSource.Local:
entry.localValue = undefined;
break;
case ValueSource.VisualState:
entry.visualStateValue = undefined;
break;
}
let currentValueSource = entry.valueSource;
if (currentValueSource !== valueSource) {
// If current valueSource is larget than the one we reset - do nothing.
// We are reseting property will lower priority and it won't change effectValue;
// Reseting larger source means we somehow was able to set value without updating currentValueSource which is clearly a bug.
return;
}
let currentValue = entry.effectiveValue;
let newValue = this.getEffectiveValueAndUpdateEntry(currentValueSource, entry, property);
if (!property.equalityComparer(currentValue, newValue)) {
// // If we fallback to defalutValue - remove propertyEntry.
// if (entry.valueSource === ValueSource.Default) {
// delete this._propertyEntries[property.id];
// }
// else {
entry.effectiveValue = newValue;
// }
this._onPropertyChanged(property, currentValue, newValue);
}
}
public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
// let realNewValue = WrappedValue.unwrap(newValue);
let valueChanged = property.onValueChanged;
if (valueChanged) {
valueChanged({
object: this,
property: property,
eventName: Observable.propertyChangeEvent,
newValue: newValue,
oldValue: oldValue
});
}
let propName = property.name;
if (this.hasListeners(Observable.propertyChangeEvent)) {
let changeData = super._createPropertyChangeData(propName, newValue);
this.notify(changeData);
}
let eventName = property.nameEvent;
if (this.hasListeners(eventName)) {
let ngChangedData = {
eventName: eventName,
propertyName: propName,
object: this,
value: newValue
}
this.notify(ngChangedData);
}
}
public _eachSetProperty(callback: (property: Property) => boolean) {
for (let i = 0, keys = Object.keys(this._propertyEntries); i < keys.length; i++) {
let key = keys[i];
let entry: PropertyEntry = this._propertyEntries[key];
if (!callback(entry.property)) {
break;
}
}
}
public _eachSetPropertyValue(callback: (property: Property, value: any) => void): void {
for (let i = 0, keys = Object.keys(this._propertyEntries); i < keys.length; i++) {
let key = keys[i];
let entry: PropertyEntry = this._propertyEntries[key];
if (!callback(entry.property, entry.effectiveValue)) {
break;
}
}
}
public toString(): string {
return this.typeName;
}
private _setValueInternal(property: Property, value: any, source: number) {
if (value === unsetValue) {
this._resetValue(property, source);
return;
}
let wrapped = value && value.wrapped;
let realValue = wrapped ? WrappedValue.unwrap(value) : value;
let validate = property.onValidateValue;
if (validate && !validate(realValue)) {
throw new Error("Invalid value " + realValue + " for property " + property.name);
}
// Convert the value to the real property type in case it is coming as a string from CSS or XML.
let converter = property.valueConverter;
if (converter && types.isString(realValue)) {
realValue = converter(realValue);
}
let entry: PropertyEntry = this._propertyEntries[property.id];
let currentValue;
if (!entry) {
entry = new PropertyEntry(property);
entry.effectiveValue = this._getDefaultValue(property);
this._propertyEntries[property.id] = entry;
}
currentValue = entry.effectiveValue;
switch (source) {
case ValueSource.Inherited:
entry.inheritedValue = realValue;
break;
case ValueSource.Css:
entry.cssValue = realValue;
break;
case ValueSource.Local:
entry.localValue = realValue;
break;
case ValueSource.VisualState:
entry.visualStateValue = realValue;
break;
}
let currentValueSource = entry.valueSource;
if (currentValueSource > source) {
return;
}
else if (currentValueSource < source) {
entry.valueSource = source;
}
if (wrapped || !property.equalityComparer(currentValue, realValue)) {
entry.effectiveValue = realValue;
this._onPropertyChanged(property, currentValue, realValue);
}
}
private getEffectiveValueAndUpdateEntry(currentValueSource: number, entry: PropertyEntry, property: Property): any {
let newValue: any;
switch (currentValueSource) {
case ValueSource.Inherited:
newValue = property.defaultValue;
entry.valueSource = ValueSource.Default;
break;
case ValueSource.Css:
if (entry.inheritedValue !== undefined) {
newValue = entry.inheritedValue;
entry.valueSource = ValueSource.Inherited;
}
else {
newValue = property.defaultValue;
entry.valueSource = ValueSource.Default;
}
break;
case ValueSource.Local:
if (entry.cssValue !== undefined) {
newValue = entry.cssValue;
entry.valueSource = ValueSource.Css;
}
else if (entry.inheritedValue !== undefined) {
newValue = entry.inheritedValue;
entry.valueSource = ValueSource.Inherited;
}
else {
newValue = property.defaultValue;
entry.valueSource = ValueSource.Default;
}
break;
case ValueSource.VisualState:
if (entry.localValue !== undefined) {
newValue = entry.localValue;
entry.valueSource = ValueSource.Local;
}
else if (entry.cssValue !== undefined) {
newValue = entry.cssValue;
entry.valueSource = ValueSource.Css;
}
else if (entry.inheritedValue !== undefined) {
newValue = entry.inheritedValue;
entry.valueSource = ValueSource.Inherited;
}
else {
newValue = property.defaultValue;
entry.valueSource = ValueSource.Default;
}
break;
}
return newValue;
}
}