Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Restoring deleted demo files. Fixes #1294
- Loading branch information
1 parent
38b3566
commit 7769776
Showing
2 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<style> | ||
.selected { | ||
font-weight: bold; | ||
color: Gray; | ||
} | ||
</style> | ||
<div id='out'></div> | ||
<script src="../../node_modules/steal/steal.js" main="@empty"></script> | ||
<script type='text/mustache' id='template'> | ||
<button can-click='back'>Back</button> | ||
<button can-click='fwd'>Forward</button> | ||
<div class='url'>URL: {{appUrl}}</div> | ||
<h2>Locations</h2> | ||
{{#appState.locations}} | ||
<div><input type='checkbox' can-value='{selected}' />{{name}}</div> | ||
{{/appState.locations}} | ||
<h2>Products</h2> | ||
{{#appState.products}} | ||
<div><a href="#!product/{{id}}" class='{{selectedClass id}}'>{{name}}</a></div> | ||
{{/appState.products}} | ||
</script> | ||
<script> | ||
steal("can/route", "can/map/define", "can/view/mustache", "can/view/bindings", "can/model", "can/util/fixture",function(){ | ||
|
||
can.fixture("GET /locations", function(){ | ||
return [ | ||
{id: 1, name: "Chicago"}, | ||
{id: 2, name: "New York"}, | ||
{id: 3, name: "LA"} | ||
] | ||
}) | ||
|
||
can.fixture("GET /products", function(){ | ||
return [ | ||
{id: 1, name: "Shampoo"}, | ||
{id: 2, name: "Conditioner"}, | ||
{id: 3, name: "Soap"} | ||
] | ||
}) | ||
|
||
var Locations = can.Model.extend({ | ||
findAll: "GET /locations" | ||
}, {}) | ||
var Products = can.Model.extend({ | ||
findAll: "GET /products" | ||
}, {}) | ||
|
||
var AppState = can.Map.extend({ | ||
define: { | ||
products: { | ||
// don't serialize this property at all in the route | ||
serialize: false, | ||
value: [] | ||
}, | ||
productId: { | ||
type: "number" | ||
}, | ||
locations: { | ||
// don't serialize this property at all in the route | ||
serialize: false, | ||
value: [] | ||
}, | ||
// virtual property that contains a comma separated list of ids based on locations that are selected | ||
locationIds: { | ||
|
||
// comma separated list of ids | ||
serialize: function(){ | ||
var selected = this.attr('locations').filter(function(location){ | ||
return location.attr('selected'); | ||
}); | ||
if(!selected.length) return; | ||
var ids = []; | ||
selected.each(function(item){ | ||
ids.push(item.attr('id')); | ||
}) | ||
return ids.join(','); | ||
}, | ||
remove: function(){ | ||
// for each id, toggle any matched location | ||
this.attr('locations').each(function(location){ | ||
location.attr('selected', false) | ||
}) | ||
}, | ||
|
||
// toggle selected from a comma separated list of ids | ||
set: function(val){ | ||
var arr = val; | ||
if(typeof val === "string"){ | ||
arr = val.split(',') | ||
} | ||
for (var i = 0; i < arr.length; i++) { | ||
arr[i] = +arr[i]; | ||
}; | ||
// for each id, toggle any matched location | ||
this.attr('locations').each(function(location){ | ||
if(arr.indexOf(location.attr('id')) !== -1){ | ||
location.attr('selected', true); | ||
} else { | ||
location.attr('selected', false) | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
}); | ||
|
||
// initialize and call map first, so anything binding to can.route will work correctly | ||
appState = new AppState(); | ||
can.route.map(appState); | ||
|
||
// when the data is ready, set the locations property | ||
appState.attr('locations', new Locations.List({})) | ||
appState.attr('products', new Products.List({})) | ||
|
||
can.route("product/:productId") | ||
|
||
// call ready after the appState is fully initialized | ||
can.route.ready(); | ||
|
||
var appUrl = can.compute(window.location.href, { | ||
get: function(){ | ||
return window.location.href | ||
}, | ||
on: function(cb){ | ||
can.route.bind('change', function(){ | ||
setTimeout(function(){ | ||
cb(); | ||
}, 20) | ||
}) | ||
|
||
} | ||
}) | ||
|
||
$("#out").html( can.view('template', { | ||
appState: appState, | ||
appUrl: appUrl, | ||
back: function(){ | ||
window.history.back(); | ||
}, | ||
fwd: function(){ | ||
window.history.forward(); | ||
} | ||
}, { | ||
selectedClass: function(id){ | ||
if(appState.attr('productId') === id()){ | ||
return 'selected'; | ||
} | ||
} | ||
}) ); | ||
|
||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
@function can.route.map map | ||
@parent can.route.static | ||
|
||
Assign a can.Map instance that acts as can.route's internal can.Map. The purpose for this is to cross-bind a top level state object (Application State) to the can.route. | ||
|
||
@signature `can.route.map(MapConstructor)` | ||
|
||
@param {can.Map} MapConstructor A can.Map constructor function. A new can.Map instance will be created and used as the can.Map internal to can.route. | ||
|
||
@signature `can.route.map(mapInstance)` | ||
|
||
@param {can.Map} mapInstance A can.Map instance, used as the can.Map internal to can.route. | ||
|
||
@body | ||
|
||
## Background | ||
|
||
One of the biggest challenges in a complex application is getting all the different parts of the app to talk to each other simply, cleanly, and reliably. | ||
|
||
An elegant way to solve this problem is using the [Observer Pattern](http://en.wikipedia.org/wiki/Observer_pattern). A single object, which can be called [Application State](https://www.youtube.com/watch?v=LrzK4exG5Ss), holds the high level state of the application. | ||
|
||
## Use | ||
|
||
`can.route.map` provides an easy to way make your Application State object cross-bound to `can.route`, using an internal can.Map instance, which is serialized into the hash (or pushstate URLs). | ||
|
||
var appState = new can.Map({ | ||
petType: "dog", | ||
storeId: 2 | ||
}); | ||
|
||
can.route.map(appState); | ||
|
||
## When to call it | ||
|
||
Call `can.route.map` at the start of the application lifecycle, before any calls to `can.route.bind`. This is because `can.route.map` creates a new internal `can.Map`, replacing the default `can.Map` instance, so binding has to occur on this new object. | ||
|
||
var appState = new can.Map({ | ||
graphType: "line", | ||
currencyType: "USD" | ||
}); | ||
|
||
can.route.map(appState); | ||
|
||
## Demo | ||
|
||
The following shows creating an appState that loads data at page load, has a virtual property 'locationIds' which serializes an array, and synchronizes the appState to can.route: | ||
|
||
@demo can/route/docs/map.html | ||
|
||
## Using arrays and can.Lists | ||
|
||
If the Application State contains a property which is any non-primitive type, its useful to use the [can.Map.prototype.define define] plugin to define how that property will serialize. `can.route` calls [can.Map.prototype.serialize serialize] internally to turn the Application State object into URL params. | ||
|
||
The following example shows a flags property, which is an array of string-based flags: | ||
|
||
var AppState = can.Map.extend({ | ||
define: { | ||
flags: { | ||
// return a string friendly format | ||
serialize: function(){ | ||
return this.attr('flags').join(','); | ||
}, | ||
// convert a stringified object into an array | ||
set: function(val){ | ||
if(val === ""){ | ||
return []; | ||
} | ||
var arr = val; | ||
if(typeof val === "string"){ | ||
arr = val.split(',') | ||
} | ||
return arr; | ||
} | ||
} | ||
}); | ||
|
||
var appState = new AppState({ | ||
flags: [] | ||
}); | ||
|
||
can.route.map(appState); | ||
|
||
## Complete example | ||
|
||
The following example shows loading some metadata on page load, which must be loaded as part of the Application State before the components can be initialized | ||
|
||
It also shows an example of a "virtual" property on the AppState, locationIds, which is the serialized version of a non-serializeable can.List, locations. A setter is defined on locationIds, which will translate changes in locationIds back to the locations can.List. | ||
|
||
var AppState = can.Map.extend({ | ||
define: { | ||
locations: { | ||
// don't serialize this property at all in the route | ||
serialize: false | ||
}, | ||
// virtual property that contains a comma separated list of ids | ||
// based on locations that are selected | ||
locationIds: { | ||
|
||
// comma separated list of ids | ||
serialize: function(){ | ||
var selected = this.attr('locations').filter( | ||
function(location){ | ||
return location.attr('selected'); | ||
}); | ||
var ids = []; | ||
selected.each(function(item){ | ||
ids.push(item.attr('id')); | ||
}) | ||
return selected.join(','); | ||
}, | ||
// toggle selected from a comma separated list of ids | ||
set: function(val){ | ||
var arr = val; | ||
if(typeof val === "string"){ | ||
arr = val.split(',') | ||
} | ||
// for each id, toggle any matched location | ||
this.attr('locations').each(function(location){ | ||
if(arr.indexOf(location.attr('id')) !== -1){ | ||
location.attr('selected', true); | ||
} else { | ||
location.attr('selected', false) | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
}); | ||
|
||
// initialize and call map first, so anything binding to can.route | ||
// will work correctly | ||
var appState = new AppState(); | ||
can.route.map(appState); | ||
|
||
// GET /locations | ||
var locations = new Location.List({}); | ||
|
||
// when the data is ready, set the locations property | ||
locations.done(function(){ | ||
var appState.attr('locations', locations) | ||
|
||
// call ready after the appState is fully initialized | ||
can.route.ready(); | ||
}) | ||
|
||
## Why | ||
|
||
The Application State object, which is cross-bound to the can.route via `can.route.map` and represents the overall state of the application, has several obvious uses: | ||
|
||
* It is passed into the various components and used to communicate their own internal state. | ||
* It provides deep linking and back button support. As the URL changes, Application State changes cause changes in application components. | ||
* It provides the ability to "save" the current state of the page, by serializing the Application State object and saving it on the backend, then restoring with that object to load this saved state. |