As mentioned in the introduction, CanJS suggests using a global
appState object to manage the state of your application. The appState object
is bound to two things:
The application’s base template
The application’s routing
Since you already know about creating instances of can.Map, creating an
appState object, which is a can.Map, will be easy. Let’s see how this works.
Open up your app.js file and update it as shown below.
$(function () {
var AppState = can.Map.extend({});
var appState = new AppState();
// Bind the application state to the root of the application
$('#can-main').html(can.view('main.stache', appState));
// Set up the routes
can.route(':page', { page: 'home' });
can.route(':page/:slug', { slug: null });
can.route(':page/:slug/:action', { slug: null, action: null });
$('body').on('click', 'a[href="javascript://"]', function(ev) {
ev.preventDefault();
});
// Bind the application state to the can.route
can.route.map(appState);
can.route.ready();
//appState.attr('page', 'restaurants');
appState.bind('change', function(ev, prop, change, newVal, oldVal) {
alert('Changed the “' + prop + '” property from “' + oldVal + '” to “' + newVal + '”.');
});
});
Routing
Before we dive into the details of the appState object, let’s quickly discuss
routing. Routing in CanJS allows us to manage browser history and client state by
synchronizing the window.location.hash with a can.Map. In other words, we can
use routing to reflect the state of our application or set the state of our application.
One of the things that makes routing powerful is that it records the state of the
application in the browser’s history. We’ll see some specific examples of this
as we proceed.
In our application, we setup routing by:
defining the possible routes by calling can.route,
binding our appState object to the route with a call to can.route.map, and
calling can.route.ready(), which sets up two-way binding between the
browser’s window.location.hash and the can.route’s internal can.Map.
On lines 10–12, we define all the potential routes in our application and the
properties on the appState object. Let’s look at each line individually.
can.route(':page', { page: 'home' });
This line does two things:
Creates a base route that is bound to one property: page.
Sets the default value of the page property to 'home'.
In our app, this will allow the following URLs:
#! (which will set page to 'home' because that’s the default)
#!orders/ (which will set page to 'orders')
#!restaurants/ (which will set page to 'restaurants')
can.route(':page/:slug', { slug: null });
This line does two things:
Binds a new slug property to our appState object.
Sets the default value of the slug property to null.
This makes the following URLs possible:
#!restaurants/spago/ (page will be 'restaurants' and slug will be 'spago')
Anything in the second part of the URL will be the slug property on our
appState object.
Binds a new action property to our appState object.
Sets the default value of the action property to null.
This makes the following URLs possible:
#!restaurants/spago/order/ for order confirmation; again, action will be 'order'
Let’s take a moment to see how these routes are bound to our appState object.
Notice the //appState.attr('page', 'restaurants'); line at the end of our
app.js file; let’s uncomment that line so it looks like
appState.attr('page', 'restaurants');
Now, refresh the app in your browser. The path will now be #!restaurants,
and you’ll notice that the Restaurants link in the navigation is highlighted.
Note that, after we initialized our routes, updating the value of our
appState’s page property caused the route to update as well.
The value of the page property was serialized and appended
to the window.location.hash.
Let’s see what happens if we adjust the value of the hash. To monitor this
change, we’ve included the following lines:
appState.bind('change', function(ev, prop, change, newVal, oldVal) {
alert('Changed the “' + prop + '” property from “' + oldVal + '” to “' + newVal + '”.');
});
These lines use can.Map.bind to
watch for changes to the appState object. Go ahead and change the URL from
#!restaurants to #!orders. You should see an alert with this message:
It was mentioned earlier that we bound our AppState to the application’s main.stache.
This is the key to connecting the AppState to our components.
Because the appState object is bound to our main template, which includes the rest of
the components in the app, these attributes will automatically be included in the scope of
the components.
Before moving on, let’s remove the following lines from our application:
appState.attr('page', 'restaurants');
appState.bind('change', function(ev, prop, change, newVal, oldVal) {
alert('Changed the “' + prop + '” property from “' + oldVal + '” to “' + newVal + '”.');
});
In this Chapter
Get the code for: chapter: app state and routing
As mentioned in the introduction, CanJS suggests using a global
appState
object to manage the state of your application. TheappState
object is bound to two things:Since you already know about creating instances of
can.Map
, creating anappState
object, which is acan.Map
, will be easy. Let’s see how this works. Open up yourapp.js
file and update it as shown below.Routing
Before we dive into the details of the
appState
object, let’s quickly discuss routing. Routing in CanJS allows us to manage browser history and client state by synchronizing thewindow.location.hash
with acan.Map
. In other words, we can use routing to reflect the state of our application or set the state of our application. One of the things that makes routing powerful is that it records the state of the application in the browser’s history. We’ll see some specific examples of this as we proceed.In our application, we setup routing by:
can.route
,appState
object to the route with a call tocan.route.map
, andcan.route.ready()
, which sets up two-way binding between the browser’swindow.location.hash
and thecan.route
’s internalcan.Map
.On lines 10–12, we define all the potential routes in our application and the properties on the
appState
object. Let’s look at each line individually.This line does two things:
page
.page
property to'home'
.In our app, this will allow the following URLs:
#!
(which will setpage
to'home'
because that’s the default)#!orders/
(which will setpage
to'orders'
)#!restaurants/
(which will setpage
to'restaurants'
)This line does two things:
slug
property to ourappState
object.slug
property tonull
.This makes the following URLs possible:
#!restaurants/spago/
(page
will be'restaurants'
andslug
will be'spago'
)Anything in the second part of the URL will be the
slug
property on ourappState
object.This line does two things:
action
property to ourappState
object.action
property tonull
.This makes the following URLs possible:
#!restaurants/spago/order/
for order confirmation; again,action
will be'order'
Let’s take a moment to see how these routes are bound to our
appState
object. Notice the//appState.attr('page', 'restaurants');
line at the end of ourapp.js
file; let’s uncomment that line so it looks likeappState.attr('page', 'restaurants');
Now, refresh the app in your browser. The path will now be
#!restaurants
, and you’ll notice that the Restaurants link in the navigation is highlighted.Note that, after we initialized our routes, updating the value of our
appState
’spage
property caused the route to update as well. The value of thepage
property was serialized and appended to thewindow.location.hash
.Let’s see what happens if we adjust the value of the hash. To monitor this change, we’ve included the following lines:
These lines use
can.Map.bind
to watch for changes to theappState
object. Go ahead and change the URL from#!restaurants
to#!orders
. You should see an alert with this message:It was mentioned earlier that we bound our AppState to the application’s
main.stache
. This is the key to connecting the AppState to our components. Because theappState
object is bound to our main template, which includes the rest of the components in the app, these attributes will automatically be included in the scope of the components.Before moving on, let’s remove the following lines from our application:
‹ Stache Templates Components ›