-
Notifications
You must be signed in to change notification settings - Fork 1
observes
@page Observes Observable Objects @parent Tutorial 1
@body Observes made with can.Observe will let you set and remove properties like on normal objects, but they also emit events when properties change that you can listen to. Observes also make it easy to work with nested properties and get the events you expect. Both can.Route and Models are based on can.Observe, but Observes are also useful on their own: for example, to maintain client-side state like pagination data.
To create an Observe, call new can.Observe(obj). This will give you an Observe
with the same properties and values as obj. can.Observe also provides
observable arrays called Lists. To create a List, call new can.Observe.List(array), which will give you a List with the same elements as
array.
@codestart var pagination = new can.Observe({page: 1, perPage: 25, count: 1388}); pagination.perPage; // 25
var hobbies = new can.Observe.List(['programming', 'basketball', 'partying']); hobbies[2]; // 'partying' @codeend
Though you can read properties from an Observe in the normal way, if you want to set the value of a property, you'll need to use attr(). There are a couple ways to use attr to set a property, and it can also be used to get property values, or get a regular object back from an Observe:
@codestart pagination.attr('perPage'); // 25 pagination.attr('perPage', 50); pagination.perPage; // 50
pagination.attr({page: 10, lastVisited: 1}); pagination.attr(); // {page: 10, perPage: 50, count: 1388, lastVisited: 1} @codeend
It might not make sense to get properties from an Observe using attr when you
can read them directly, but when reading properties for use in a template with
can.view or when using a property in a Compute, using attr tells the
template or compute you want to watch that value in case it changes.
Properties can be removed from Observes with removeAttr. This is the
equivalent of the delete keyword:
@codestart pagination.removeAttr('count'); pagination.attr(); // {page: 10, perPage: 50, lastVisited: 1} @codeend
When a property on an Observe is changed with attr, the Observe will emit two
events: a change event and an event with the same name as the property that
was changed. You can listen for these events by using
bind:
@codestart paginate.bind('change', function(event, attr, how, newVal, oldVal) { attr; // 'perPage' how; // 'set' newVal; // 30 oldVal; // 50 }); paginate.bind('perPage', function(event, newVal, oldVal) { newVal; // 30 oldVal; // 50 });
paginate.attr('perPage', 30); @codeend
You can similarly stop listening to these events by using unbind:
@codestart var timesChanged = 0, changeHandler = function() { timesChanged++; }, obs = new can.Observe({value: 10});
obs.bind('change', changeHandler); obs.attr('value', 20); timesChanged; // 1
obs.unbind('change', changeHandler); obs.attr('value', 30); timesChanged; // 1 @codeend
If you want to iterate through the properties on an Observe, use each:
@codestart paginate.each(function(val, key) { console.log(key + ': ' + val); });
// The console shows: // page: 10 // perPage: 30 // lastVisited: 1 @codeend
As mentioned above, CanJS also provides observable arrays with can.Observe.List. can.Observe.List inherits from can.Observe, so a List works much the same way an Observe does, with the addition of several methods useful for working with arrays:
- indexOf, which looks for an item in a List.
- pop, which removes the last item from a List.
- push, which adds an item to the end of a List.
- shift, which removes the first item from a List.
- unshift, which adds an item to the front of a List.
- splice, which removes and inserts items anywhere in a List.
When these methods are used to modify a List, the appropriate events are emitted. See the API for Lists for more information on the arguments passed to those event handlers.
CanJS also provides a way to make values themselves observable with can.compute. A Compute represents a value that can be read, set, and listened to just like an Observe.
A simple Compute contains a single value, and is created by calling
can.compute(value). This value can be read, set, and listened to:
@codestart // create a Compute var age = can.compute(25), previousAge = 0;
// read the Compute's value age(); // 25
// listen for changes in the Compute's value age.bind('change', function(ev, newVal, oldVal) { previousAge = oldVal; });
// set the Compute's value age(26);
age(); // 26 previousAge; // 25 @codeend
Computes can also get values derived from other observable values. This type of
Compute is created by calling can.compute(getterFunction). When the values
the Compute is derived from changes, the value of the Compute changes:
@codestart var name = new can.Observe({ first: 'Alice', last: 'Liddell' }); var fullName = can.compute(function() { // We use attr to read the values so the compute knows what to listen to. return name.attr('first') + ' ' + name.attr('last'); }); var previousName = '';
fullName(); // 'Alice Liddell'
fullName.bind('change', function(ev, newVal, oldVal) { previousName = oldVal; });
name.attr({ first: 'Allison', last: 'Wonderland' });
fullname(); // 'Allison Wonderland' previousName; // 'Alice Liddell' @codeend
Since the value of the Compute is cached any time a constituent value is changed, reading the value is fast.
Computes are useful for creating a link to a property in an Observe, and one of the most frequent examples of this is when converting from one unit to another.
@codestart // progress ranges from 0 to 1. var project = new can.Observe({ progress: 0.3 }); var percentage = can.compute(function(newVal) { if(newVal !== undefined) { // set a value project.attr('progress', newVal / 100); } else { // get the value return project.attr('progress') * 100; } });
percentage(); // 30
// Setting percentage... percentage(75); // ...updates project.progress! project.progress; // .75 @codeend