can-route
Advanced tools
Comparing version 4.4.3 to 4.4.4
@module {Object} can-route can-route | ||
@group can-route.static static | ||
@group can-route.static 0 static | ||
@group deprecated 1 deprecated | ||
@download can/route | ||
@@ -10,3 +11,3 @@ @test can-route/test.html | ||
@description Manage browser history and client state by synchronizing the `window.location.hash` with an observable. | ||
@description Manage browser history and client state by synchronizing the `window.location.hash` with an observable. See the [guides/routing Routing] for in depth examples. | ||
@@ -20,20 +21,20 @@ @type {Object} | ||
```js | ||
{ | ||
data, // The bound key-value observable. | ||
urlData, // The observable that represents the | ||
// hash. Defaults to RouteHash. | ||
register, // Register routes that translate between | ||
// the url and the bound observable. | ||
start, // Begin updating the bound observable with | ||
// url data and vice versa. | ||
deparam, // Given url fragment, return the data for it. | ||
rule, // Given url fragment, return the routing rule | ||
param, // Given data, return a url fragment. | ||
url, // Given data, return a url for it. | ||
link, // Given data, return an <a> tag for it. | ||
isCurrent, // Given data, return true if the current url matches | ||
// the data. | ||
currentRule // Return the matched rule name. | ||
} | ||
``` | ||
{ | ||
data, // The bound key-value observable. | ||
urlData, // The observable that represents the | ||
// hash. Defaults to RouteHash. | ||
register, // Register routes that translate between | ||
// the url and the bound observable. | ||
start, // Begin updating the bound observable with | ||
// url data and vice versa. | ||
deparam, // Given url fragment, return the data for it. | ||
rule, // Given url fragment, return the routing rule | ||
param, // Given data, return a url fragment. | ||
url, // Given data, return a url for it. | ||
link, // Given data, return an <a> tag for it. | ||
isCurrent, // Given data, return true if the current url matches | ||
// the data. | ||
currentRule // Return the matched rule name. | ||
} | ||
``` | ||
@@ -54,3 +55,3 @@ @body | ||
create history enabled single-page apps. However, | ||
`route` addresses several other needs aswell, such as: | ||
`route` addresses several other needs as well, such as: | ||
@@ -80,57 +81,68 @@ - Pretty urls. | ||
Typically, the map is the view-model of the top-level [can-component] in your | ||
application. For example, the following defines `<my-app>`, and uses the view-model | ||
of a `<my-app>` element already in the page as the `route.data`: | ||
`can-route` is an observable. Once initialized using [can-route.start `route.start()`], it is going to change, you can respond to those changes. The following example has the my-app component's `routeData` property return `route.data`. It responds to changes in routing in `componentToShow`. | ||
```js | ||
import Component from "can-component"; | ||
import route from "can-route"; | ||
import "can-stache-route-helpers"; | ||
```html | ||
<my-app> | ||
<mock-url> | ||
<script type="module"> | ||
import {DefineMap, Component, route} from "can"; | ||
import "//unpkg.com/mock-url@^5"; | ||
const PageHome = Component.extend({ | ||
tag: "page-home", | ||
view: `<h1>Home page</h1> | ||
<a href="{{ routeUrl(page='other') }}"> | ||
Go to another page | ||
</a>`, | ||
ViewModel: {}, | ||
}); | ||
const PageOther = Component.extend({ | ||
tag: "page-other", | ||
view: `<h1>Other page</h1> | ||
<a href="{{ routeUrl(page='home') }}"> | ||
Go home | ||
</a>`, | ||
ViewModel: {}, | ||
}); | ||
Component.extend({ | ||
tag: "my-app", | ||
ViewModel: { | ||
page: "string" | ||
tag: "my-app", | ||
ViewModel: { | ||
routeData: { | ||
default() { | ||
route.data = new DefineMap(); | ||
route.register("{page}", { page: "home" }); | ||
route.start(); | ||
return route.data; | ||
} | ||
}, | ||
view: ` | ||
{{#switch(page)}} | ||
{{#case("home")}} | ||
<h1>Home Page</h1> | ||
<a href="{{#routeUrl(page='products')}}">Products</a> | ||
{{/case}} | ||
{{#case("products")}} | ||
<h1>Products</h1> | ||
<a href="{{#routeUrl(page='home')}}">Home</a> | ||
{{/case}} | ||
{{#default()}} | ||
<h1>Page Not Found</h1> | ||
<a href="{{#routeUrl(page='home')}}">Home</a> | ||
{{/default}} | ||
{{/switch}} | ||
` | ||
get componentToShow() { | ||
switch(this.routeData.page) { | ||
case "home": | ||
return new PageHome(); | ||
case "other": | ||
return new PageOther(); | ||
} | ||
}, | ||
page: "string" | ||
}, | ||
view: `{{componentToShow}}` | ||
}); | ||
route.data = document.querySelector( "my-app" ); | ||
route.register( "{page}" ); | ||
route.start(); | ||
</script> | ||
``` | ||
@codepen | ||
> __Note__: The `route.data = document.querySelector("my-app")` statement is what | ||
> sets `route.data` to `<my-app>`'s view-model. | ||
`route.data` defaults to [can-define/map/map], but `route.data` can be set to any observable. The following uses [can-observe]: | ||
An observable can be set as `route.data` directly. The following sets `route.data` | ||
to an `AppViewModel` instance: | ||
```js | ||
import DefineMap from "can-define/map/map"; | ||
import route from "can-route"; | ||
import {DefineMap, route, observe} from "can/everything"; | ||
const AppViewModel = DefineMap.extend( { | ||
page: "string" | ||
} ); | ||
const appState = new AppViewModel(); | ||
route.data = appState; | ||
route.data = new observe(); | ||
route.register( "{page}", { page: "home" } ); | ||
route.start(); | ||
console.log( route.data.page ) //-> "home" | ||
``` | ||
@codepen | ||
@@ -147,17 +159,18 @@ Understanding how maps work is essential to understanding `can-route`. | ||
```js | ||
const AppViewModel = DefineMap.extend( { | ||
page: "string" | ||
} ); | ||
route.data = new AppViewModel(); | ||
route.register( "{page}" ); | ||
import {DefineMap, route} from "can"; | ||
route.data = new DefineMap(); | ||
route.register( "{page}", {page: "recipes"} ); | ||
route.start(); | ||
``` | ||
You can listen to when the url changes from `"#!recipes"` to `"#!settings"` with: | ||
// You can listen when the url changes from `"#!recipes"` to `"#!settings"` with: | ||
```js | ||
route.data.on( "page", function( ev, newVal, oldVal ) { | ||
// page changed from "recipes" to "settings" | ||
route.data.on( "page", ( ev, newVal, oldVal ) => { | ||
console.log(oldVal); //-> "recipes" | ||
console.log(newVal); //-> "settings" | ||
} ); | ||
route.data.page = "settings"; | ||
``` | ||
@codepen | ||
@@ -181,4 +194,2 @@ ### Updating can-route | ||
If using [can-map] or [can-simple-map] to back your route, update `route.data` using `attr`. | ||
### Encoded `/` | ||
@@ -189,19 +200,17 @@ | ||
```js | ||
route.data.type = "image/bar"; | ||
// OR | ||
route.attr( "type", "image/bar" ); | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {DefineMap, route} from "can"; | ||
route.data = new DefineMap( {type: "image/bar"} ); // location.hash -> #!&type=image%2Fbar | ||
route.start(); | ||
</script> | ||
``` | ||
@codepen | ||
The URL will look like this: | ||
https://example.com/#!type=image%2Fbar | ||
The location hash will look like this: | ||
#!type=image%2Fbar | ||
## Creating a route | ||
Use `route.register(url, defaults)` to create a | ||
Use [`route.register(url, defaults)`](can-route.register) to create a | ||
routing rule. A rule is a mapping from a url to | ||
@@ -212,15 +221,32 @@ an object (that is the route’s data). | ||
```js | ||
route.register( "#!content/{type}" ); | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
route.register( "content/{type}" ); | ||
route.data.type = "example"; // location.hash -> #!content/example | ||
route.start(); | ||
</script> | ||
``` | ||
@codepen | ||
If no routes are added, or no route is matched, | ||
can-route’s data is updated with the [can-route.deparam deparamed] | ||
can-route’s data is updated with the [can-route.deparam deparam]ed | ||
hash. | ||
```js | ||
location.hash = "#!type=videos"; | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
// route -> {type : "videos"} | ||
location.hash = "#!&type=videos"; | ||
route.start(); | ||
console.log(route.data); //-> {type : "videos"} | ||
</script> | ||
``` | ||
@codepen | ||
@@ -231,31 +257,56 @@ Once routes are added and the hash changes, | ||
```js | ||
route.register( "#!content/{type}" ); | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
route.register( "content/{type}" ); | ||
location.hash = "#!content/images"; | ||
route.start(); | ||
// route -> {type : "images"} | ||
route.data.type = "songs"; | ||
// location.hash -> "#!content/songs" | ||
console.log( route.data ) //-> {type : "images"} | ||
route.data.type = "songs"; // location.hash -> "#!content/songs" | ||
</script> | ||
``` | ||
@codepen | ||
Default values can be added to a route: | ||
Default values can be added to a route, this is the second argument passed into [can-route.register]: | ||
```js | ||
route.register( "content/{type}", { type: "videos" } ); | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
route.register( "content/{type}", {type: "videos"} ); | ||
location.hash = "#!content/"; | ||
// route -> {type : "videos"} | ||
route.start(); | ||
console.log( route.data ); //-> {type: "videos"} | ||
// location.hash -> "#!content/" | ||
</script> | ||
``` | ||
@codepen | ||
@highlight 6 | ||
Defaults can also be set on the root page of your app: | ||
Defaults can also be set on the root page of your app. An empty string (`""`) is treated as the "root" page of the app. If there is no hash, or if using [can-route-pushstate] someone is at `/`: | ||
```js | ||
route.register( "", { page: "index" } ); | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
route.register( "", {page: "index"} ); | ||
location.hash = "#!"; | ||
// route -> {page : "index"} | ||
route.start() | ||
console.log( route.data ); //-> {page : "index"} | ||
// location.hash -> "#!" | ||
</script> | ||
``` | ||
@codepen | ||
@@ -273,4 +324,4 @@ ## Initializing can-route | ||
Typically, you don’t set `location.hash` | ||
directly. Instead, you can change properties on can-route | ||
Typically, you don’t set `location.hash` directly. | ||
Instead, you can change properties on can-route | ||
like: | ||
@@ -285,13 +336,15 @@ | ||
Often, you want to create links. can-route provides | ||
the [can-route.link] and [can-route.url] helpers to make this | ||
Often, you want to create links. [can-stache-route-helpers] provides | ||
the [can-stache-route-helpers.routeUrl] helper to make this | ||
easy: | ||
```js | ||
route.link( "Videos", { type: "videos" } ); | ||
```html | ||
<a href="{{ routeUrl(type='videos') }}">Videos</a> | ||
``` | ||
As long as `route.data` is a [can-define/map/map] [can-define/map/map.prototype.assign route.data.assign( { } )] can be used to overwrite, but not delete properties and [can-define/map/map.prototype.update route.data.update( { } )] can be used to overwrite AND delete properties. | ||
## Finding the matched route | ||
The matched rule is stored in the compute `route.currentRule` and is used to set the `window.location.hash`. The process can-route uses to find the matched rule is: | ||
The matched rule available at [can-route.currentRule `route.currentRule`] and is used to set the `window.location.hash`. The process can-route uses to find the matched rule is: | ||
1. Find all routes with all of their map properties set | ||
@@ -301,2 +354,3 @@ 2. If multiple routes are matched, find the route with the highest number of set properties | ||
### Find all routes with all of their map properties set | ||
@@ -307,8 +361,16 @@ | ||
```js | ||
import {route} from "can"; | ||
route.register( "{page}/{section}" ); | ||
route.start(); | ||
route.data.page = "contact"; | ||
route.data.section = "email"; | ||
route.currentRule(); // "{page}/{section}" | ||
setTimeout(() => { | ||
const result = route.currentRule(); | ||
console.log( result ); //-> "{page}/{section}" | ||
}, 100); | ||
``` | ||
@codepen | ||
@@ -318,9 +380,18 @@ If a route contains default values, these map properties must also be set to match the default value in order for the route to be matched: | ||
```js | ||
import {route} from "can"; | ||
route.register( "{page}", { section: "email" } ); | ||
route.start(); | ||
route.data.page = "contact"; | ||
route.data.section = "email"; | ||
route.currentRule(); // "{page}" | ||
setTimeout(() => { | ||
const result = route.currentRule(); | ||
console.log( result ); //-> "{page}" | ||
}, 100); | ||
``` | ||
@codepen | ||
### Find the route with the highest number of set properties | ||
@@ -331,9 +402,17 @@ | ||
```js | ||
import {route} from "can"; | ||
route.register( "{page}" ); | ||
route.register( "{page}/{section}" ); | ||
route.start(); | ||
route.data.page = "two"; | ||
route.data.section = "a"; | ||
route.currentRule(); // "{page}/{section}" | ||
setTimeout(() => { | ||
const result = route.currentRule(); | ||
console.log( result ) //-> "{page}/{section}" | ||
}, 100); | ||
``` | ||
@codepen | ||
@@ -345,8 +424,16 @@ ### Find the route that was registered first | ||
```js | ||
import {route} from "can"; | ||
route.register( "", { page: "home" } ); | ||
route.register( "{section}" ); | ||
route.start(); | ||
route.data.page = "home"; | ||
route.data.section = "a"; | ||
route.currentRule(); // "" | ||
setTimeout(() => { | ||
const result = route.currentRule(); | ||
console.log(result); //-> "" | ||
}, 100); | ||
``` | ||
@codepen |
@@ -6,16 +6,24 @@ @function can-route.currentRule currentRule | ||
@signature `route.currentRule()` | ||
@return {String} The currently matched [can-route.register registered] routing rule. | ||
Use `route.currentRule()` to find the current route rule. | ||
@body | ||
```js | ||
import {route} from "can"; | ||
## Use | ||
route.register( "{type}" ); | ||
route.register( "{type}/{subtype}" ); | ||
route.start(); | ||
Use `route.currentRule()` to find the current route rule. | ||
route.data.type = "foo"; | ||
setTimeout(() => { | ||
console.log( route.currentRule() ); //-> "{type}" | ||
route.data.subtype = "bar"; | ||
}, 100); | ||
```js | ||
route.register( "{type}", { type: "foo" } ); | ||
route.register( "{type}/{subtype}" ); | ||
route.currentRule(); // "{type}" | ||
route.data.subtype = "foo"; | ||
route.currentRule(); // "{type}/{subtype}" | ||
``` | ||
setTimeout(() => { | ||
console.log( route.currentRule() ); //-> "{type}/{subtype}" | ||
}, 200); | ||
``` | ||
@codepen | ||
@return {String} The currently matched [can-route.register registered] routing rule. |
182
doc/data.md
@@ -8,165 +8,53 @@ @property {Object|HTMLElement} can-route.data data | ||
key-value pairs, once [can-route.start] is called, changes in `route.data`'s | ||
properties will update the hash and vice-versa. | ||
properties will update the hash and vice-versa. `route.data` defaults to a [can-define/map/map]. | ||
```js | ||
import DefineMap from "can-define/map/map"; | ||
import route from "can-route"; | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import {DefineMap, route} from "can"; | ||
import "//unpkg.com/mock-url@^5.0.0/mock-url.mjs"; | ||
route.data = new DefineMap( { page: "" } ); | ||
route.register( "{page}" ); | ||
route.start(); | ||
``` | ||
route.data = new DefineMap( {page: ""} ); | ||
route.register( "{page}" ); | ||
route.start(); | ||
location.hash = "#!example"; | ||
setTimeout(()=> { | ||
console.log( route.data ); //-> {page: "example"} | ||
}, 100); | ||
</script> | ||
``` | ||
@codepen | ||
@type {HTMLElement} If `route.data` is set to an element, its | ||
observable [can-view-model] will be used as the observable connected | ||
to the browser's hash. | ||
to the browser's hash. | ||
```js | ||
import Component from "can-component"; | ||
import route from "can-route"; | ||
<section class="warnings"> | ||
<div class="deprecated warning"> | ||
<h3>Deprecated</h3> | ||
<div class="signature-wrapper"> | ||
<p>Assigning an <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">HTMLElement</a> to <code>route.data</code> has been deprecated in favor of setting it to an observable. If you have any further questions please refer to the [guides/routing Routing] guide. | ||
</div> | ||
</div> | ||
</section> | ||
Component.extend( { | ||
tag: "my-app", | ||
autoMount: true, | ||
ViewModel: { /* ... */ }, | ||
view: { /* ... */ } | ||
} ); | ||
route.data = document.querySelector( "my-app" ); | ||
route.register( "{page}" ); | ||
route.start(); | ||
``` | ||
@body | ||
## Background | ||
For in-depth examples see the the [guides/routing Routing] guide. | ||
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 ViewModel](https://www.youtube.com/watch?v=LrzK4exG5Ss), holds the high level state of the application. | ||
## Use | ||
Setting `route.data` is an easy way to cross-bind your Application ViewModel object to `route`. This will serialize your Application ViewModel into the hash (or pushstate URLs). | ||
`route.data` defaults to [can-define/map/map], but `route.data` can be set to any observable. The following uses [can-observe]: | ||
```js | ||
const ViewModel = DefineMap.extend( { | ||
petType: "string", | ||
storeId: "number" | ||
} ); | ||
const viewModel = new ViewModel( { | ||
petType: "string", | ||
storeId: "number" | ||
} ); | ||
route.data = viewModel; | ||
``` | ||
import {DefineMap, route, observe} from "can/everything"; | ||
`route.data` can also be set to a constructor function. A new instance will be created and bound to: | ||
```js | ||
const ViewModel = DefineMap.extend( { | ||
page: { | ||
type: "string", | ||
set: function( page ) { | ||
if ( page === "user" ) { | ||
this.verifyLoggedIn(); | ||
} | ||
return page; | ||
} | ||
} | ||
} ); | ||
route.data = ViewModel; | ||
route.data = new observe(); | ||
route.register( "{page}", { page: "home" } ); | ||
route.start(); | ||
console.log( route.data.page ) //-> "home" | ||
``` | ||
## When to set it | ||
Set `route.data` at the start of the application lifecycle, before any calls to `route.addEventListener`. This will allow events to correctly bind on this new object. | ||
## Demo | ||
The following shows creating an Application ViewModel that loads data at page load, has a virtual property 'locationIds' which serializes an array, and synchronizes the viewModel to can-route: | ||
@demo demos/can-route/data.html | ||
## Complete example | ||
The following example shows loading some metadata on page load, which must be loaded as part of the Application ViewModel before the components can be initialized | ||
It also shows an example of a "virtual" property on the AppViewModel called locationIds, which is the serialized version of a non-serializeable can.List called locations. A setter is defined on locationIds, which will translate changes in locationIds back to the locations can.List. | ||
```js | ||
const Location = DefineMap.extend( { | ||
selected: "boolean", | ||
id: "any" | ||
} ); | ||
const LocationList = DefineList.extend( { | ||
"*": Location | ||
} ); | ||
const AppViewModel = DefineMap.extend( { | ||
locations: { | ||
type: "any", | ||
// 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() { | ||
const selected = thislocations.filter( | ||
function( location ) { | ||
return location.selected; | ||
} ); | ||
const ids = []; | ||
selected.each( function( item ) { | ||
ids.push( item.id ); | ||
} ); | ||
return selected.join( "," ); | ||
}, | ||
// toggle selected from a comma separated list of ids | ||
set: function( val ) { | ||
let arr = val; | ||
if ( typeof val === "string" ) { | ||
arr = val.split( "," ); | ||
} | ||
// for each id, toggle any matched location | ||
this.locations.forEach( function( location ) { | ||
if ( arr.indexOf( location.id ) !== -1 ) { | ||
location.selected = true; | ||
} else { | ||
location.selected = false; | ||
} | ||
} ); | ||
} | ||
} | ||
} ); | ||
// initialize and set route.data first, so anything binding to can-route | ||
// will work correctly | ||
const viewModel = new AppViewModel(); | ||
route.data = appViewModel; | ||
// GET /locations | ||
const locations = new Location.List( {} ); | ||
// when the data is ready, set the locations property | ||
locations.done( function() { | ||
viewModel.locations = locations; | ||
// call start after the AppViewModel is fully initialized | ||
route.start(); | ||
} ); | ||
``` | ||
## Why | ||
The Application ViewModel object, which is cross-bound to the can-route via `route.data` 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 ViewModel changes cause changes in application components. | ||
* It provides the ability to "save" the current state of the page, by serializing the Application ViewModel object and saving it on the backend, then restoring with that object to load this saved state. | ||
@codepen |
@@ -8,21 +8,50 @@ @function can-route.register register | ||
Create a url matching rule. Optionally provide defaults that will be applied to the underlying [can-route.data] when the rule matches. | ||
Create a url matching rule. Optionally provide defaults that will be applied to the underlying [can-route.data] when the rule matches. | ||
The following sets `route.data.page = "cart"` when the url is `#cart` and | ||
`route.data.page = "home"` when the url is `#`. | ||
The following sets `route.data.page = "cart"` when the url is `#cart` and `route.data.page = "home"` when the url is `#`. | ||
```js | ||
route.register( "{page}", { page: "home" } ); | ||
``` | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
@param {String} rule the fragment identifier to match. The fragment identifier should contain characters (a-Z), optionally wrapped in braces ( { } ). Identifiers wrapped in braces are interpreted as being properties on can-route’s map. Examples: | ||
route.register( "{page}", {page: "home"} ); | ||
route.start(); | ||
console.log( route.data.page ); // -> "home" | ||
route.data.page = "cart"; | ||
</script> | ||
``` | ||
@codepen | ||
```js | ||
route.register( "{foo}" ); | ||
route.register( "foo/{bar}" ); | ||
``` | ||
@param {String} rule the fragment identifier to match. The fragment identifier shouldn't contain characters that have special meaning: `/`, `{`, `}`, `?`, `#`, `!`, `=`, `[`, `]`, `&`), for example: `route.register("_||${foo}@^")` is a valid fragment. Identifiers wrapped in braces ( `{ }` ) are interpreted as being properties on can-route’s [can-route.data], these can contain (a-Z), typical property identifiers (`_`, `$`), and numbers after the first character. Examples: | ||
@param {Object} [defaults] An object of default values. These defaults are applied to can-route’s map when the route is matched. | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
route.register( "{foo}" ); | ||
route.register( "foo/{bar}" ); | ||
console.log( route.data ); //-> {foo: undefined, bar: undefined} | ||
route.start(); | ||
route.data.bar = "fie"; // Url hash changes to #!foo/fie | ||
</script> | ||
``` | ||
@codepen | ||
@return {Object} The internal route object. Use values on this object with caution. It is | ||
subject to change. | ||
@param {Object} [defaults] An object of default values. These defaults are applied to can-route’s [can-route.data] when the route is matched. | ||
@return {Object} The internal route object. | ||
Since `route.register` returns the route object, register calls can me chained. | ||
```js | ||
route.register("todos/{todoId}") | ||
.register("users/{userId}"); | ||
``` | ||
@function can-route.rule rule | ||
@parent can-route.static | ||
@description Get the routing rule that matches a url. | ||
@signature `route.rule( url )` | ||
@signature `route.rule(url)` | ||
Returns a string that best matches the provided url. | ||
```js | ||
route.register( "recipes/{recipeId}" ); | ||
route.register( "tasks/{taskId}" ); | ||
route.rule( "recipes/5" ); //-> "recipes/{recipeId}" | ||
``` | ||
```js | ||
import {route} from "can"; | ||
route.register( "recipes/{recipeId}" ); | ||
route.register( "tasks/{taskId}" ); | ||
console.log( route.rule( "recipes/5" ) ); //-> "recipes/{recipeId}" | ||
``` | ||
@codepen | ||
@param {String} url A url or url fragment. | ||
@param {String} url A url fragment. | ||
@return {String|undefined} Returns the [can-route.register registered] routing rule | ||
that best matches the provided url. If no rule matches, `undefined` is returned. | ||
@return {String|undefined} Returns the [can-route.register registered] routing rule | ||
that best matches the provided url. If no rule matches, `undefined` is returned. | ||
@body |
@@ -5,17 +5,24 @@ @function can-route.start start | ||
Initializes can-route. | ||
@description Initializes the two way relationship between the url and route.data. | ||
@signature `route.start()` | ||
Sets up the two-way binding between the hash and the can-route observable | ||
map and sets the route map to its initial values. | ||
Sets up the two-way binding between the hash and the [can-route.data can-route.data] and sets the route.data to its initial values. If URL data and route.data set at the same time the URL data will take precedence. | ||
```js | ||
route.register( "{page}", { page: "home" } ); | ||
route.start(); | ||
route.data.page; // -> "home" | ||
``` | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
@return {can-route} The can-route object. | ||
route.register( "{page}", { page: "home" } ); | ||
route.start(); | ||
console.log( route.data.page ); // -> "home" | ||
</script> | ||
``` | ||
@codepen | ||
@highlight 7 | ||
@return {can-route} The can-route object. | ||
@body | ||
@@ -28,5 +35,15 @@ | ||
```js | ||
import {route} from "can"; | ||
route.register( "overview/{dateStart}-{dateEnd}" ); | ||
route.register( "{type}/{id}" ); | ||
route.start(); | ||
console.log( route.data ); // -> { | ||
// dateEnd: undefined, | ||
// dateStart: undefined, | ||
// id: undefined, | ||
// type: undefined | ||
// } | ||
``` | ||
@codepen |
@@ -5,18 +5,31 @@ @function can-route.stop stop | ||
Stops listening to the [can-route.data] observable and tears down any setup bindings. | ||
@description Stops listening to the [can-route.data] observable and tears down any setup bindings. | ||
@signature `route.stop()` | ||
Stops listening to changes in the URL as well as the observable defined in [can-route.data], and removes the current binding. | ||
Stops listening to changes in the URL as well as the observable defined in [can-route.data], and removes the current binding. | ||
```js | ||
route.register( "{page}", { page: "home" } ); | ||
route.start(); | ||
route.data.page = "home"; | ||
route.stop(); | ||
route.data.page = "cart"; // hash is still #home | ||
``` | ||
```html | ||
<mock-url></mock-url> | ||
<script type="module"> | ||
import "//unpkg.com/mock-url@^5.0.0"; | ||
import {route} from "can"; | ||
@return {can-route} The can-route object. | ||
route.register("{page}", { page: "" }); | ||
route.start(); | ||
route.data.page = "home"; | ||
// Changing the route is not synchronous | ||
setTimeout(() => { | ||
route.stop(); | ||
route.data.page = "cart"; // hash is still "home" | ||
console.log( location.hash ) //-> "#!home" | ||
}, 1000); | ||
</script> | ||
``` | ||
@codepen | ||
@return {can-route} The can-route object. | ||
@body | ||
@@ -26,8 +39,55 @@ | ||
If you need to disconnect an observable from the URL, call stop: | ||
If you need to disconnect an observable from the URL, call stop. | ||
To reconnect, call [can-route.start] again. | ||
```js | ||
route.stop(); | ||
In the example shows a possible use reason for stopping can-route. | ||
When the user logs out the page doesn't change, though the hash still updates. | ||
Notice the `logout`/`login` functions start and stop route. When logged out you can't change the page, | ||
even though the hash still updates. | ||
```html | ||
<mock-url></mock-url> | ||
<my-app></my-app> | ||
<script type="module"> | ||
import {DefineMap, route, Component } from "can"; | ||
import "//unpkg.com/mock-url@^5"; | ||
Component.extend({ | ||
tag: "my-app", | ||
view: `<a href="{{ routeUrl(page="dashboard") }}">dashboard</a> | ||
<a href="{{ routeUrl(page="admin") }}">admin</a><br /> | ||
{{# if (showLogin) }} | ||
<button on:click="login()">login</button> | ||
{{ else }} | ||
<button on:click="logout()">logout</button> | ||
{{/ if }} | ||
<h1>{{componentToShow}}</h1> | ||
`, | ||
ViewModel: { | ||
routeData: { | ||
default() { | ||
route.register("{page}"); | ||
route.data.page = "admin"; | ||
route.start(); | ||
return route.data; | ||
} | ||
}, | ||
logout() { | ||
route.data.page = "login"; | ||
route.stop(); | ||
}, | ||
login() { | ||
route.start(); | ||
route.data.page = "admin"; | ||
}, | ||
get componentToShow() { | ||
return this.routeData.page; | ||
}, | ||
get showLogin() { | ||
return this.routeData.page === "login"; | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
To reconnect, call [can-route.start] again. | ||
@codepen |
@@ -7,5 +7,3 @@ @property {ValueObservable} can-route.urlData urlData | ||
@type {ValueObservable} `urlData` is an observable value that represents the part of the URL cross | ||
bound to the [can-route.data] state object. It can be set to other observable urls like [can-route-pushstate] | ||
or [can-route-mock]. It defaults to [can-route-hash]. | ||
@type {ValueObservable} `urlData` is an observable value that represents the part of the URL cross bound to the [can-route.data] state object. It can be set to other observable urls like [can-route-pushstate] or [can-route-mock]. It defaults to [can-route-hash]. | ||
@@ -15,8 +13,8 @@ The following shows setting `urlData` to another observable. | ||
```js | ||
import {route, RouteMock, DefineMap} from "can"; | ||
import {route, RouteMock} from "can/everything"; | ||
// route.data will update routeMock and be updated by changes in | ||
// routeMock. | ||
var routeMock = route.urlData = new RouteMock(); | ||
var routeData = route.data = new DefineMap({},false); | ||
const routeMock = route.urlData = new RouteMock(); | ||
const routeData = route.data; | ||
@@ -29,3 +27,3 @@ // begin binding | ||
routeData.foo //-> "bar"; | ||
console.log( routeData.foo ); //-> "bar"; | ||
``` | ||
@@ -42,3 +40,2 @@ @codepen | ||
Besides implementing the standard `ValueObservable` symbols: | ||
@@ -45,0 +42,0 @@ |
{ | ||
"name": "can-route", | ||
"version": "4.4.3", | ||
"version": "4.4.4", | ||
"description": "Observable front-end application routing for CanJS.", | ||
@@ -5,0 +5,0 @@ "homepage": "https://canjs.com/doc/can-route.html", |
@@ -46,54 +46,2 @@ "use strict"; | ||
/** | ||
* @function can-route.deparam deparam | ||
* @parent can-route.static | ||
* @description Extract data from a route path. | ||
* @signature `route.deparam(url)` | ||
* | ||
* Extract data from a url, creating an object representing its values. | ||
* | ||
* ```js | ||
* route.register("{page}"); | ||
* | ||
* const result = route.deparam("page=home"); | ||
* console.log(result.page); // -> "home" | ||
* ``` | ||
* | ||
* @param {String} url A route fragment to extract data from. | ||
* @return {Object} An object containing the extracted data. | ||
* | ||
* @body | ||
* | ||
* Creates a data object based on the query string passed into it. This is | ||
* useful to create an object based on the `location.hash`. | ||
* | ||
* ```js | ||
* route.deparam("id=5&type=videos"); | ||
* // -> { id: 5, type: "videos" } | ||
* ``` | ||
* | ||
* | ||
* It's important to make sure the hash or exclamation point is not passed | ||
* to `route.deparam` otherwise it will be included in the first property's | ||
* name. | ||
* | ||
* ```js | ||
* route.data.id = 5 // location.hash -> #!id=5 | ||
* route.data.type = "videos" | ||
* // location.hash -> #!id=5&type=videos | ||
* route.deparam(location.hash); | ||
* // -> { #!id: 5, type: "videos" } | ||
* ``` | ||
* | ||
* `route.deparam` will try and find a matching route and, if it does, | ||
* will deconstruct the URL and parse out the key/value parameters into the | ||
* data object. | ||
* | ||
* ```js | ||
* route.register("{type}/{id}"); | ||
* | ||
* route.deparam("videos/5"); | ||
* // -> { id: 5, route: "{type}/{id}", type: "videos" } | ||
* ``` | ||
*/ | ||
function canRoute_deparam(url) { | ||
@@ -100,0 +48,0 @@ |
@@ -110,35 +110,2 @@ "use strict"; | ||
/** | ||
* @function can-route.param param | ||
* @parent can-route.static | ||
* @description Get a route path from given data. | ||
* @signature `route.param(data)` | ||
* @param {data} object The data to populate the route with. | ||
* @param {String} [currentRouteName] The current route name. If provided, this | ||
* can be used to "stick" the url to a previous route. By "stick", we mean that | ||
* if there are multiple registered routes that match the `object`, the | ||
* the `currentRouteName` will be used. | ||
* @return {String} The route, with the data populated in it. | ||
* | ||
* @body | ||
* Parameterizes the raw JS object representation provided in data. | ||
* | ||
* ```js | ||
* route.param({ type: "video", id: 5 }); | ||
* // -> "type=video&id=5" | ||
* ``` | ||
* | ||
* If a route matching the provided data is found, that URL is built | ||
* from the data. Any remaining data is added at the end of the | ||
* URL as & separated key/value parameters. | ||
* | ||
* ```js | ||
* route.register("{type}/{id}"); | ||
* | ||
* route.param({ type: "video", id: 5 }) // -> "video/5" | ||
* route.param({ type: "video", id: 5, isNew: false }) | ||
* // -> "video/5&isNew=false" | ||
* ``` | ||
*/ | ||
function canRoute_param(data, currentRouteName) { | ||
@@ -145,0 +112,0 @@ return paramFromRoute(getMatchedRoute(data, currentRouteName), data); |
@@ -41,93 +41,4 @@ "use strict"; | ||
module.exports = { | ||
/** | ||
* @function can-route.url url | ||
* @parent can-route.static | ||
* @description Creates a URL fragment based on registered routes given a set of data. | ||
* @signature `route.url(data [, merge])` | ||
* | ||
* Make a URL fragment that when set to window.location.hash will update can-route's properties | ||
* to match those in `data`. | ||
* | ||
* ```js | ||
* route.url({ page: "home" }); | ||
* // -> "#!page=home" | ||
* ``` | ||
* | ||
* @param {Object} data The data to populate the route with. | ||
* @param {Boolean} [merge] Whether the given options should be merged into | ||
* the current state of the route. | ||
* @return {String} The route URL and query string. | ||
* | ||
* @body | ||
* Similar to [can-route.link], but instead of creating an anchor tag, | ||
* `route.url` creates only the URL based on the route options passed into it. | ||
* | ||
* ```js | ||
* route.url( { type: "videos", id: 5 } ); | ||
* // -> "#!type=videos&id=5" | ||
* ``` | ||
* | ||
* If a route matching the provided data is found the URL is built from the | ||
* data. Any remaining data is added at the end of the URL as & separated | ||
* key/value parameters. | ||
* | ||
* ```js | ||
* route.register("{type}/{id}"); | ||
* | ||
* route.url( { type: "videos", id: 5 } ) // -> "#!videos/5" | ||
* route.url( { type: "video", id: 5, isNew: false } ) | ||
* // -> "#!video/5&isNew=false" | ||
* ``` | ||
*/ | ||
url: canRoute_url, | ||
/** | ||
* @function can-route.link link | ||
* @parent can-route.static | ||
* @description Creates a string representation of an anchor link using | ||
* data and the registered routes. | ||
* @signature `route.link(innerText, data, props [, merge])` | ||
* | ||
* Make an anchor tag (`<a>`) that when clicked on will update can-route's | ||
* properties to match those in `data`. | ||
* | ||
* @param {Object} innerText The text inside the link. | ||
* @param {Object} data The data to populate the route with. | ||
* @param {Object} props Properties for the anchor other than `href`. | ||
* @param {Boolean} [merge] Whether the given options should be merged into the current state of the route. | ||
* @return {String} A string with an anchor tag that points to the populated route. | ||
* | ||
* @body | ||
* Creates and returns an anchor tag with an href of the route | ||
* attributes passed into it, as well as any properties desired | ||
* for the tag. | ||
* | ||
* ```js | ||
* route.link( "My videos", { type: "videos" }, {}, false ) | ||
* // -> <a href="#!type=videos">My videos</a> | ||
* ``` | ||
* | ||
* Other attributes besides href can be added to the anchor tag | ||
* by passing in a data object with the attributes desired. | ||
* | ||
* ```js | ||
* route.link( "My videos", { type: "videos" }, | ||
* { className: "new" }, false ) | ||
* // -> <a href="#!type=videos" class="new">My Videos</a> | ||
* ``` | ||
* | ||
* It is possible to utilize the current route options when making anchor | ||
* tags in order to make your code more reusable. If merge is set to true, | ||
* the route options passed into `canRoute.link` will be passed into the | ||
* current ones. | ||
* | ||
* ```js | ||
* location.hash = "#!type=videos" | ||
* route.link( "The zoo", { id: 5 }, true ) | ||
* // -> <a href="#!type=videos&id=5">The zoo</true> | ||
* | ||
* location.hash = "#!type=pictures" | ||
* route.link( "The zoo", { id: 5 }, true ) | ||
* // -> <a href="#!type=pictures&id=5">The zoo</true> | ||
* ``` | ||
*/ | ||
link: function canRoute_link(name, options, props, merge) { | ||
@@ -139,46 +50,3 @@ return "<a " + makeProps( | ||
}, | ||
/** | ||
* @function can-route.isCurrent isCurrent | ||
* @parent can-route.static | ||
* | ||
* Check if data represents the current route. | ||
* | ||
* @signature `route.isCurrent(data [,subsetMatch] )` | ||
* | ||
* Compares `data` to the current route. Used to verify if an object is | ||
* representative of the current route. | ||
* | ||
* ```js | ||
* route.data.set({page: "recipes", id: '5'}); | ||
* | ||
* route.isCurrent({page: "recipes"}); //-> false | ||
* route.isCurrent({page: "recipes"}, true); //-> true | ||
* ``` | ||
* | ||
* @param {Object} data Data to check agains the current route. | ||
* @param {Boolean} [subsetMatch] If true, `route.current` will return true | ||
* if every value in `data` matches the current route data, even if | ||
* the route data has additional properties that are not matched. Defaults to `false` | ||
* where every property needs to be present. | ||
* @return {Boolean} Whether the data matches the current URL. | ||
* | ||
* @body | ||
* | ||
* ## Use | ||
* | ||
* Checks the page's current URL to see if the route represents the options | ||
* passed into the function. | ||
* | ||
* Returns true if the options represent the current URL. | ||
* | ||
* ```js | ||
* route.data.id = 5; // location.hash -> "#!id=5" | ||
* route.isCurrent({ id: 5 }); // -> true | ||
* route.isCurrent({ id: 5, type: 'videos' }); // -> false | ||
* | ||
* route.data.type = 'videos'; | ||
* // location.hash -> #!id=5&type=videos | ||
* route.isCurrent({ id: 5, type: 'videos' }); // -> true | ||
* ``` | ||
*/ | ||
isCurrent: function canRoute_isCurrent(options, subsetMatch) { | ||
@@ -185,0 +53,0 @@ if(subsetMatch) { |
@@ -20,3 +20,3 @@ 'use strict'; | ||
platform: 'OS X 10.13', | ||
version: 'latest', | ||
version: '11', | ||
maxDuration: maxDuration, | ||
@@ -23,0 +23,0 @@ commandTimeout: commandTimeout, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
115649
49
2402
1