You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Property is our most basic and simplest form of the Observer pattern. It is used as a wrapper of a Javascript field, called its value. Observers are notified when its value is set to a different value. Observers add listeners through the `link` and `lazyLink` methods and remove listeners through the `unlink` method.
719
721
720
-
Very important pattern for new PhET developers
722
+
In general, with PhET simulation code, it is encouraged to use the many subtypes of Property, which depend on the type of its internal value (ie. the values of `NumberProperty` are numbers). Some other common sub-types that are used are `StringProperty`, `BooleanProperty`, `Vector2Property`, etc.
Please see the [Model-View-Controller (MVC)](https://github.com/phetsims/phet-info/blob/master/doc/phet-software-design-patterns.md#model-view-controller-mvc) section of this document for context.
725
726
726
-
NOTE: when this gets fleshed out, scenery input system, options callbacks should be passed the SCENERY/Event from their
727
-
input listeners.
727
+
In short, the observer pattern acts as a key communicator within the model-view hierarchy of PhET simulations. The model is oblivious to the view, so the model uses the observer pattern for the view (which has a reference to the model) to observe the state of the model and correctly render a mirrored view representation.
728
+
729
+
See for example:
730
+
```js
731
+
// model
732
+
classBall {
733
+
constructor( ... ) {
734
+
...
735
+
736
+
// @public {Vector2Property} - the position (coordinates) of the Ball, in meters.
737
+
this.positionProperty=newVector2Property( newVector2( ... ) /** the initial wrapped value of the Property */ );
738
+
}
739
+
}
740
+
741
+
// view
742
+
classBallNodeextendsNode {
743
+
744
+
constructor( ball, ... ) {
745
+
...
746
+
747
+
// Observe when the ball's position changes through the Property API.
748
+
ball.positionProperty.link( position=> { // position is the current value of ball.positionProperty
749
+
this.center=modelViewTransform.modelToViewPosition( position );
750
+
} );
751
+
}
752
+
}
753
+
```
754
+
755
+
In this example you can see a how the Ball view updates itself by observing the `positionProperty` of the ball model. If you were to call
756
+
```js
757
+
ball.positionProperty.value=newVector( ... );
758
+
```
759
+
or
760
+
```js
761
+
ball.positionProperty.set( newVector( ... ) );
762
+
```
763
+
its listeners will be invoked.
764
+
765
+
#### Other Notes
766
+
- Ensure that you aren't causing any memory leaks. Property holds references to its listeners, so, in the case above, if you were to dispose `BallNode` it would be kept by the Property and wouldn't be garbage collected. Reference the [Dispose](https://github.com/phetsims/phet-info/blob/master/doc/phet-software-design-patterns.md#dispose) section.
767
+
- Generally, listeners don't normally set the Property that it is listening too. This is called a reentrant:
768
+
```js
769
+
constmassProperty=newNumberProperty( 4 );
770
+
771
+
massProperty.lazyLink( mass=> {
772
+
massProperty.value=5; // Reentrant, would cause an assertion error.
773
+
} );
774
+
```
775
+
If, however, it is absolutely necessary to set the property value, you can pass the `reentrant: true` option to the Property instance.
776
+
- In the examples above, the names of Properties are suffixed with`Property` (ie. `massProperty`, `positionProperty`, etc.). Wetry to be verbose withthis practice to emphasize a distinction between a normal javascript field and a wrapped Property.
DerivedProperty is another Property sub-type, but unlike other subtypes (which are mostly for type-specific values), DerivedProperty is a generic Property whose value is determined based on other Properties, called its dependencies.
In this example, `[ this.massProperty, this.accelerationProperty ]` is the dependencies of the DerivedProperty, and the second parameter (the lambda) is the derivation function.
788
+
789
+
If the `massProperty` OR the `accelerationProperty` is set to a different value, then its value is recomputed based on what the derivation function returns, which is passed the values of the dependencies in corresponding order.
790
+
791
+
DerivedProperty usually has the same role in the mvc hierarchy, as outlined [above](https://github.com/phetsims/phet-info/blob/master/doc/phet-software-design-patterns.md#role-in-mvc). It is still a subtype of Property, so observers are notified when its value changes and observers are added through `link` and `lazyLink` methods. However, note that the value of a DerivedProperty instance cannot be set externally.
792
+
793
+
#### OtherNotes
794
+
-AllPropertiesanditssubclassesuse [validate](https://github.com/phetsims/axon/blob/master/js/validate.js), meaning the [ValidatorDef](https://github.com/phetsims/axon/blob/master/js/ValidatorDef.js.) options are apart of its API.
795
+
796
+
For type-specific subclasses like `NumberProperty`, these are set foryou. However, this is needed forDerivedProperty. Sofor the example above, the declaration should look like
Multilink is a convenience classthat is used to observe multiple Properties with the same observer functionality. Similar to DerivedProperty, it has its "dependencies"of Properties, and when any dependency's value changes, the observer is invoked with the values of the dependencies in corresponding order. However, it is *not* a subclass of Property and doesn't conform to the Property API.
809
+
810
+
Note that Multilinks are not created through its native constructor. Rather, they are created through static creator methods of Property (`Property.multilink`and`Property.unmultilink`).
811
+
812
+
#### Other Notes
813
+
- In some use cases of `Multilink` and `DerivedProperty`, the observer needs to know which Property caused the notification. One solution is to add independent listeners to each dependency and turn the DerivedProperty into a Property that is modified by each listener. Please reference https://github.com/phetsims/axon/issues/259.
ObservableArray is another common iteration of the Observer pattern. ObservableArrays are a wrapper class of an array that notifies observers when items are added or removed from the Array. Its API closely resembles the prototype of native arrays (it contains a `push`, `forEach`, `map`, etc. methods).
818
+
819
+
#### Role in MVC
820
+
Like `Property`, `ObservableArray` can act as a key communicator within the model-view hierarchy of PhET simulations.
821
+
822
+
One common pattern is:
823
+
```js
824
+
// model
825
+
// @public {ObservableArray.<Cart>}
826
+
this.carts=newObservableArray();
827
+
828
+
829
+
// view
830
+
this.carts.addItemAddedListener( cart=> {
831
+
this.addChild( newCartNode( cart ) );
832
+
} );
833
+
```
834
+
835
+
Then, wherever `this.carts.push( newCart() )` is called, a new CartNode is added through the observer.
836
+
837
+
As a reminder from above, observers are referenced as a listener in the ObservableArray, so be sure to call `removeItemAddedListener()` to release listeners when needed.
You may see `Emitters` used in the shared common code between simulation. Emitters are a generic event-based class that follows the observer pattern to allow clients to subscribe (through the `addListener` method) to a single specific event.
841
+
842
+
Usually, Emitters are not needed in sim-specific code, and most of PhET's observing can be achieved with the classes outlined above.
843
+
844
+
### Events
845
+
Another form of PhET's version of the Observer pattern is through user-triggered events, such as dragging, clicking (pressing), etc. This is all done through the scenery input system.
846
+
847
+
Scenery Nodes support `FireListener`, `DragListener`, `PressListener`, etc. Listeners subscribe to when the user does a specified event, which may alter the simulation. Listeners are often passed a [SceneryEvent](https://github.com/phetsims/scenery/blob/master/js/input/SceneryEvent.js).
848
+
849
+
#### Role in MVC
850
+
View classes observing scenery input events are a key communicator in the model-view hierarchy. For instance, user input may propagate and affect model properties or may create new model objects, as described in the [creator-pattern](https://github.com/phetsims/phet-info/blob/master/doc/phet-software-design-patterns.md#creator-with-drag-forwarding) section.
851
+
852
+
853
+
As a reminder from above, Input Listeners (such as `DragListener`) are internally referenced in Node, so be sure to call `removeInputListener()` to release listeners if needed.
728
854
729
-
NOTE: the author of this section should include patterns/strategies for how to identify the notifying Property in
730
-
DerivedProperty and Multilink; see comments in expressed in https://github.com/phetsims/axon/issues/259.
0 commit comments