can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
675 lines (497 loc) • 19.8 kB
HTML
<!--####################################################################
THIS IS A GENERATED FILE -- ANY CHANGES MADE WILL BE OVERWRITTEN
INSTEAD CHANGE:
source: [object Object]
@page guides/pmo/Observables
######################################################################## -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>CanJS - Observables</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" type="text/css" href="../../static/bundles/bit-docs-site/static.css">
<link rel="shortcut icon" sizes="16x16 24x24 32x32 48x48 64x64" href="/docs/images/canjs_favicon.ico">
<link rel="apple-touch-icon" sizes="57x57" href="../../../docs/images/canjs_favicon_57x57.png">
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="../../../docs/images/canjs_favicon_57x57.png">
<link rel="apple-touch-icon" sizes="72x72" href="../../../docs/images/canjs_favicon_72x72.png">
<link rel="apple-touch-icon" sizes="114x114" href="../../../docs/images/canjs_favicon_114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="../../../docs/images/canjs_favicon_128x128.png">
<link rel="apple-touch-icon" sizes="144x144" href="../../../docs/images/canjs_favicon_144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="../../../docs/images/canjs_favicon_152x152.png">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta name="apple-mobile-web-app-status-bar-style" content="white-translucent">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-2302003-11', 'auto');
ga('send', 'pageview');
</script>
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger"/>
<label for="nav-trigger">Menu</label>
<div id="everything">
<div id="left" class="column">
<div class="top-left">
<div class="brand">
<div class="logo">
<a href="../../../index.html" alt="CanJS"></a>
<div class="dropdown project-dropdown">
<a href="https://donejs.com/">DoneJS</a>
<a href="http://stealjs.com/">StealJS</a>
<a href="http://jquerypp.com/">jQuery ++</a>
<a href="https://funcunit.com/">FuncUnit</a>
<a href="http://documentjs.com/">DocumentJS</a>
</div>
</div>
<div class="version">
<div class="version-number">
3.0.0
</div>
<div class="dropdown version-dropdown">
<a href="https://v2.canjs.com">2.3.27</a>
</div>
</div>
</div>
<div class="search-bar">
<p>
</p>
</div>
</div>
<div class="bottom-left">
<div class="social-side-container">
<ul class="social-side">
<li>
<a class="header-mobile github" href="https://github.com/canjs/canjs" target="_blank"><img class="social-icon-small" src="../../../docs/images/github.png">Github</a>
</li>
<li>
<a class="header-mobile twitter" href="https://twitter.com/canjs" target="_blank"><img class="social-icon-small" src="../../../docs/images/twitter.png">Twitter</a>
</li>
</ul>
<ul class="social-side">
<li>
<a class="header-mobile" href="https://gitter.im/canjs/canjs" target="_blank">Chat</a>
</li>
<li>
<a class="header-mobile" href="http://forums.donejs.com/c/canjs" target="_blank">Forum</a>
</li>
</ul>
</div>
<ul>
<li class="
">
<a class="page"
href="Components.html"
title="">
Components
</a>
</li>
<li class="
">
<a class="page"
href="ApplicationDesign.html"
title="">
Application Design
</a>
</li>
<li class="
">
<a class="page"
href="../../pmo/Setup.html"
title="">
Setup
</a>
</li>
<li class="
">
<a class="page"
href="Constructors.html"
title="">
Constructors
</a>
</li>
<li class="
">
<a class="page"
href="TheDefinePlugin.html"
title="">
The Define Plugin
</a>
</li>
<li class="
">
<a class="page"
href="StacheTemplates.html"
title="">
Stache Templates
</a>
</li>
<li class="
">
<a class="page"
href="AppStateAndRouting.html"
title="">
App State and Routing
</a>
</li>
<li class="current
parent
expanded">
<a class="page"
href="Observables.html"
title="">
Observables
</a>
</li>
<li class="
">
<a class="page"
href="ViewModels.html"
title="">
View Models
</a>
</li>
<li class="
">
<a class="page"
href="DataModelsAndFixtures.html"
title="">
Data Models and Fixtures
</a>
</li>
<li class="
">
<a class="page"
href="LoadingStates.html"
title="">
Loading States
</a>
</li>
<li class="
">
<a class="page"
href="EventHandling.html"
title="">
Event Handling
</a>
</li>
<li class="
">
<a class="page"
href="WebServiceCommunication.html"
title="">
Web Service Communication
</a>
</li>
<li class="
">
<a class="page"
href="Recap.html"
title="">
Recap
</a>
</li>
</ul>
</div>
</div>
<div id="right" class="column">
<div class="top-right">
<div class="top-right-top">
<ul class="top-right-bitovi">
<li class="dropdown">
<a href="http://bitovi.com" class="bitovi icon-bits">Bitovi</a>
<ul class="dropdown-menu">
<li><a href="http://bitovi.com">Bitovi.com</a></li>
<li><a href="http://bitovi.com/blog/">Blog</a></li>
<li><a href="http://bitovi.com/consulting/">Consulting</a></li>
<li><a href="http://bitovi.com/training/">Training</a></li>
<li><a href="http://bitovi.com/open-source/">Open Source</a></li>
</ul>
</li>
</ul>
<div class="brand">
<div class="logo">
<a href="../../../index.html" alt="CanJS"></a>
</div>
</div>
<ul class="top-right-links">
<li>
<a href="https://gitter.im/canjs/canjs">Chat</a>
</li>
<li>
<a href="http://forums.donejs.com/c/canjs">Forum</a>
</li>
<li>
<a class="github-button nav-social" href="https://github.com/canjs/canjs" data-count-href="/canjs/canjs/stargazers" data-count-api="/repos/canjs/canjs#stargazers_count">Star</a>
</li>
<li>
<a href="https://twitter.com/canjs" class="twitter-follow-button nav-social" data-show-count="true" data-show-screen-name="false">Follow @canjs</a><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
</li>
</ul>
</div>
<div class="breadcrumb">
<li><a href="../pmo.html">Place My Order Guide</a></li> /
<li><a href="Observables.html">Observables</a></li>
<li class="breadcrumb-dropdown">/ <a> On this page</a>
<ul class="on-this-page"></ul>
</li>
<div class="nav-toggle" title="Back to top"></div>
</div>
</div>
<div class="bottom-right">
<article>
<section class="title">
<div class="page-type">
<h1>Observables</h1>
<div>page</div>
</div>
<section class="description">
</section>
</section>
<section class="on-this-page-table">
</section>
<section class="title-footer">
<ul class="title-links">
<!-- <li><a href="#">docco</a></li> -->
<li><a href="//github.com/canjs/canjs/tree/v3.0.0/docs/can-guides/experiment/pmo/observables.md">source</a></li>
<!-- <li><a href="#">download</a></li> -->
<!-- <li><a href="#">tests</a></li> -->
</ul>
</section>
<section class="body">
<div class="getting-started">
<hr />
<p><strong>In this Chapter</strong></p>
<ul>
<li><code>can.Map</code>, and</li>
<li><code>can.List</code></li>
</ul>
<hr />
<p>Observables are the subjects in the
<a href="http://en.wikipedia.org/wiki/Observer_pattern">observer pattern</a>.
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.</p>
<p>In this section, we’ll review the two observables that make up the core of most CanJS objects:</p>
<ul>
<li><a href="../docs/can.Map.html"><code>can.Map</code></a> - Used for Objects.</li>
<li><a href="../docs/can.List.html"><code>can.List</code></a> - Used for Arrays.</li>
</ul>
<p><code>can.Map</code> and <code>can.List</code> are often extended to create observable types. For example,
<a href="../docs/can.Model.html">can.Model</a> and <a href="../docs/can.route.html">can.route</a> are
based on <code>can.Map</code>, and a <code>can.Component</code>’s <a href="../docs/can.Component.prototype.viewModel.html"><code>viewModel</code></a>
is a <code>can.Map</code>.</p>
<h2>Creating Instances</h2>
<p>To create a Map, call <code>new can.Map(object)</code>. This will give you a map
with the same properties and values as the <em>object</em> you passed in to the <code>can.Map</code> constructor.</p>
<p>To create a List, call <code>new can.List(array)</code>. This will give you a List with the same elements as the
<em>array</em> you passed into the <code>can.List</code> constructor.</p>
<pre><code>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'
</code></pre>
<h2>Manipulating properties</h2>
<p>The <a href="../docs/can.Map.prototype.attr.html"><code>attr</code></a> method is
used to read a property from, or write a property to a <code>can.Map</code> or <code>can.List</code>.
While you can read the properties of a <code>can.Map</code> or <code>can.List</code> directly off
of the object, to take advantage of the observable functionality you must
use the <code>.attr</code> syntax.</p>
<pre><code>var pagination = new can.Map({page: 1, perPage: 25, count: 1388});
pagination.attr('perPage'); // 25
pagination.attr('perPage', 50);
pagination.attr('perPage'); // 50
pagination.attr({page: 10, lastVisited: 1});
pagination.attr(); // {page: 10, perPage: 50, count: 1388, lastVisited: 1}
</code></pre>
<p>Properties can be removed by using <a href="../docs/can.Map.prototype.removeAttr.html"><code>removeAttr</code></a>,
which is equivalent to the <code>delete</code> keyword:</p>
<pre><code>pagination.removeAttr('count');
pagination.attr(); // {page: 10, perPage: 50, lastVisited: 1}
</code></pre>
<h2>Extending a Map</h2>
<p>Extending a <code>can.Map</code> (or <code>can.List</code>) lets you create custom observable
types. The following extends <code>can.Map</code> to create a Paginate type that
has a <code>.next()</code> method:</p>
<pre><code>Paginate = can.Map.extend({
define: {
limit: {
value: 100
},
offset: {
value: 100
},
count: {
value: Infinity
}
},
next: function() {
this.attr('offset', this.attr('offset') + this.attr('limit') );
}
});
var pageInfo = new Paginate();
pageInfo.attr("offset") //-> 100
pageInfo.next();
pageInfo.attr("offset") //-> 200
</code></pre>
<h2>Responding to changes</h2>
<p>When a property on a Map is changed with <code>attr</code>, it will emit an event with the
name of the changed property. You can <a href="../docs/can.Map.prototype.bind.html">bind</a>
to those events and perform some action:</p>
<pre><code>pagination.bind('page', function(event, newVal, oldVal) {
newVal; // 11
oldVal; // 10
$("#page").text("Page: "+newVal);
});
pagination.attr('page', 11);
</code></pre>
<p>Although <code>bind</code> and its corresponding <code>unbind</code> method exist, <strong>there's almost no
reason to ever use them!</strong> This is because there are better ways to perform
the common actions that would require binding to an observable.</p>
<p>For example, <code>stache</code> templates will automatically update when an observable changes:</p>
<pre><code>var template = can.stache("<span id='page'>{{page}}</span>");
$("body").append(template(pagination));
document.getElementById("page").innerHTML //-> "11"
pagination.attr('page', 12);
document.getElementById("page").innerHTML //-> "12"
</code></pre>
<p>The other common use case is to create some new, derived value. <a href="../docs/can.compute.html">can.compute</a>
or <a href="../docs/can.Map.prototype.define.get.html">define getters</a> lets you use functional
(and reactive) programming techniques to derive new values from source
state.</p>
<p>For example, we can create a virtual <code>page</code> observable that derives its value from the
<code>offset</code> and <code>limit</code>:</p>
<pre><code>var pagination = new Paginate({
limit: 10,
offset: 20
});
var page = can.compute(function(){
return Math.floor(pagination.attr('offset') /
pagination.attr('limit')) + 1;
});
page() //-> 3
page.bind("change", function(ev, newValue){
newValue //-> 4
});
pagination.attr("offset",30);
</code></pre>
<p>In this example <code>page</code> will automatically be updated when either <code>offset</code> or <code>limit</code> change.</p>
<p>However, <code>page</code> is more commonly created as a "virtual" property of the <code>Paginate</code> Map type
using a <a href="../docs/can.Map.prototype.define.get.html">define getter</a>:</p>
<pre><code>Paginate = can.Map.extend({
define: {
...
page: {
get: function() {
return Math.floor(this.attr('offset') / this.attr('limit')) + 1;
}
}
},
...
});
var pageInfo = new Paginate({
limit: 10,
offset: 20,
count: 30
});
pageInfo.attr("page") //-> 3
pageInfo.bind("page", function(ev, newVal){
newVal //-> 4
});
pageInfo.next();
</code></pre>
<p>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.</p>
<p>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.</p>
<p>For example, consider deriving a total for one of two menus depending on the time
of day:</p>
<pre><code>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;
});
</code></pre>
<p>In this example, <code>total</code> will listen to not only <code>timeOfDay</code>, but
also when items are added or removed to <code>lunch</code> or <code>dinner</code>, and each item's
<code>price</code>. Furthermore, it only listens to just what needs to be listened to. It will
listen to either <code>lunch</code> or <code>dinner</code>, but not both.</p>
<p>In the <a href="TheDefinePlugin.html">next chapter</a> we'll expand on the use of the define plugin
and show how it can handle asyncronous derived data like AJAX requests.</p>
<h2>Iterating though a Map</h2>
<p>If you want to iterate through the properties on a Map, use <code>each</code>:</p>
<pre><code>var pagination = new can.Map({page: 10, perPage: 25, count: 1388});
pagination.each(function(val, key) {
console.log(key + ': ' + val);
});
// The console shows:
// page: 10
// perPage: 25
// count: 1388
</code></pre>
<h2>Observable Arrays</h2>
<p>CanJS also provides observable arrays with <code>can.List</code>.
<code>can.List</code> inherits from <code>can.Map</code>. A <code>can.List</code> works much the same way a
<code>can.Map</code> does, with the addition of methods useful for working with
arrays:</p>
<ul>
<li><a href="../docs/can.List.prototype.indexOf.html"><code>indexOf</code></a>, which looks for an item in a
List.</li>
<li><a href="../docs/can.List.prototype.pop.html"><code>pop</code></a>, which removes the last item from a
List.</li>
<li><a href="../docs/can.List.prototype.push.html"><code>push</code></a>, which adds an item to the end of a
List.</li>
<li><a href="../docs/can.List.prototype.shift.html"><code>shift</code></a>, which removes the first item from
a List.</li>
<li><a href="../docs/can.List.prototype.unshift.html"><code>unshift</code></a>, which adds an item to the front
of a List.</li>
<li><a href="../docs/can.List.prototype.splice.html"><code>splice</code></a>, which removes and inserts items
anywhere in a List.</li>
</ul>
<p>When these methods are used to modify a List events are
emitted that you can listen for, as well. See <a href="../docs/can.List.html">the API for Lists</a> for more
information.</p>
<p><span class="pull-left"><a href="ApplicationFoundations.html">‹ Application Foundations</a></span>
<span class="pull-right"><a href="TheDefinePlugin.html">The Define Plugin ›</a></span></p>
</div>
</section>
<script type="text/javascript">
var docObject = {"src":{"path":"docs/can-guides/experiment/pmo/observables.md"},"description":"\n","name":"guides/pmo/Observables","title":"Observables","type":"page","parent":"guides/pmo","order":3,"disabletableofcontents":true,"comment":" ","pathToRoot":"../../.."};
</script>
</article>
<footer><p>CanJS is part of <a href="http://donejs.com" target="_blank">DoneJS</a>. Created and maintained by the core <a href="https://donejs.com/About.html#section=section_Team" target="_blank">DoneJS team</a> and <a href="http://bitovi.com" target="_blank">Bitovi</a>. <strong>Currently 3.0.0.</strong></p></footer>
</div>
</div>
</div>
<script>
steal = {
instantiated: {
"bundles/bit-docs-site/static.css!$css" : null
}
};
</script>
<script type='text/javascript' data-main="bit-docs-site/static" src="../../static/node_modules/steal/steal.production.js"></script>
<script async defer src="https://buttons.github.io/buttons.js"></script>
</body>
</html>