mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Optimized DependencyObject setValue performance (#2260)
* Cached all properties from metadata as fields on Property ResetValue does not remove entry but only reset value for the given ValueSource (Local by default) Optimized setValueInternal performance * Removed TS dependency * Fix tslint
This commit is contained in:
@@ -61,7 +61,7 @@ export class Button extends common.Button {
|
||||
|
||||
public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
|
||||
super._onPropertyChanged(property, oldValue, newValue);
|
||||
if (property.metadata.affectsStyle) {
|
||||
if (property.affectsStyle) {
|
||||
this._updateHandler();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import definition = require("ui/core/bindable");
|
||||
import {Observable, PropertyChangeData} from "data/observable";
|
||||
import {DependencyObservable, Property, PropertyMetadata, PropertyMetadataSettings, PropertyChangeData as DependencyPropertyChangeData} from "ui/core/dependency-observable";
|
||||
import {unsetValue, DependencyObservable, Property, PropertyMetadata, PropertyMetadataSettings, PropertyChangeData as DependencyPropertyChangeData} from "ui/core/dependency-observable";
|
||||
import weakEvents = require("ui/core/weak-event-listener");
|
||||
import types = require("utils/types");
|
||||
import trace = require("trace");
|
||||
@@ -60,9 +60,9 @@ export class Bindable extends DependencyObservable implements definition.Bindabl
|
||||
binding.sourceIsBindingContext = true;
|
||||
}
|
||||
|
||||
if (!types.isNullOrUndefined(bindingSource)) {
|
||||
binding.bind(bindingSource);
|
||||
}
|
||||
// if (!types.isNullOrUndefined(bindingSource)) {
|
||||
binding.bind(bindingSource);
|
||||
// }
|
||||
}
|
||||
|
||||
public unbind(property: string) {
|
||||
@@ -91,7 +91,7 @@ export class Bindable extends DependencyObservable implements definition.Bindabl
|
||||
}
|
||||
super._onPropertyChanged(property, oldValue, newValue);
|
||||
if (this instanceof viewModule.View) {
|
||||
if (property.metadata.inheritable && (<viewModule.View>(<any>this))._isInheritedChange() === true) {
|
||||
if (property.inheritable && (<viewModule.View>(<any>this))._isInheritedChange() === true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ export class Binding {
|
||||
this.sourceOptions = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private sourceAsObject(source: any): any {
|
||||
/* tslint:disable */
|
||||
let objectType = typeof source;
|
||||
@@ -521,7 +521,7 @@ export class Binding {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateOptions(this.targetOptions, value);
|
||||
this.updateOptions(this.targetOptions, types.isNullOrUndefined(value) ? unsetValue : value);
|
||||
}
|
||||
|
||||
private updateSource(value: any) {
|
||||
@@ -619,4 +619,4 @@ export class Binding {
|
||||
|
||||
this.updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,10 @@
|
||||
declare module "ui/core/dependency-observable" {
|
||||
import observable = require("data/observable");
|
||||
|
||||
/**
|
||||
* Value specifing that Property value should be reset. Used when bindingContext on bound property is creared/null.
|
||||
*/
|
||||
export let unsetValue: Object;
|
||||
/**
|
||||
* Interface used by Propery 'defaultValueGetter' function to specify if the default value returned by the native instance can be cached or not.
|
||||
* One example is - android.widget.Button background. It is state drawable so it cannot be reused/cached.
|
||||
@@ -17,6 +21,7 @@ declare module "ui/core/dependency-observable" {
|
||||
* Represents a special Property which supports changed callback, metadata and value validation.
|
||||
*/
|
||||
export class Property {
|
||||
|
||||
/**
|
||||
* Creates a new Property instance.
|
||||
* @param name The name of the property.
|
||||
@@ -25,17 +30,17 @@ declare module "ui/core/dependency-observable" {
|
||||
* @param valueConverter A function that can create an instance of the property type given a string. Used when parsing CSS and XML attribute values which are strings.
|
||||
*/
|
||||
constructor(name: string, ownerType: string, metadata: PropertyMetadata, valueConverter?: (value: string) => any);
|
||||
|
||||
|
||||
/**
|
||||
* Gets the name of the property. This is a read-only property.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the id of the property. This is used for fast lookup. This is a read-only property.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the PropertyMetadata object associated with the property. This is a read-only property.
|
||||
*/
|
||||
@@ -51,6 +56,14 @@ declare module "ui/core/dependency-observable" {
|
||||
* If default value is 'undefined' and this property is set this function will be used to extract the default value from the native instance.
|
||||
*/
|
||||
defaultValueGetter: (instance: DependencyObservable) => NativeValueResult;
|
||||
|
||||
defaultValue: any;
|
||||
onValueChanged: PropertyChangedCallback;
|
||||
onValidateValue: PropertyValidationCallback;
|
||||
equalityComparer: PropertyEqualityComparer;
|
||||
affectsLayout: boolean;
|
||||
inheritable: boolean;
|
||||
affectsStyle: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +81,7 @@ declare module "ui/core/dependency-observable" {
|
||||
constructor(
|
||||
defaultValue: any,
|
||||
options?: number,
|
||||
onChanged?: PropertyChangedCallback,
|
||||
onChanged?: PropertyChangedCallback,
|
||||
onValidateValue?: PropertyValidationCallback,
|
||||
equalityComparer?: PropertyEqualityComparer);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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) {
|
||||
@@ -54,210 +55,107 @@ export module ValueSource {
|
||||
}
|
||||
|
||||
export class PropertyMetadata implements definition.PropertyMetadata {
|
||||
private _defaultValue: any;
|
||||
private _options: number;
|
||||
private _onChanged: definition.PropertyChangedCallback;
|
||||
private _onValidateValue: definition.PropertyValidationCallback;
|
||||
private _equalityComparer: definition.PropertyEqualityComparer;
|
||||
public inheritable: boolean;
|
||||
public affectsStyle: boolean;
|
||||
public affectsLayout: boolean;
|
||||
public onValueChanged: definition.PropertyChangedCallback;
|
||||
|
||||
constructor(
|
||||
defaultValue: any,
|
||||
options?: number,
|
||||
public defaultValue: any,
|
||||
public options: number = PropertyMetadataSettings.None,
|
||||
onChanged?: definition.PropertyChangedCallback,
|
||||
onValidateValue?: definition.PropertyValidationCallback,
|
||||
equalityComparer?: definition.PropertyEqualityComparer) {
|
||||
this._defaultValue = defaultValue;
|
||||
this._options = options;
|
||||
if (types.isNullOrUndefined(this._options)) {
|
||||
this._options = PropertyMetadataSettings.None;
|
||||
}
|
||||
this._onChanged = onChanged;
|
||||
this._onValidateValue = onValidateValue;
|
||||
this._equalityComparer = equalityComparer;
|
||||
}
|
||||
|
||||
public get defaultValue(): any {
|
||||
return this._defaultValue;
|
||||
}
|
||||
|
||||
public get options(): number {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public get onValueChanged(): definition.PropertyChangedCallback {
|
||||
return this._onChanged;
|
||||
}
|
||||
public set onValueChanged(value: definition.PropertyChangedCallback) {
|
||||
this._onChanged = value;
|
||||
}
|
||||
|
||||
public get onValidateValue(): definition.PropertyValidationCallback {
|
||||
return this._onValidateValue;
|
||||
}
|
||||
|
||||
public get equalityComparer(): definition.PropertyEqualityComparer {
|
||||
return this._equalityComparer;
|
||||
}
|
||||
|
||||
public get affectsLayout(): boolean {
|
||||
return (this._options & PropertyMetadataSettings.AffectsLayout) === PropertyMetadataSettings.AffectsLayout;
|
||||
}
|
||||
|
||||
public get affectsStyle(): boolean {
|
||||
return (this._options & PropertyMetadataSettings.AffectsStyle) === PropertyMetadataSettings.AffectsStyle;
|
||||
}
|
||||
|
||||
public get inheritable(): boolean {
|
||||
return (this._options & PropertyMetadataSettings.Inheritable) === PropertyMetadataSettings.Inheritable;
|
||||
public onValidateValue?: definition.PropertyValidationCallback,
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
|
||||
// function property(property: Property): PropertyDecorator {
|
||||
// function _getValue() {
|
||||
// return this._getValue(property);
|
||||
// }
|
||||
|
||||
// function _setValue(value: any) {
|
||||
// this._setValueInternal(property, value, ValueSource.Local);
|
||||
// }
|
||||
|
||||
// return (target: Object, propertyKey: string) => {
|
||||
// Object.defineProperty(target, propertyKey, {
|
||||
// get: _getValue,
|
||||
// set: _setValue,
|
||||
// enumerable: true,
|
||||
// configurable: true
|
||||
// });
|
||||
// };
|
||||
// }
|
||||
|
||||
export class Property implements definition.Property {
|
||||
private _metadata: PropertyMetadata;
|
||||
private _key: string;
|
||||
private _name: string;
|
||||
private _ownerType: string;
|
||||
private _id: number;
|
||||
private _valueConverter: (value: any) => any;
|
||||
|
||||
constructor(name: string, ownerType: string, metadata: PropertyMetadata, valueConverter?: (value: string) => any) {
|
||||
public key: string;
|
||||
public id: number;
|
||||
public defaultValue;
|
||||
public onValidateValue;
|
||||
public equalityComparer;
|
||||
public inheritable;
|
||||
public affectsStyle;
|
||||
public affectsLayout;
|
||||
public onValueChanged: definition.PropertyChangedCallback;
|
||||
public nameEvent: string;
|
||||
constructor(public name: string, public ownerType: string, public metadata: PropertyMetadata, public valueConverter?: (value: string) => any) {
|
||||
// register key
|
||||
this._key = generatePropertyKey(name, ownerType, true);
|
||||
this.key = generatePropertyKey(name, ownerType, true);
|
||||
|
||||
if (propertyFromKey[this._key]) {
|
||||
if (propertyFromKey[this.key]) {
|
||||
throw new Error("Property " + name + " already registered for type " + ownerType + ".");
|
||||
}
|
||||
|
||||
propertyFromKey[this._key] = this;
|
||||
propertyFromKey[this.key] = this;
|
||||
|
||||
if (!metadata || !(metadata instanceof PropertyMetadata)) {
|
||||
throw new Error("Expected valid PropertyMetadata instance.");
|
||||
}
|
||||
|
||||
this._name = name;
|
||||
this._ownerType = ownerType;
|
||||
this._metadata = metadata;
|
||||
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.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;
|
||||
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get metadata(): PropertyMetadata {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
public isValidValue(value: Object): boolean {
|
||||
if (this.metadata.onValidateValue) {
|
||||
return this.metadata.onValidateValue(value);
|
||||
}
|
||||
|
||||
// TODO: consider type check here (e.g. passing function where object is expected)
|
||||
return true;
|
||||
}
|
||||
|
||||
public get valueConverter(): (value: string) => any {
|
||||
return this._valueConverter;
|
||||
}
|
||||
|
||||
public _getEffectiveValue(entry: PropertyEntry): any {
|
||||
if (types.isDefined(entry.localValue)) {
|
||||
entry.valueSource = ValueSource.Local;
|
||||
return entry.localValue;
|
||||
}
|
||||
|
||||
if (types.isDefined(entry.inheritedValue)) {
|
||||
entry.valueSource = ValueSource.Inherited;
|
||||
return entry.inheritedValue;
|
||||
}
|
||||
|
||||
entry.valueSource = ValueSource.Default;
|
||||
return this.metadata.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
export class PropertyEntry implements definition.PropertyEntry {
|
||||
private _property: Property;
|
||||
public valueSource: number = ValueSource.Default;
|
||||
public inheritedValue: any;
|
||||
public cssValue: any;
|
||||
public localValue: any;
|
||||
public effectiveValue: any;
|
||||
public visualStateValue: any;
|
||||
|
||||
private _valueSource: number;
|
||||
private _inheritedValue: any;
|
||||
private _cssValue: any;
|
||||
private _localValue: any;
|
||||
private _effectiveValue: any;
|
||||
private _visualStateValue: any;
|
||||
|
||||
constructor(property: Property) {
|
||||
this._property = property;
|
||||
}
|
||||
|
||||
get property(): Property {
|
||||
return this._property;
|
||||
}
|
||||
|
||||
get effectiveValue() {
|
||||
if (!this._effectiveValue) {
|
||||
this._effectiveValue = this._property._getEffectiveValue(this);
|
||||
}
|
||||
|
||||
return this._effectiveValue;
|
||||
}
|
||||
|
||||
get valueSource(): number {
|
||||
return this._valueSource;
|
||||
}
|
||||
set valueSource(value: number) {
|
||||
this._valueSource = value;
|
||||
}
|
||||
|
||||
get localValue(): any {
|
||||
return this._localValue;
|
||||
}
|
||||
set localValue(value: any) {
|
||||
this._localValue = value;
|
||||
this._effectiveValue = undefined;
|
||||
}
|
||||
|
||||
get inheritedValue(): any {
|
||||
return this._inheritedValue;
|
||||
}
|
||||
set inheritedValue(value: any) {
|
||||
this._inheritedValue = value;
|
||||
this._effectiveValue = undefined;
|
||||
}
|
||||
|
||||
get cssValue(): any {
|
||||
return this._cssValue;
|
||||
}
|
||||
set cssValue(value: any) {
|
||||
this._cssValue = value;
|
||||
this._effectiveValue = undefined;
|
||||
}
|
||||
|
||||
get visualStateValue(): any {
|
||||
return this._visualStateValue;
|
||||
}
|
||||
set visualStateValue(value: any) {
|
||||
this._visualStateValue = value;
|
||||
this._effectiveValue = undefined;
|
||||
constructor(public property: Property) {
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public resetValue() {
|
||||
this._valueSource = ValueSource.Default;
|
||||
this._visualStateValue = undefined;
|
||||
this._localValue = undefined;
|
||||
this._cssValue = undefined;
|
||||
this._inheritedValue = undefined;
|
||||
this._effectiveValue = undefined;
|
||||
this.valueSource = ValueSource.Default;
|
||||
this.inheritedValue = this.cssValue = this.localValue = this.visualStateValue = this.effectiveValue = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +167,7 @@ export class DependencyObservable extends Observable implements definition.Depen
|
||||
public set(name: string, value: any) {
|
||||
var property = getPropertyByNameAndType(name, this);
|
||||
if (property) {
|
||||
this._setValue(property, value, ValueSource.Local);
|
||||
this._setValueInternal(property, value, ValueSource.Local);
|
||||
} else {
|
||||
super.set(name, value);
|
||||
}
|
||||
@@ -285,16 +183,7 @@ export class DependencyObservable extends Observable implements definition.Depen
|
||||
}
|
||||
|
||||
public _setValue(property: Property, value: any, source?: number) {
|
||||
let realValue = WrappedValue.unwrap(value);
|
||||
if (!property.isValidValue(realValue)) {
|
||||
throw new Error("Invalid value " + realValue + " for property " + property.name);
|
||||
}
|
||||
|
||||
if (types.isUndefined(source)) {
|
||||
source = ValueSource.Local;
|
||||
}
|
||||
|
||||
this._setValueInternal(property, value, source);
|
||||
this._setValueInternal(property, value, source || ValueSource.Local);
|
||||
}
|
||||
|
||||
public _getValueSource(property: Property): number {
|
||||
@@ -311,12 +200,18 @@ export class DependencyObservable extends Observable implements definition.Depen
|
||||
if (entry) {
|
||||
return entry.effectiveValue;
|
||||
}
|
||||
else if (property.defaultValueGetter) { // we check for cached properties only for these which have 'defaultValueGetter' defined;
|
||||
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.
|
||||
var view = (<any>this)._view || this;
|
||||
let key = types.getClass(view) + "." + property.id;
|
||||
let defaultValue = defaultValueForPropertyPerType.get(key);
|
||||
if (types.isUndefined(defaultValue) && view._nativeView) {
|
||||
if (!defaultValueForPropertyPerType.has(key) && view._nativeView) {
|
||||
let defaultValueResult = property.defaultValueGetter(this);
|
||||
defaultValue = defaultValueResult.result;
|
||||
if (defaultValueResult.cacheable) {
|
||||
@@ -327,70 +222,101 @@ export class DependencyObservable extends Observable implements definition.Depen
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return property.metadata.defaultValue;
|
||||
return property.defaultValue;
|
||||
}
|
||||
|
||||
public _resetValue(property: Property, source?: number) {
|
||||
if (!(property.id in this._propertyEntries)) {
|
||||
public _resetValue(property: Property, source: number = ValueSource.Local) {
|
||||
let entry: PropertyEntry = this._propertyEntries[property.id];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (types.isDefined(source)) {
|
||||
// resetting particular modifier to undefined will remove it from the effective value composition
|
||||
this._setValueInternal(property, undefined, source);
|
||||
} else {
|
||||
var currentValue = this._getValue(property);
|
||||
delete this._propertyEntries[property.id];
|
||||
var newValue = this._getValue(property);
|
||||
|
||||
var comparer: (x: any, y: any) => boolean = property.metadata.equalityComparer || this._defaultComparer;
|
||||
if (!comparer(currentValue, newValue)) {
|
||||
this._onPropertyChanged(property, currentValue, newValue);
|
||||
}
|
||||
switch (source) {
|
||||
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 !== source) {
|
||||
// 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.getEffectiveValue(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);
|
||||
}
|
||||
|
||||
// if (types.isDefined(source)) {
|
||||
// // resetting particular modifier to undefined will remove it from the effective value composition
|
||||
// this._resetValueInternal(property, source);
|
||||
// } else {
|
||||
// let currentValue = entry.effectiveValue;
|
||||
// delete this._propertyEntries[property.id];
|
||||
// let newValue = this._getDefaultValue(property);
|
||||
// if (!property.equalityComparer(currentValue, newValue)) {
|
||||
// this._onPropertyChanged(property, currentValue, newValue);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public _onPropertyChanged(property: Property, oldValue: any, newValue: any) {
|
||||
let realNewValue = WrappedValue.unwrap(newValue);
|
||||
if (property.metadata.onValueChanged) {
|
||||
property.metadata.onValueChanged({
|
||||
// let realNewValue = WrappedValue.unwrap(newValue);
|
||||
let valueChanged = property.onValueChanged;
|
||||
if (valueChanged) {
|
||||
valueChanged({
|
||||
object: this,
|
||||
property: property,
|
||||
eventName: Observable.propertyChangeEvent,
|
||||
newValue: realNewValue,
|
||||
newValue: newValue,
|
||||
oldValue: oldValue
|
||||
});
|
||||
}
|
||||
|
||||
let propName = property.name;
|
||||
if (this.hasListeners(Observable.propertyChangeEvent)) {
|
||||
var changeData = super._createPropertyChangeData(property.name, newValue);
|
||||
let changeData = super._createPropertyChangeData(propName, newValue);
|
||||
this.notify(changeData);
|
||||
}
|
||||
|
||||
let eventName = property.name + "Change";
|
||||
let eventName = property.nameEvent;
|
||||
if (this.hasListeners(eventName)) {
|
||||
var ngChangedData = {
|
||||
let ngChangedData = {
|
||||
eventName: eventName,
|
||||
propertyName: property.name,
|
||||
propertyName: propName,
|
||||
object: this,
|
||||
value: realNewValue
|
||||
value: newValue
|
||||
}
|
||||
this.notify(ngChangedData);
|
||||
}
|
||||
}
|
||||
|
||||
public _eachSetProperty(callback: (property: Property) => boolean) {
|
||||
var i;
|
||||
var key;
|
||||
var entry: PropertyEntry;
|
||||
var retVal: boolean;
|
||||
var keys = Object.keys(this._propertyEntries);
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
key = keys[i];
|
||||
entry = this._propertyEntries[key];
|
||||
retVal = callback(entry.property);
|
||||
if (!retVal) {
|
||||
for (let i = 0, keys = Object.keys(this._propertyEntries); i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
let entry = this._propertyEntries[key];
|
||||
if (!callback(entry.property)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -401,27 +327,47 @@ export class DependencyObservable extends Observable implements definition.Depen
|
||||
}
|
||||
|
||||
private _setValueInternal(property: Property, value: any, source: number) {
|
||||
let realValue = WrappedValue.unwrap(value);
|
||||
// Convert the value to the real property type in case it is coming as a string from CSS or XML.
|
||||
if (types.isString(realValue) && property.valueConverter) {
|
||||
realValue = property.valueConverter(realValue);
|
||||
if (value === unsetValue) {
|
||||
this._resetValue(property, source);
|
||||
return;
|
||||
}
|
||||
|
||||
var entry: PropertyEntry = this._propertyEntries[property.id];
|
||||
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);
|
||||
this._propertyEntries[property.id] = entry;
|
||||
currentValue = this._getDefaultValue(property);
|
||||
// In rare case when we set local value equal to default value we need to update effectiveValue as well.
|
||||
// Otherwise effectiveValue will stay undefined.
|
||||
if (property.equalityComparer(currentValue, realValue)) {
|
||||
entry.effectiveValue = realValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentValue = entry.effectiveValue;
|
||||
}
|
||||
|
||||
var currentValue = entry.effectiveValue;
|
||||
|
||||
switch (source) {
|
||||
case ValueSource.Css:
|
||||
entry.cssValue = realValue;
|
||||
break;
|
||||
case ValueSource.Inherited:
|
||||
entry.inheritedValue = realValue;
|
||||
break;
|
||||
case ValueSource.Css:
|
||||
entry.cssValue = realValue;
|
||||
break;
|
||||
case ValueSource.Local:
|
||||
entry.localValue = realValue;
|
||||
break;
|
||||
@@ -430,13 +376,78 @@ export class DependencyObservable extends Observable implements definition.Depen
|
||||
break;
|
||||
}
|
||||
|
||||
var comparer: (x: any, y: any) => boolean = property.metadata.equalityComparer || this._defaultComparer;
|
||||
if ((value && value.wrapped) || !comparer(currentValue, entry.effectiveValue)) {
|
||||
this._onPropertyChanged(property, currentValue, entry.effectiveValue);
|
||||
let currentValueSource = entry.valueSource;
|
||||
if (currentValueSource > source) {
|
||||
return;
|
||||
}
|
||||
else if (currentValueSource < source) {
|
||||
entry.valueSource = source;
|
||||
}
|
||||
|
||||
if (wrapped || !property.equalityComparer(currentValue, realValue)) {
|
||||
if (realValue === undefined) {
|
||||
realValue = this.getEffectiveValue(currentValueSource, entry, property);
|
||||
}
|
||||
|
||||
entry.effectiveValue = realValue;
|
||||
this._onPropertyChanged(property, currentValue, realValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _defaultComparer(x: any, y: any): boolean {
|
||||
return x === y;
|
||||
private getEffectiveValue(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;
|
||||
}
|
||||
}
|
||||
@@ -419,7 +419,7 @@ export class View extends ProxyObject implements definition.View {
|
||||
set opacity(value: number) {
|
||||
this.style.opacity = value;
|
||||
}
|
||||
|
||||
|
||||
//END Style property shortcuts
|
||||
|
||||
get translateX(): number {
|
||||
@@ -585,16 +585,13 @@ export class View extends ProxyObject implements definition.View {
|
||||
super._onPropertyChanged(property, oldValue, newValue);
|
||||
|
||||
if (this._childrenCount > 0) {
|
||||
var shouldUpdateInheritableProps = ((property.metadata && property.metadata.inheritable) &&
|
||||
!(property instanceof styling.Property));
|
||||
var that = this;
|
||||
let shouldUpdateInheritableProps = (property.inheritable && !(property instanceof styling.Property));
|
||||
if (shouldUpdateInheritableProps) {
|
||||
var notifyEachChild = function (child: View) {
|
||||
child._setValue(property, that._getValue(property), ValueSource.Inherited);
|
||||
return true;
|
||||
};
|
||||
this._updatingInheritedProperties = true;
|
||||
this._eachChildView(notifyEachChild);
|
||||
this._eachChildView((child) => {
|
||||
child._setValue(property, this._getValue(property), ValueSource.Inherited);
|
||||
return true;
|
||||
});
|
||||
this._updatingInheritedProperties = false;
|
||||
}
|
||||
}
|
||||
@@ -1010,19 +1007,13 @@ export class View extends ProxyObject implements definition.View {
|
||||
}
|
||||
|
||||
public _inheritProperties(parentView: View) {
|
||||
var that = this;
|
||||
var inheritablePropertySetCallback = function (property: Property) {
|
||||
if (property instanceof styling.Property) {
|
||||
return true;
|
||||
}
|
||||
if (property.metadata && property.metadata.inheritable) {
|
||||
var baseValue = parentView._getValue(property);
|
||||
that._setValue(property, baseValue, ValueSource.Inherited);
|
||||
parentView._eachSetProperty((property) => {
|
||||
if (!(property instanceof styling.Property) && property.inheritable) {
|
||||
let baseValue = parentView._getValue(property);
|
||||
this._setValue(property, baseValue, ValueSource.Inherited);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
parentView._eachSetProperty(inheritablePropertySetCallback);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1056,16 +1047,12 @@ export class View extends ProxyObject implements definition.View {
|
||||
ensureBindable();
|
||||
|
||||
view._setValue(bindable.Bindable.bindingContextProperty, undefined, ValueSource.Inherited);
|
||||
var inheritablePropertiesSetCallback = function (property: Property) {
|
||||
if (property instanceof styling.Property) {
|
||||
return true;
|
||||
}
|
||||
if (property.metadata && property.metadata.inheritable) {
|
||||
view._eachSetProperty((property) => {
|
||||
if (!(property instanceof styling.Property) && property.inheritable) {
|
||||
view._resetValue(property, ValueSource.Inherited);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
view._eachSetProperty(inheritablePropertiesSetCallback);
|
||||
});
|
||||
}
|
||||
|
||||
public _parentChanged(oldParent: View): void {
|
||||
@@ -1209,7 +1196,7 @@ export class View extends ProxyObject implements definition.View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public toString(): string {
|
||||
var str = this.typeName;
|
||||
if (this.id) {
|
||||
|
||||
@@ -624,7 +624,7 @@ export class ViewStyler implements style.Styler {
|
||||
}
|
||||
|
||||
private static resetNativeLayoutParamsProperty(view: View, nativeValue: any): void {
|
||||
ViewStyler.setNativeLayoutParamsProperty(view, style.nativeLayoutParamsProperty.metadata.defaultValue)
|
||||
ViewStyler.setNativeLayoutParamsProperty(view, style.nativeLayoutParamsProperty.defaultValue)
|
||||
}
|
||||
|
||||
private static setPaddingProperty(view: View, newValue: Thickness) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import enums = require("ui/enums");
|
||||
|
||||
global.moduleMerge(common, exports);
|
||||
|
||||
common.orientationProperty.metadata.onValueChanged = function scrollViewOrientationChanged(data: dependencyObservable.PropertyChangeData) {
|
||||
common.orientationProperty.onValueChanged = function scrollViewOrientationChanged(data: dependencyObservable.PropertyChangeData) {
|
||||
(<ScrollView>data.object)._onOrientationChanged(data.oldValue, data.newValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import definition = require("ui/styling/style-property");
|
||||
import types = require("utils/types");
|
||||
import observable = require("ui/core/dependency-observable");
|
||||
import observable = require("ui/core/dependency-observable");
|
||||
|
||||
var propertiesByName = {};
|
||||
var propertiesByCssName = {};
|
||||
@@ -15,7 +15,7 @@ function registerProperty(property: Property) {
|
||||
propertiesByCssName[property.cssName] = property;
|
||||
propertiesByName[property.name] = property;
|
||||
|
||||
if (property.metadata.inheritable) {
|
||||
if (property.inheritable) {
|
||||
inheritableProperties.push(property);
|
||||
}
|
||||
}
|
||||
@@ -71,63 +71,26 @@ export function getPropertyByCssName(name: string): Property {
|
||||
|
||||
export function eachProperty(callback: (property: Property) => void) {
|
||||
types.verifyCallback(callback);
|
||||
|
||||
var i;
|
||||
var key;
|
||||
var keys = Object.keys(propertiesByName);
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
key = keys[i];
|
||||
for (let i = 0, keys = Object.keys(propertiesByName); i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
callback(propertiesByName[key]);
|
||||
}
|
||||
}
|
||||
|
||||
export function eachInheritableProperty(callback: (property: Property) => void) {
|
||||
types.verifyCallback(callback);
|
||||
|
||||
var i;
|
||||
for (i = 0; i < inheritableProperties.length; i++) {
|
||||
for (let i = 0; i < inheritableProperties.length; i++) {
|
||||
callback(inheritableProperties[i]);
|
||||
}
|
||||
}
|
||||
|
||||
export class Property extends observable.Property implements definition.Property {
|
||||
private _cssName;
|
||||
|
||||
constructor(name: string, cssName: string, metadata: observable.PropertyMetadata, valueConverter?: (value: any) => any) {
|
||||
constructor(name: string, public cssName: string, metadata: observable.PropertyMetadata, valueConverter?: (value: any) => any) {
|
||||
super(name, "Style", metadata, valueConverter);
|
||||
|
||||
this._cssName = cssName;
|
||||
this.cssName = cssName;
|
||||
|
||||
registerProperty(this);
|
||||
}
|
||||
|
||||
public get cssName(): string {
|
||||
return this._cssName;
|
||||
}
|
||||
|
||||
public _getEffectiveValue(entry: observable.PropertyEntry): any {
|
||||
if (types.isDefined(entry.visualStateValue)) {
|
||||
entry.valueSource = observable.ValueSource.VisualState;
|
||||
return entry.visualStateValue;
|
||||
}
|
||||
|
||||
if (types.isDefined(entry.localValue)) {
|
||||
entry.valueSource = observable.ValueSource.Local;
|
||||
return entry.localValue;
|
||||
}
|
||||
|
||||
if (types.isDefined(entry.cssValue)) {
|
||||
entry.valueSource = observable.ValueSource.Css;
|
||||
return entry.cssValue;
|
||||
}
|
||||
|
||||
if (types.isDefined(entry.inheritedValue)) {
|
||||
entry.valueSource = observable.ValueSource.Inherited;
|
||||
return entry.inheritedValue;
|
||||
}
|
||||
|
||||
entry.valueSource = observable.ValueSource.Default;
|
||||
return this.metadata.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,20 +910,17 @@ export class Style extends DependencyObservable implements styling.Style {
|
||||
|
||||
// The effective value of an inheritable property has changed
|
||||
// propagate the change down to the descendants to update their inherited properties.
|
||||
if (this._view._childrenCount === 0 || !property.metadata.inheritable) {
|
||||
if (this._view._childrenCount === 0 || !property.inheritable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var eachChild = function (child: View): boolean {
|
||||
this._view._eachChildView((child: View) => {
|
||||
child.style._inheritStyleProperty(property);
|
||||
return true;
|
||||
}
|
||||
|
||||
this._view._eachChildView(eachChild);
|
||||
});
|
||||
}
|
||||
|
||||
private _applyStyleProperty(property: Property, newValue: any) {
|
||||
|
||||
if (!this._view._shouldApplyStyleHandlers()) {
|
||||
return;
|
||||
}
|
||||
@@ -934,7 +931,7 @@ export class Style extends DependencyObservable implements styling.Style {
|
||||
}
|
||||
|
||||
try {
|
||||
var handler: definition.StylePropertyChangedHandler = getHandler(property, this._view);
|
||||
let handler: definition.StylePropertyChangedHandler = getHandler(property, this._view);
|
||||
|
||||
if (!handler) {
|
||||
if (trace.enabled) {
|
||||
@@ -946,12 +943,12 @@ export class Style extends DependencyObservable implements styling.Style {
|
||||
trace.write("Found handler for property: " + property.name + ", view:" + this._view, trace.categories.Style);
|
||||
}
|
||||
|
||||
var shouldReset = false;
|
||||
if (property.metadata.equalityComparer) {
|
||||
shouldReset = property.metadata.equalityComparer(newValue, property.metadata.defaultValue);
|
||||
let shouldReset = false;
|
||||
if (property.equalityComparer) {
|
||||
shouldReset = property.equalityComparer(newValue, property.defaultValue);
|
||||
}
|
||||
else {
|
||||
shouldReset = (newValue === property.metadata.defaultValue);
|
||||
shouldReset = (newValue === property.defaultValue);
|
||||
}
|
||||
|
||||
if (shouldReset) {
|
||||
@@ -971,12 +968,12 @@ export class Style extends DependencyObservable implements styling.Style {
|
||||
}
|
||||
|
||||
public _inheritStyleProperty(property: Property) {
|
||||
if (!property.metadata.inheritable) {
|
||||
if (!property.inheritable) {
|
||||
throw new Error("An attempt was made to inherit a style property which is not marked as 'inheritable'.");
|
||||
}
|
||||
|
||||
var currentParent = this._view.parent;
|
||||
var valueSource: number;
|
||||
let currentParent = this._view.parent;
|
||||
let valueSource: number;
|
||||
|
||||
while (currentParent) {
|
||||
valueSource = currentParent.style._getValueSource(property);
|
||||
@@ -990,9 +987,8 @@ export class Style extends DependencyObservable implements styling.Style {
|
||||
}
|
||||
|
||||
public _inheritStyleProperties() {
|
||||
var that = this;
|
||||
styleProperty.eachInheritableProperty((p) => {
|
||||
that._inheritStyleProperty(p);
|
||||
this._inheritStyleProperty(p);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ export class TimePicker extends common.TimePicker {
|
||||
|
||||
var c = java.util.Calendar.getInstance();
|
||||
|
||||
if (this.hour === common.TimePicker.hourProperty.metadata.defaultValue) {
|
||||
if (this.hour === common.TimePicker.hourProperty.defaultValue) {
|
||||
this.hour = c.get(java.util.Calendar.HOUR_OF_DAY);
|
||||
}
|
||||
|
||||
if (this.minute === common.TimePicker.minuteProperty.metadata.defaultValue) {
|
||||
if (this.minute === common.TimePicker.minuteProperty.defaultValue) {
|
||||
this.minute = c.get(java.util.Calendar.MINUTE);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user