DynamicProperty¶
Overview¶
Creates a Property that does synchronization of values with a swappable Property that itself can change. Handles the case where you need a Property that can switch between acting like multiple other Properties.
With no other options specified, the value of this Property is: - null, if valuePropertyProperty.value === null - valuePropertyProperty.value.value otherwise
The value of this Property (generalized, with the options available) is: - derive( defaultValue ), if valuePropertyProperty.value === null - map( derive( valuePropertyProperty.value ).value ) otherwise
Generally, this DynamicProperty uses one-way synchronization (it only listens to the source), but if the 'bidirectional' option is true, it will use two-way synchronization (changes to this Property will change the active source). Thus when this Property changes value (when bidirectional is true), it will set: - derive( valuePropertyProperty.value ).value = inverseMap( this.value ), if valuePropertyProperty.value !== null
General example
const firstProperty = new Property( Color.RED ); const secondProperty = new Property( Color.BLUE ); const currentProperty = new Property( firstProperty ); // {Property.<Property.<Color>>}
const backgroundFill = new DynamicProperty( currentProperty ) // Turns into a {Property.<Color>} backgroundFill.value; // Color.RED, since: currentProperty.value === firstProperty and firstProperty.value === Color.RED firstProperty.value = Color.YELLOW; backgroundFill.value; // Color.YELLOW - It's connected to firstProperty right now
currentProperty.value = secondProperty; backgroundFill.value; // Color.BLUE - It's the secondProperty's value
secondProperty.value = Color.MAGENTA; backgroundFill.value; // Color.MAGENTA - Yes, it's listening to the other Property now.
Also supports falling back to null if our main Property is set to null: currentProperty.value = null; backgroundFill.value; // null
'derive' option
Additionally, DynamicProperty supports the ability to derive the Property value from our main Property's value. For example, say you have multiple scenes each with the type: scene: { backgroundColorProperty: {Property.<Color>} } and you have a currentSceneProperty: {Property.<Scene>}, you may want to create: const currentBackgroundColorProperty = new DynamicProperty( currentSceneProperty, { derive: 'backgroundColorProperty' } ); This would always report the current scene's current background color. What if you sometimes don't have a scene active, e.g. {Property.<Scene|null>}? You can provide a default value: new DynamicProperty( currentSceneProperty, { derive: 'backgroundColorProperty', defaultValue: Color.BLACK } ); So that if the currentSceneProperty's value is null, the value of our DynamicProperty will be Color.BLACK. NOTE there are constraints using derive: 'string' when using parametric type parameters. See https://github.com/phetsims/projectile-data-lab/issues/10
'bidirectional' option
If you would like for direct changes to this Property to change the original source (bidirectional synchronization), then pass bidirectional:true: const firstProperty = new Property( 5 ); const secondProperty = new Property( 10 ); const numberPropertyProperty = new Property( firstProperty ); const dynamicProperty = new DynamicProperty( numberPropertyProperty, { bidirectional: true } ); dynamicProperty.value = 2; // allowed now that it is bidirectional, otherwise prohibited firstProperty.value; // 2 numberPropertyProperty.value = secondProperty; // change which Property is active dynamicProperty.value; // 10, from the new Property dynamicProperty.value = 0; secondProperty.value; // 0, set above. firstProperty.value; // still 2 from above, since our dynamic Property switched to the other Property
'map' and 'inverseMap' options
DynamicProperty also supports mapping values to different types. For example, say we have a numberPropertyProperty {Property.<Property.<number>>}, but want to have a {Property.<string>} as the output. Then: new DynamicProperty( numberPropertyProperty, { map: function( number ) { return '' + number; } } ); will do the trick. If this needs to be done with a bidirectional DynamicProperty, also include inverseMap: new DynamicProperty( numberPropertyProperty, { bidirectional: true, map: function( number ) { return '' + number; }, inverseMap: function( string ) { return Number.parseFloat( string ); } } ); so that changes to the dynamic Property will result in a change in the numberPropertyProperty's value.
@author Jonathan Olson <jonathan.olson@colorado.edu>
Class DynamicProperty¶
ThisValueType: The value type of the resulting DynamicProperty InnerValueType: The value type of the inner (derived) Property, whose value gets mapped to ThisValueType and back OuterValueType: The value type of the main passed-in Property (whose value may be derived to the InnerValueType) e.g.: class Foo { colorProperty: Property<Color> } new DynamicProperty<number, Color, Foo>( someFooProperty, { derive: 'colorProperty', map: ( color: Color ) => color.alpha } ); Here, ThisValueType=number (we're a Property<number>). You've passed in a Property<Foo>, so OuterValueType is a Foo. InnerValueType is what we get from our derive (Color), and what the parameter of our map is.
Constructor¶
new DynamicProperty( valuePropertyProperty : TNullableProperty<OuterValueType> | TReadOnlyProperty<OuterValueType>, providedOptions? : DynamicPropertyOptions<ThisValueType, InnerValueType, OuterValueType> )¶
Instance Methods¶
dispose()¶
Disposes this Property
reset()¶
Resets the current property (if it's a Property instead of a TinyProperty)
set( value : ThisValueType )¶
Prevent setting this Property manually if it is not marked as bidirectional.
isSettable() : boolean¶
Returns true if this Property value can be set externally, by set() or .value =
Instance Properties¶
derive : ( u: OuterValueType ) => TReadOnlyProperty<InnerValueType>¶
(protected, readonly)
map : ( v: InnerValueType ) => ThisValueType¶
(protected, readonly)
inverseMap : ( t: ThisValueType ) => InnerValueType¶
(protected, readonly)
bidirectional : boolean¶
(protected, readonly)
Type DynamicPropertyOptions¶
- bidirectional?: boolean
If set to true then changes to this Property (if valuePropertyProperty.value is non-null at the time) will also be made to derive( valuePropertyProperty.value ). - defaultValue?: InnerValueType
If valuePropertyProperty.value === null, this dynamicProperty will act instead like derive( valuePropertyProperty.value ) === new Property( defaultValue ). Note that if a custom map function is provided, it will be applied to this defaultValue to determine our Property's value. - derive?: ( ( outerValue: OuterValueType ) => TReadOnlyProperty<InnerValueType> ) | KeysMatching<OuterValueType, TReadOnlyProperty<InnerValueType>>
Maps a non-null valuePropertyProperty.value into the Property to be used. See top-level documentation for usage. If it's a string, it will grab that named property out (e.g. it's like passing u => u[ derive ]) NOTE: This accepts TReadOnlyProperty, but if you have bidirectional:true it must be a full TProperty. This is not currently type checked. NOTE there are constraints using derive: 'string' when using parametric type parameters. See https://github.com/phetsims/projectile-data-lab/issues/10 - map?: ( ( innerValue: InnerValueType ) => ThisValueType ) | KeysMatching<InnerValueType, ThisValueType>
Maps our input Property value to/from this Property's value. See top-level documentation for usage. If it's a string, it will grab that named property out (e.g. it's like passing u => u[ derive ]) - inverseMap?: ( ( value: ThisValueType ) => InnerValueType ) | KeysMatching<ThisValueType, InnerValueType>
- & PropertyOptions<ThisValueType>
Type TNullableProperty¶
TReadOnlyProperty<T | null> | TReadOnlyProperty<T>
Source Code¶
See the source for DynamicProperty.ts in the axon repository.