value
Specify the behavior of a property by listening to changes in other properties.
value(prop)
The value
behavior is used to compose a property value from events dispatched
by other properties on the map. It's similar to get, but can
be used to build property behaviors that get can not provide.
value
enables techniques very similar to using event streams and functional
reactive programming. Use prop.listenTo
to listen to events dispatched on
the map or other observables, prop.stopListening
to stop listening to those
events if needed, and prop.resolve
to set a new value on the observable.
For example, the following counts the number of times the name
property changed:
import {DefineMap} from "can";
const Person = DefineMap.extend( "Person", {
name: "string",
nameChangeCount: {
value( prop ) {
let count = 0;
prop.listenTo( "name", () => {
prop.resolve( ++count );
} );
prop.resolve( count );
}
}
} );
const p = new Person();
p.on( "nameChangeCount", ( ev, newValue ) => {
console.log( "name changed " + newValue + " times." );
} );
p.name = "Justin"; // logs name changed 1 times
p.name = "Ramiya"; // logs name changed 2 times
If the property defined by value
is unbound, the value
function will be called each time. Use prop.resolve
synchronously
to provide a value.
type, default, get, and set behaviors are ignored when value
is present.
value
properties are not enumerable by default.
Parameters
- prop
{ValueOptions}
:An object of methods and values used to specify the property behavior:
prop.resolve(value)
{function(Any)}
Sets the value of this property asvalue
. During a batch, the last value passed toprop.resolve
will be used as the value.prop.listenTo(bindTarget, event, handler, queue)
{function(Any,String,Fuction,String)}
A function that sets up a binding that will be automatically torn-down when thevalue
property is unbound. Thisprop.listenTo
method is very similar to the listenTo method available on DefineMap. It differs only that it:- defaults bindings within the notifyQueue.
- calls handlers with
this
as the instance. - localizes saved bindings to the property instead of the entire map.
Examples:
// Binds to the map's `name` event: prop.listenTo( "name", handler ); // Binds to the todos `length` event: prop.listenTo( todos, "length", handler ); // Binds to the `todos` `length` event in the mutate queue: prop.listenTo( todos, "length", handler, "mutate" ); // Binds to an `onValue` emitter: prop.listenTo( observable, handler );
prop.stopListening(bindTarget, event, handler, queue)
{function(Any,String,Fuction,String)}
A function that removes bindings registered by theprop.listenTo
argument. Thisprop.stopListening
method is very similar to the stopListening method available on DefineMap. It differs only that it:- defaults to unbinding within the notifyQueue.
- unbinds saved bindings by
prop.listenTo
.
Examples:
// Unbind all handlers bound using `listenTo`: prop.stopListening(); // Unbind handlers to the map's `name` event: prop.stopListening( "name" ); // Unbind a specific handler on the map's `name` event // registered in the "notify" queue. prop.stopListening( "name", handler ); // Unbind all handlers bound to `todos` using `listenTo`: prop.stopListening( todos ); // Unbind all `length` handlers bound to `todos` // using `listenTo`: prop.stopListening( todos, "length" ); // Unbind all handlers to an `onValue` emitter: prop.stopListening( observable );
prop.lastSet
{can-simple-observable}
An observable value that gets set when this property is set. You can read its value or listen to when its value changes to derive the property value. The following makesproperty
behave like a normal object property that can be get or set:import {DefineMap} from "can"; const Example = DefineMap.extend( { property: { value: function( prop ) { console.log( prop.lastSet.get() ); //-> "test" // Set `property` initial value to set value. prop.resolve( prop.lastSet.get() ); // When the property is set, update `property`. prop.listenTo( prop.lastSet, prop.resolve ); } } } ); const e = new Example(); e.property = "test"; e.serialize();
Returns
{function}
:
An optional teardown function. If provided, the teardown function
will be called when the property is unbound after stopListening()
is used to
remove all bindings.
The following time
property increments every second. Notice how a function
is returned to clear the interval when the property is returned:
import {DefineMap} from "can";
const Timer = DefineMap.extend( "Timer", {
time: {
value( prop ) {
prop.resolve( new Date() );
const interval = setInterval( () => {
const date = new Date()
console.log( date.getSeconds() ); //-> logs a new date every second
prop.resolve( date );
}, 1000 );
return () => {
clearInterval( interval );
};
}
}
} );
const timer = new Timer();
timer.listenTo( "time", () => {} );
setTimeout( () => {
timer.stopListening( "time" );
}, 5000 ); //-> stops logging after five seconds
Use
The value
behavior should be used where the get behavior can
not derive a property value from instantaneous values. This often happens in situations
where the fact that something changes needs to saved in the state of the application.
Our next example shows how get should be used with the
fullName
property. The following creates a fullName
property
that derives its value from the instantaneous first
and last
values:
import {DefineMap} from "can";
const Person = DefineMap.extend( "Person", {
first: "string",
last: "string",
get fullName() {
return this.first + " " + this.last;
}
} );
const p = new Person({ first: "John", last: "Smith" });
console.log( p.fullName ); //-> "John Smith"
get is great for these types of values. But get is unable to derive property values based on the change of values or the passage of time.
The following fullNameChangeCount
increments every time fullName
changes:
import {DefineMap} from "can";
const Person = DefineMap.extend( "Person", {
first: "string",
last: "string",
fullName: {
get() {
return this.first + " " + this.last;
}
},
fullNameChangeCount: {
value( prop ) {
let count = 0;
prop.resolve( 0 );
prop.listenTo( "fullName", () => {
prop.resolve( ++count );
} );
}
}
} );
const p = new Person({ first: "John", last: "Smith" });
p.on("fullNameChangeCount", () => {});
p.first = "Justin";
p.last = "Meyer";
console.log(p.fullNameChangeCount); //-> 2