can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
720 lines (563 loc) • 22.9 kB
HTML
<!--####################################################################
THIS IS A GENERATED FILE -- ANY CHANGES MADE WILL BE OVERWRITTEN
INSTEAD CHANGE:
source: [object Object]
@page guides/pmo/DataModelsAndFixtures
######################################################################## -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>CanJS - Data Models and Fixtures</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="
">
<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="current
parent
expanded">
<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="DataModelsAndFixtures.html">Data Models and Fixtures</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>Data Models and Fixtures</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/data-models-and-fixtures.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.Model</code></li>
<li><code>can.fixture</code></li>
<li>Connecting <code>can.Model</code> with <code>can.Component</code></li>
</ul>
<p>Get the code for: <a href="/guides/examples/PlaceMyOrder/ch-6_canjs-getting-started.zip">chapter: data models and fixtures</a></p>
<hr />
<p>The next item we’re going to go over is <a href="../docs/can.Model.html">can.Model</a>.
Models make interacting with JSON REST services <em>really easy</em>. They do this by
encapsulating most of the code required to connect to a service and managing
the data the service returns. Additionally, <code>can.Model</code> extends
<a href="../docs/can.Map.html">can.Map</a>, meaning that the objects returned have all of
the features of a <code>can.Map</code>, such as being <a href="Observables.html">observable</a>.</p>
<p>For applications requiring real-time, high performance, restful data connections
you should check out <a href="http://connect.canjs.com/">can-connect</a>. For our simple case,
we’ll use <code>can.Model</code> to provide data for our state and city elements from
the last chapter.</p>
<p>First, let’s open the <code>models/state.js</code> file and add the following code:</p>
<pre><code>var State = can.Model.extend({
findAll: 'GET /api/states'
}, {
// Include second, empty parameter object to set instanceProperties
});
</code></pre>
<p>Then add the following to <code>models/city.js</code>:</p>
<pre><code>var City = can.Model.extend({
findAll: 'GET /api/cities'
}, {
// Include second, empty parameter object to set instanceProperties
});
</code></pre>
<p>Because it is a <a href="../docs/can.Construct.html">can.Construct</a>, <code>can.Model.extend</code>
can take up to three parameters:</p>
<ol>
<li><code>name</code></li>
<li><code>staticProperties</code></li>
<li><code>instanceProperties</code></li>
</ol>
<p>A <code>can.Model</code>’s <code>staticProperties</code> parameter has several reserved properties you
can add that simplify accessing data from a JSON REST service. These
properties are:</p>
<ol>
<li><code>findAll</code></li>
<li><code>findOne</code></li>
<li><code>create</code></li>
<li><code>update</code></li>
<li><code>destroy</code></li>
</ol>
<p>The <code>find*</code>, <code>create</code>, <code>update</code>, and <code>destroy</code> functions are available directly
off of the object definition (i.e., they are static). The <code>destroy</code> function is
available off of specific instances of a <code>can.Model</code>. We’ll see how to
use these below.</p>
<p><strong>Reminder</strong>: The number of parameters you pass in to an <code>extend</code> function is
important. If you pass in a single parameter object, the <code>extend</code> function will
use that to set the <code>instanceProperties</code>. If you pass in two parameter
objects, the <em>first</em> object passed in will be used to set the
<code>staticProperties</code>. The second parameter will be used to set the
<code>instanceProperties</code>. Here, we only want to set the <code>staticProperties</code>, so we
must pass in a second, empty object.</p>
<p>A few examples below illustrate this important point:</p>
<pre><code>var MyModel = can.Model.extend({
findAll: function () {
// Static function
}
}, {
destroy: function () {
// Instance function
}
});
MyModel.findAll(); // Reference a function defined on the constructor
var modelInstance = new MyModel();
modelInstance.destroy(); // Reference a function defined on the prototype
</code></pre>
<h2>The Data for Our Model</h2>
<p>We’re not going to connect to a server to retrieve our data; however, we’re
going to code our model as if we were. How can this possibly work? CanJS
provides a handy utility, <code>can.fixture</code>, that we can use to easily mimic the
functionality of connecting to a server. <code>can.fixture</code>
intercepts an AJAX request and simulates a server response with a file or a
function. You can use <code>can.fixture</code> to develop JavaScript independently of
backend services.</p>
<p><code>can.fixture</code> is not included with the base CanJS package. It’s a good practice
to keep it separate from your production CanJS library, which is why we
downloaded it as a separate file, and then loaded it into index.html via a separate
script tag, rather than including it with our custom download.
<em>If you use <code>can.fixture</code> during development, remember
to remove it once you need to connect to your REST services</em>.</p>
<p>Let’s create a fixture that will respond to our requests for the list of states.
Add the following code to the <code>models/fixtures.js</code> file:</p>
<pre><code>can.fixture('GET /api/states', 'models/states.json');
</code></pre>
<p>The first argument to <code>can.fixture</code>, <code>GET /api/states</code>, tells CanJS to
intercept any <code>GET</code> requests to the resource <code>/api/states</code>. The second argument
is a path to a file with the data the fixture will return. Because we’re simulating
a <code>findAll</code> function, we need to return an array. The <code>findAll</code> function
expects an array. By default, if it does not receive one, it will throw an error.
If you need to connect to services that return data that doesn’t match the expected
return type of the <code>find*</code> functions, don’t fret. There are ways to manage this,
which we’ll work with later on.</p>
<p>Let’s also create a fixture that will respond to our requests for the list
of cities for each state. This one is going to be a little different because
we want to be able to return a different list depending on which state is
included in the request. Thankfully, <code>can.fixture</code> is flexible and allows
you to dynamically respond to requests. Let’s add the following code to the
<code>models/fixtures.js</code> file:</p>
<pre><code>can.fixture('GET /api/cities', function(request, response) {
can.ajax({
url: 'models/' + request.data.state + '.json',
success: function(data) {
response(data);
}
});
});
</code></pre>
<p>The first argument to <code>can.fixture</code>, <code>GET /api/cities</code>, is similar to our
restaurants example: we’re setting up this fixture to intercept any <code>GET</code>
requests to <code>/api/cities</code>. The second argument, however, is different: it
is a function that returns the data we want to get when the application makes
a service call. In our example, we’re making an AJAX request (via
<a href="../docs/can.ajax.html">can.ajax</a>) to get the fixture data from a JSON file,
then responding to the request with the data we fetched.</p>
<h2>Connecting the Model to the Component</h2>
<p>It’s time to connect all of this together in our view model. Simply open up
<code>components/restaurant_list/restaurant_list.js</code>, find the states property:</p>
<pre><code> states: {
value: [
{
cities: ['Green Bay', 'Milwaukee'],
name: 'Wisconsin'
},
{
cities: ['Detroit', 'Ann Arbor'],
name: 'Michigan'
},
{
cities: ['Chicago', 'Peoria'],
name: 'Illinois'
}
]
},
</code></pre>
<p>and replace it with this:</p>
<pre><code> states: {
get: function() {
return State.findAll({});
}
},
</code></pre>
<p>In the same file, find the cities property:</p>
<pre><code> cities: {
get: function() {
var state = this.attr('state');
return state && this.attr('citiesByState')[state];
}
},
</code></pre>
<p>and replace it with this:</p>
<pre><code> cities: {
get: function() {
var state = this.attr('state');
return state ? City.findAll({ state: state }) : null;
}
},
</code></pre>
<p>You can remove the <code>citiesByState</code> property since we won’t be using it anymore.
Let’s also update the <code>components/restaurant_list/restaurant_list.stache</code> file
to match the changes we made in the view model. The most significant change is
that our <code>cities</code> and <code>states</code> properties now return a <a href="../docs/can.List.plugins.promise.html">promise</a>
instead of just an array. Find the <code>form</code> element:</p>
<pre><code> <form class="form">
<div class="form-group">
<label>State</label>
<select {($value)}="state">
{{^if state}}
<option value="">Choose a state</option>
{{/if}}
{{#each states}}
<option value="{{name}}">{{name}}</option>
{{/each}}
</select>
</div>
<div class="form-group">
<label>City</label>
<select {($value)}="city">
{{^if city}}
<option value="">Choose a city</option>
{{/if}}
{{#each cities}}
<option>{{.}}</option>
{{/each}}
</select>
</div>
</form>
</code></pre>
<p>and replace it with this new one:</p>
<pre><code> <form class="form">
<div class="form-group">
<label>State</label>
<select {($value)}="state" {{#if states.isPending}}disabled{{/if}}>
{{#if states.isPending}}
<option value="">Loading...</option>
{{else}}
{{^if state}}
<option value="">Choose a state</option>
{{/if}}
{{#each states.value}}
<option value="{{short}}">{{name}}</option>
{{/each}}
{{/if}}
</select>
</div>
<div class="form-group">
<label>City</label>
<select {($value)}="city" {{^if state}}disabled{{/if}}>
{{#if cities.isPending}}
<option value="">Loading...</option>
{{else}}
{{^if city}}
<option value="">Choose a city</option>
{{/if}}
{{#each cities.value}}
<option>{{name}}</option>
{{/each}}
{{/if}}
</select>
</div>
</form>
</code></pre>
<p>Note that there are a few ways to call a <code>findAll</code> function on a <code>can.Model</code>. The
first way is to call the function explicitly. Using the <code>State</code> model as an
example, that would look like this:</p>
<pre><code>State.findAll({ /* paramsObject */ },
function(returnedObject){
// ...
},
function(errorObject){
// ...
});
</code></pre>
<p>We also have the ability to use <code>can.Deferred</code>, which allows us to chain
callback functions off of each other. You can read more about this from the
<a href="https://api.jquery.com/category/deferred-object/">jQuery API</a>. Using this
method, we could write our <code>findAll</code> like this:</p>
<pre><code>State.findAll({ /* paramsObject */ })
/* When the API call succeeds, .done() is called */
.done(function(returnedObject) {
// ...
})
/* When the API call errors, .fails() is called */
.fail(function(errorObject) {
// ...
});
</code></pre>
<p>Both are acceptable, but throughout the guide we will use the Deferred method
as it more explicitly states which callback function is which.</p>
<p>Finally, let’s add the scripts we modified to our <code>index.html</code> file.
Find these lines:</p>
<pre><code> <!-- Replace with city model script -->
<!-- Replace with fixtures script -->
<!-- Replace with state model script -->
</code></pre>
<p>and replace it with these lines:</p>
<pre><code> <script src="models/city.js"></script>
<script src="models/fixtures.js"></script>
<script src="models/state.js"></script>
</code></pre>
<p>Let’s go back to our app now and see what happens! If everything went
according to plan, you should be able to refresh the Restaurants page
and see the same list that we had before. Selecting a state, then a city,
should work the same as well.</p>
<p><img src="../can/guides/images/view-models/view_model_city.png" alt="set up state selector" /></p>
<hr />
<p><span class="pull-left"><a href="ViewModels.html">‹ View Models</a></span>
<span class="pull-right"><a href="LoadingStates.html">Loading States ›</a></span></p>
</div>
</section>
<script type="text/javascript">
var docObject = {"src":{"path":"docs/can-guides/experiment/pmo/data-models-and-fixtures.md"},"description":"\n","name":"guides/pmo/DataModelsAndFixtures","title":"Data Models and Fixtures","type":"page","parent":"guides/pmo","order":9,"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>