Observables are the subjects in the
observer pattern.
They let you create relationships between objects
where one object (or objects) listens for and responds to changes in another object.
Most of the core objects in CanJS are observables. Understanding how to effectively
work with observables lies at the heart of understanding how to build successful
CanJS applications.
In this section, we’ll review the two observables that make up the core of most CanJS objects:
can.Map and can.List are often extended to create observable types. For example,
can.Model and can.route are
based on can.Map, and a can.Component’s viewModel
is a can.Map.
Creating Instances
To create a Map, call new can.Map(object). This will give you a map
with the same properties and values as the object you passed in to the can.Map constructor.
To create a List, call new can.List(array). This will give you a List with the same elements as the
array you passed into the can.List constructor.
var pagination = new can.Map({page: 1, perPage: 25, count: 1388});
pagination.attr('perPage'); // 25
var hobbies = new can.List(['programming', 'bball', 'party rocking']);
hobbies.attr(2); // 'party rocking'
Manipulating properties
The attr method is
used to read a property from, or write a property to a can.Map or can.List.
While you can read the properties of a can.Map or can.List directly off
of the object, to take advantage of the observable functionality you must
use the .attr syntax.
Extending a can.Map (or can.List) lets you create custom observable
types. The following extends can.Map to create a Paginate type that
has a .next() method:
When a property on a Map is changed with attr, it will emit an event with the
name of the changed property. You can bind
to those events and perform some action:
Although bind and its corresponding unbind method exist, there's almost no
reason to ever use them! This is because there are better ways to perform
the common actions that would require binding to an observable.
For example, stache templates will automatically update when an observable changes:
The other common use case is to create some new, derived value. can.compute
or define getters lets you use functional
(and reactive) programming techniques to derive new values from source
state.
For example, we can create a virtual page observable that derives its value from the
offset and limit:
Using computes and define getters are very similar to using functional-reactive programming
event streams. Given some source state, they are able to derive and combine it into new values.
Computes and define getters are easier, but less powerful than event streams. Computes
and define getters only respond to changes in values where event streams
are also able to respond to events. However, computes and define getters
are eaiser to express and automatically manage subscriptions to source values.
For example, consider deriving a total for one of two menus depending on the time
of day:
var lunch = new can.List([
{name: "nachos", price: 10.25},
{name: "water", price: 0},
{name: "taco", price: 3.25}
]);
var dinner = new can.List([
{name: "burrito", price: 12.25},
{name: "agua", price: 1.20}
]);
var timeOfDay = can.compute("lunch");
var total = can.compute(function(){
var list = timeOfDay() === "lunch" ? lunch : dinner;
var sum = 0;
list.forEach(function(item){
sum += item.attr("price");
});
return sum;
});
In this example, total will listen to not only timeOfDay, but
also when items are added or removed to lunch or dinner, and each item's
price. Furthermore, it only listens to just what needs to be listened to. It will
listen to either lunch or dinner, but not both.
In the next chapter we'll expand on the use of the define plugin
and show how it can handle asyncronous derived data like AJAX requests.
Iterating though a Map
If you want to iterate through the properties on a Map, use each:
CanJS also provides observable arrays with can.List.
can.List inherits from can.Map. A can.List works much the same way a
can.Map does, with the addition of methods useful for working with
arrays:
In this Chapter
can.Map
, andcan.List
Observables are the subjects in the observer pattern. They let you create relationships between objects where one object (or objects) listens for and responds to changes in another object. Most of the core objects in CanJS are observables. Understanding how to effectively work with observables lies at the heart of understanding how to build successful CanJS applications.
In this section, we’ll review the two observables that make up the core of most CanJS objects:
can.Map
- Used for Objects.can.List
- Used for Arrays.can.Map
andcan.List
are often extended to create observable types. For example, can.Model and can.route are based oncan.Map
, and acan.Component
’sviewModel
is acan.Map
.Creating Instances
To create a Map, call
new can.Map(object)
. This will give you a map with the same properties and values as the object you passed in to thecan.Map
constructor.To create a List, call
new can.List(array)
. This will give you a List with the same elements as the array you passed into thecan.List
constructor.Manipulating properties
The
attr
method is used to read a property from, or write a property to acan.Map
orcan.List
. While you can read the properties of acan.Map
orcan.List
directly off of the object, to take advantage of the observable functionality you must use the.attr
syntax.Properties can be removed by using
removeAttr
, which is equivalent to thedelete
keyword:Extending a Map
Extending a
can.Map
(orcan.List
) lets you create custom observable types. The following extendscan.Map
to create a Paginate type that has a.next()
method:Responding to changes
When a property on a Map is changed with
attr
, it will emit an event with the name of the changed property. You can bind to those events and perform some action:Although
bind
and its correspondingunbind
method exist, there's almost no reason to ever use them! This is because there are better ways to perform the common actions that would require binding to an observable.For example,
stache
templates will automatically update when an observable changes:The other common use case is to create some new, derived value. can.compute or define getters lets you use functional (and reactive) programming techniques to derive new values from source state.
For example, we can create a virtual
page
observable that derives its value from theoffset
andlimit
:In this example
page
will automatically be updated when eitheroffset
orlimit
change.However,
page
is more commonly created as a "virtual" property of thePaginate
Map type using a define getter:Using computes and define getters are very similar to using functional-reactive programming event streams. Given some source state, they are able to derive and combine it into new values.
Computes and define getters are easier, but less powerful than event streams. Computes and define getters only respond to changes in values where event streams are also able to respond to events. However, computes and define getters are eaiser to express and automatically manage subscriptions to source values.
For example, consider deriving a total for one of two menus depending on the time of day:
In this example,
total
will listen to not onlytimeOfDay
, but also when items are added or removed tolunch
ordinner
, and each item'sprice
. Furthermore, it only listens to just what needs to be listened to. It will listen to eitherlunch
ordinner
, but not both.In the next chapter we'll expand on the use of the define plugin and show how it can handle asyncronous derived data like AJAX requests.
Iterating though a Map
If you want to iterate through the properties on a Map, use
each
:Observable Arrays
CanJS also provides observable arrays with
can.List
.can.List
inherits fromcan.Map
. Acan.List
works much the same way acan.Map
does, with the addition of 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 events are emitted that you can listen for, as well. See the API for Lists for more information.
‹ Application Foundations The Define Plugin ›