set
Specify what happens when a value is set on a map attribute.
set( [newVal,] [setValue] )
A set function defines the behavior of what happens when a value is set on a Map. It is typically used to:
- Add or remove other attributes as side effects
- Coerce the set value into an appropriate action
The behavior of the setter depends on the number of arguments specified. This means that a setter like:
define: {
prop: {
set: function(){}
}
}
behaves differently than:
define: {
prop: {
set: function(newVal){}
}
}
Parameters
- newVal
{*}
:The type function coerced value the user intends to set on the Map.
- setValue
{function(newValue)}
:A callback that can set the value of the property asyncronously.
Returns
{*|undefined}
:
If a non-undefined value is returned, that value is set as the attribute value.
If an undefined
value is returned, the behavior depends on the number of
arguments the setter declares:
- If the setter does not specify the
newValue
argument, the attribute value is set to whatever was passed to attr. - If the setter specifies the
newValue
argument only, the attribute value will be removed. - If the setter specifies both
newValue
andsetValue
, the value of the property will not be updated untilsetValue
is called.
Use
An attribute's set
function can be used to customize the behavior of when an attribute value is set
via .attr()
. Lets see some common cases:
Side effects
The following makes setting a page
property update the offset
:
define: {
page: {
set: function(newVal){
this.attr('offset', (parseInt(newVal) - 1) *
this.attr('limit'));
}
}
}
The following makes changing makeId
remove the modelId
property:
define: {
makeId: {
set: function(newValue){
// Check if we are changing.
if(newValue !== this.attr("makeId")) {
this.removeAttr("modelId");
}
// Must return value to set as we have a `newValue` argument.
return newValue;
}
}
}
Asynchronous Setter
The following shows an async setter:
define: {
prop: {
set: function( newVal, setVal){
$.get("/something", {}, setVal );
}
}
}
Behavior depends on the number of arguments.
When a setter returns undefined
, its behavior changes depending on the number of arguments.
With 0 arguments, the original set value is set on the attribute.
MyMap = Map.extend({
define: {
prop: {set: function(){}}
}
})
var map = new MyMap({prop : "foo"});
map.attr("prop") //-> "foo"
With 1 argument, undefined
will remove the property.
MyMap = Map.extend({
define: {
prop: {set: function(newVal){}}
}
})
var map = new MyMap({prop : "foo"});
Map.keys(map) //-> []
With 2 arguments, undefined
leaves the property in place. It is expected
that setValue
will be called:
MyMap = Map.extend({
define: {
prop: {set: function(newVal, setValue){}}
}
})
var map = new MyMap({prop : "foo"});
map.attr("prop") //-> "foo"
Side effects
A set function provides a useful hook for performing side effect logic as a certain property is being changed.
For example, in the example below, Paginator can.Map includes a page
property, which derives its value entirely from other properties (limit and offset). If something tries to set the page
directly, the set method will set the value of offset
:
var Paginate = Map.extend({
define: {
page: {
set: function (newVal) {
this.attr('offset', (parseInt(newVal) - 1) * this.attr('limit'));
},
get: function () {
return Math.floor(this.attr('offset') / this.attr('limit')) + 1;
}
}
}
});
var p = new Paginate({limit: 10, offset: 20});
Merging
By default, if a value returned from a setter is an object, array, can.Map, or can.List, the effect will be to replace the property with the new object completely.
Contact = Map.extend({
define: {
info: {
set: function(newVal){
return newVal;
}
}
}
})
var alice = new Contact({
info: {name: 'Alice Liddell', email: 'alice@liddell.com'}
});
alice.attr(); // {name: 'Alice Liddell', 'email': 'alice@liddell.com'}
alice.info._cid; // '.map1'
alice.attr('info', {name: 'Allison Wonderland', phone: '888-888-8888'});
alice.attr(); // {name: 'Allison Wonderland', 'phone': '888-888-8888'}
alice.info._cid; // '.map2'
By contrast, if you access a property of a Map using .attr
, then change it by calling .attr
on it directly, the new properties will be merged with the existing nested Map, not replaced.
var contact = new Map({
'info' : {'breath' : 'smells like roses'}
});
var newInfo = {'teeth' : 'shiny and clean'};
contact.attr('info').attr(newInfo); // info is now a merged object
If you would rather have the new Map or List merged into the current value, not replaced, call
.attr
inside the setter:
Contact = Map.extend({
define: {
info: {
set: function(newVal){
this.info.attr(newVal);
return this.info;
}
}
}
})
var alice = new Contact({
info: {name: 'Alice Liddell', email: 'alice@liddell.com'}
});
alice.attr(); // {name: 'Alice Liddell', 'email': 'alice@liddell.com'}
alice.info._cid; // '.map1'
alice.attr('info', {name: 'Allison Wonderland', phone: '888-888-8888'});
alice.attr();
//{
// name: 'Allison Wonderland',
// email: 'alice@liddell.com',
// phone: '888-888-8888'
//}
alice.info._cid; // '.map1'
Batched Changes
By default, calls to set methods are wrapped in a call to batch.start and batch.stop, so if a set method has side effects that set more than one property, all these sets are wrapped in a single batch for better performance.