UNPKG

can-define

Version:

Create observable objects with JS dot operator compatibility

232 lines (166 loc) 6.18 kB
@function can-define.types.get get @parent can-define.behaviors Specify what happens when a certain property is read on a map. `get` functions work like a [can-compute] and automatically update themselves when a dependent observable value is changed. @signature `get( [lastSetValue] )` Defines the behavior when a property value is read on a instance. Used to provide properties that derive their value from other properties on the object, or the property value that was set on the object. Specify `get` like: ```js { propertyName: { get: function() { /* ... */ } }, propertyName: { get: function( lastSetValue ) { /* ... */ } } } ``` @param {*} [lastSetValue] The value last set by `instance.propertyName = value`. Typically, _lastSetValue_ should be an observable value, like a [can-simple-observable] or promise. If it's not, it's likely that a [can-define.types.set] should be used instead. @return {*} The value of the property. @signature `get( lastSetValue, resolve(value) )` Asynchronously defines the behavior when a value is read on an instance. Used to provide property values that are available asynchronously. Only observed properties (via [can-event-queue/map/map.on], [can-event-queue/map/map.addEventListener], etc) will be passed the `resolve` function. It will be `undefined` if the value is not observed. This is for memory safety. Specify `get` like: ```js { propertyName: { get: function( lastSetValue, resolve ) { /* ... */ } } } ``` @param {*} lastSetValue The value last set by `instance.propertyName = value`. @param {function|undefined} resolve(value) Updates the value of the property. This can be called multiple times if needed. Will be `undefined` if the value is not observed. @return {*} The value of the property before `resolve` is called. Or a value for unobserved property reads to return. @body ## Use Getter methods are useful for: - Defining virtual properties on a map. - Defining property values that change with their _internal_ set value. ## Virtual properties Virtual properties are properties that don't actually store any value, but derive their value from some other properties on the map. Whenever a getter is provided, it is wrapped in a [can-compute], which ensures that whenever its dependent properties change, a change event will fire for this property also. ```js import { DefineMap } from "can"; const Person = DefineMap.extend( { first: "string", last: "string", fullName: { get: function() { return this.first + " " + this.last; } } } ); const p = new Person( { first: "Justin", last: "Meyer" } ); console.log(p.fullName); //-> "Justin Meyer" p.on( "fullName", function( ev, newVal ) { console.log(newVal); //-> "Lincoln Meyer"; } ); p.first = "Lincoln"; ``` @codepen ## Asynchronous virtual properties Often, a virtual property's value only becomes available after some period of time. For example, given a `personId`, one might want to retrieve a related person: ```js import { DefineMap } from "can"; const AppState = DefineMap.extend( { personId: "number", person: { get: function( lastSetValue, resolve ) { Person.get( { id: this.personId } ) .then( function( person ) { resolve( person ); } ); } } } ); ``` Asynchronous properties should be bound to before reading their value. If they are not bound to, the `get` function will be called each time. The following example will make multiple `Person.get` requests: ```js const state = new AppState( { personId: 5 } ); state.person; //-> undefined // called sometime later /* ... */ state.person; //-> undefined ``` However, by binding, the compute only reruns the `get` function once `personId` changes: ```js const state = new AppState( { personId: 5 } ); state.on( "person", function() {} ); state.person; //-> undefined // called sometime later state.person; //-> Person<{id: 5}> ``` A template like [can-stache] will automatically bind for you, so you can pass `state` to the template like the following without binding: ```js const template = stache( "<span>{{person.fullName}}</span>" ); const state = new AppState( {} ); const frag = template( state ); state.personId = 5; frag.childNodes[ 0 ].innerHTML; //=> "" // sometime later frag.childNodes[ 0 ].innerHTML; //=> "Lincoln Meyer" ``` The magic tags are updated as `personId`, `person`, and `fullName` change. ## Properties values that change with their _internal_ set value A getter can be used to derive a value from a set value. A getter's `lastSetValue` argument is the last value set by `instance.propertyName = value`. For example, a property might be set to a compute, but when read, provides the value of the compute. ```js import { DefineMap, SimpleObservable, Reflect } from "can"; const MyMap = DefineMap.extend( { value: { get: function( lastSetValue ) { return lastSetValue.value; } } } ); const map = new MyMap(); const observable = new SimpleObservable( 1 ); map.value = observable; console.log(map.value); //-> 1 Reflect.setValue(observable, 2); console.log(map.value); //-> 2 ``` @codepen This technique should only be used when the `lastSetValue` is some form of observable, that when it changes, can update the `getter` value. For simple conversions, [can-define.types.set] or [can-define.types.type] should be used. ## Updating the virtual property value It's common to update virtual property values instead of replacing it. The following example creates an empty `locationIds` [can-define/list/list] when a new instance of `Store` is created. However, as `locations` change, the [can-define/list/list] will be updated with the `id`s of the `locations`. ```js import { DefineMap, DefineList } from "can"; const Store = DefineMap.extend( { locations: { Default: DefineList }, locationIds: { Default: DefineList, get: function( initialValue ) { const ids = this.locations.map( function( location ) { return location.id; } ); return initialValue.replace( ids ); } } } ); const s = new Store(); console.log(s.locationIds[0]); //-> undefined s.locations.push({ id: 1 }); console.log(s.locationIds[0]); //-> 1 ``` @codepen