From 8554cf50daac105cd9901a64b6d6baec15cde0bb Mon Sep 17 00:00:00 2001 From: Thomas Dillard Date: Thu, 8 Nov 2018 11:16:43 -0700 Subject: [PATCH] Fixing documentation issues (#211) * Updated examples to use codepen and indentation. * Moved documentation from inline to seperate files. This includes the following documentation - deparam - isCurrent - link - param - url All documentation is avaliable in the doc folder. * removed newline split from start.md * Updated examples and linked to Codepen. - currentRule.md - deparam.md - isCurrent.md - link.md - param.md - url.md * updated deparam documentation and example. * Incremental Changes to Examples and Docs `doc/currentRule.md` needed major updates to the signature example so it would work. This includes starting the route and adding Timeouts. Other changes are minor refactoring to improve over all consistency. * Updated examples and documentation for can-route and data. Fixed spacing in link and url. * minor doc and example updates. This includes removing examples that have been deemed redundant. * Deprecated route.link - There were some issues creating a deprecated group in can-route.md. Added to #209. * Added example to show possible use for using route.stop. * Minor documentation updates and examples * added note about how route.rule is used. * removed incorrect doc from rule.md * Small documentation updates * using Safari 11 in SauceLabs --- doc/can-route.md | 325 ++++++++++++++++++++++++++++----------------- doc/currentRule.md | 30 +++-- doc/data.md | 182 +++++-------------------- doc/deparam.md | 69 ++++++++++ doc/isCurrent.md | 34 +++++ doc/link.md | 69 ++++++++++ doc/param.md | 26 ++++ doc/register.md | 63 ++++++--- doc/rule.md | 26 ++-- doc/start.md | 35 +++-- doc/stop.md | 90 ++++++++++--- doc/url.md | 62 +++++++++ doc/urlData.md | 13 +- src/deparam.js | 52 -------- src/param.js | 33 ----- src/url-helpers.js | 136 +------------------ 16 files changed, 688 insertions(+), 557 deletions(-) create mode 100644 doc/deparam.md create mode 100644 doc/isCurrent.md create mode 100644 doc/link.md create mode 100644 doc/param.md create mode 100644 doc/url.md diff --git a/doc/can-route.md b/doc/can-route.md index e769ee6..cfc2184 100644 --- a/doc/can-route.md +++ b/doc/can-route.md @@ -1,5 +1,6 @@ @module {Object} can-route can-route -@group can-route.static static +@group can-route.static 0 static +@group deprecated 1 deprecated @download can/route @test can-route/test.html @parent can-routing @@ -7,7 +8,7 @@ @link ../docco/route/route.html docco @package ../package.json -@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. @type {Object} @@ -16,24 +17,24 @@ the can-route export: ```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 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 tag for it. + isCurrent, // Given data, return true if the current url matches + // the data. + currentRule // Return the matched rule name. + } + ``` @body @@ -49,7 +50,7 @@ without changing the page. This provides the basics needed to create history enabled single-page apps. However, -`route` addresses several other needs aswell, such as: +`route` addresses several other needs as well, such as: - Pretty urls. - Keeping routes independent of application code. @@ -74,61 +75,72 @@ can-route keeps the state of the hash in-sync with the [can-route.data] containe Underlying `can-route` is an observable map: [can-route.data can-route.data]. Depending on what type of map your application uses this could be a [can-define/map/map], an [can-observe.Object] or maybe even a [can-simple-map]. -Typically, the map is the view-model of the top-level [can-component] in your -application. For example, the following defines ``, and uses the view-model -of a `` 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 + + + + ``` +@codepen -> __Note__: The `route.data = document.querySelector("my-app")` statement is what -> sets `route.data` to ``'s view-model. - -An observable can be set as `route.data` directly. The following sets `route.data` -to an `AppViewModel` instance: +`route.data` defaults to [can-define/map/map], but `route.data` can be set to any observable. The following uses [can-observe]: ```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 Understanding how maps work is essential to understanding `can-route`. @@ -140,21 +152,22 @@ You can listen to changes in the url by listening on the underlying route data. your route data and rule might have a page property: ```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 ### Updating can-route @@ -173,82 +186,120 @@ route.data.update( { page: "tasks", id: 5 } ); When you make changes to can-route, they will automatically change the hash. -If using [can-map] or [can-simple-map] to back your route, update `route.data` using `attr`. - ### Encoded `/` If the change in your route data includes a `/`, the `/` will be encoded into `%2F`. You will see this result in the URL and `location.hash`. -```js -route.data.type = "image/bar"; -// OR -route.attr( "type", "image/bar" ); -``` - -The URL will look like this: +```html + + +``` +@codepen ## 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 an object (that is the route’s data). In order to map to specific properties in the url, prepend a colon to the name of the property like: -```js -route.register( "#!content/{type}" ); +```html + + ``` +@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 + + ``` +@codepen Once routes are added and the hash changes, can-route looks for matching routes and uses them to update can-route’s data. -```js -route.register( "#!content/{type}" ); -location.hash = "#!content/images"; +```html + + ``` +@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 + + ``` +@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 + + ``` +@codepen ## Initializing can-route @@ -261,8 +312,8 @@ route.start(); ## Changing the 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: ```js @@ -272,65 +323,101 @@ route.data.type = "videos"; This will automatically look up the appropriate route and update the hash. -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 +Videos ``` +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 2. If multiple routes are matched, find the route with the highest number of set properties 3. If multiple routes are still matched, use the route that was registered first + ### Find all routes with all of their map properties set In order for a route to be matched, all of the map properties it uses must be set. For example, in the following route, `page` and `section` must be set in order for this route to be matched: ```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 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 If multiple routes have all of their properties set, the route with the highest number of set properties will be used: ```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 ### Find the route that was registered first If multiple routes are still matched, the route that was registered first will be matched: ```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 diff --git a/doc/currentRule.md b/doc/currentRule.md index f53c0cf..4aa60ba 100644 --- a/doc/currentRule.md +++ b/doc/currentRule.md @@ -3,18 +3,26 @@ @description A compute representing the currently matched routing rule route. @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. diff --git a/doc/data.md b/doc/data.md index 095f9bf..9cf9d4c 100644 --- a/doc/data.md +++ b/doc/data.md @@ -5,167 +5,55 @@ An observable key-value object used to cross bind to the url observable [can-rou @type {Object} If `route.data` is set to a [can-reflect]ed observable object of 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 + + + ``` + @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"; - -Component.extend( { - tag: "my-app", - autoMount: true, - ViewModel: { /* ... */ }, - view: { /* ... */ } -} ); -route.data = document.querySelector( "my-app" ); -route.register( "{page}" ); -route.start(); -``` +
+
+

Deprecated

+
+

Assigning an HTMLElement to route.data 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. +

+
+
@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 ViewModel](https://www.youtube.com/watch?v=LrzK4exG5Ss), holds the high level state of the application. +For in-depth examples see the the [guides/routing Routing] guide. ## 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). - -```js -const ViewModel = DefineMap.extend( { - petType: "string", - storeId: "number" -} ); -const viewModel = new ViewModel( { - petType: "string", - storeId: "number" -} ); -route.data = viewModel; -``` - -`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; -``` - -## 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. +`route.data` defaults to [can-define/map/map], but `route.data` can be set to any observable. The following uses [can-observe]: ```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( "," ); - }, +import {DefineMap, route, observe} from "can/everything"; - // 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(); -} ); +route.data = new observe(); +route.register( "{page}", { page: "home" } ); +route.start(); +console.log( route.data.page ) //-> "home" ``` - -## 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 diff --git a/doc/deparam.md b/doc/deparam.md new file mode 100644 index 0000000..d5a23aa --- /dev/null +++ b/doc/deparam.md @@ -0,0 +1,69 @@ +@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 fragment, creating an object representing its values. The url fragment could be a [location.hash](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hash) or [location.search](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search). + + ```js + import {route} from "can"; + + const result = route.deparam("page=home"); + console.log( result.page ); //-> "home" + ``` + @codepen + + @param {String} url A route fragment to extract data from. + @return {Object} An object containing the extracted data. + +@body + +## Use + +`route.deparam` 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 +import {route} from "can"; + +const result = route.deparam("id=5&type=videos"); +console.log( result ); //-> { id: 5, type: "videos" } +``` +@codepen + +It's important to make sure the hash or exclamation point is not passed to `route.deparam` otherwise it will be included as a property. + +```html + + +``` +@codepen + +`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 +import {route} from "can"; + +route.register("{type}/{id}"); + +const result = route.deparam("videos/5"); +console.log( result ); //-> { id: 5, type: "videos" } +``` +@codepen diff --git a/doc/isCurrent.md b/doc/isCurrent.md new file mode 100644 index 0000000..2aa5d37 --- /dev/null +++ b/doc/isCurrent.md @@ -0,0 +1,34 @@ +@function can-route.isCurrent isCurrent +@parent can-route.static + +@description 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. + + The following example calls `route.isCurrent` with a single matching parameter when `route.data` has two properties. If `subsetMatch` is `false` or left default `route.isCurrent` won't try and match subsets. + + ```js + import {route} from "can"; + + route.data = {page: "recipes", id: "5"}; // location.hash -> "#!&page=recipes&id=5" + route.start(); + + setTimeout(() => { + const completeSet = route.isCurrent( {page: "recipes"} ); + console.log( completeSet ); //-> false + + const subSet = route.isCurrent( {page: "recipes"}, true ); + console.log( subSet ); //-> true + }, 200); + ``` + @codepen + + @param {Object} data Data to check against 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. diff --git a/doc/link.md b/doc/link.md new file mode 100644 index 0000000..81956c9 --- /dev/null +++ b/doc/link.md @@ -0,0 +1,69 @@ +@function can-route.link link +@parent can-route.deprecated + +@description Creates a string representation of an anchor link using data and the registered routes. + +
+
+

Deprecated 4.4.1

+
+

route.link has been deprecated in favor of can-stache-route-helpers.routeCurrent. +

+
+
+ +@signature `route.link(innerText, data, props [, merge])` + + Make an anchor tag (``) that when clicked on will update can-route's properties to match those in `data`. Creates and returns an anchor tag with a href of the route attributes passed into it, as well as any properties desired for the tag. + + ```js + import {route} from "can"; + + const link = route.link( "My videos", { type: "videos" }, {}, false ); + console.log( link ); //-> 'My videos' + ``` + @codepen + + @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`. + + Other attributes besides href can be added to the anchor tag by passing in a data object with the attributes desired. + + ```js + import {route} from "can"; + + const link = route.link( + "My videos", + { type: "videos" }, + { className: "new" }, + false + ); + console.log( link ); //-> 'My videos' + ``` + @codepen + + @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 + +## Use + +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 +import {route} from "can"; + +location.hash = "#!type=videos"; +const videoLink = route.link( "The zoo", { id: 5 }, {}, true ); +console.log( videoLink ); //-> The zoo + +location.hash = "#!type=pictures"; +const pictureLink = route.link( "The zoo", { id: 5 }, {}, true ); +console.log( pictureLink ); //-> The zoo +``` +@codepen diff --git a/doc/param.md b/doc/param.md new file mode 100644 index 0000000..f95c770 --- /dev/null +++ b/doc/param.md @@ -0,0 +1,26 @@ +@function can-route.param param +@parent can-route.static + +@description Creates a url fragment that represents provided state. + +@signature `route.param( data )` + + Parameterizes the raw JS object representation provided in data. Any remaining data is added at the end of the URL as & separated key/value parameters. + + ```js + import {route} from "can"; + + route.register( "{type}/{id}" ); + + const video = route.param( { type: "video", id: 5 } ); + console.log( video ); // -> "video/5" + + const notNewVideo = route.param( { type: "video", id: 5, isNew: false } ); + console.log( notNewVideo ); // -> "video/5&isNew=false" + ``` + @codepen + + @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 `currentRouteName` will be used. + + @return {String} The route, with the data populated in it. diff --git a/doc/register.md b/doc/register.md index 13a7b80..f6989ee 100644 --- a/doc/register.md +++ b/doc/register.md @@ -5,23 +5,52 @@ @signature `route.register(rule [, defaults])` -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 `#`. + + ```html + + + ``` + @codepen + + @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: + + ```html + + + ``` + @codepen + + @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}"); + ``` -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" } ); -``` -@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: - -```js -route.register( "{foo}" ); -route.register( "foo/{bar}" ); -``` - -@param {Object} [defaults] An object of default values. These defaults are applied to can-route’s map when the route is matched. - -@return {Object} The internal route object. Use values on this object with caution. It is - subject to change. diff --git a/doc/rule.md b/doc/rule.md index cc24aa5..9c86199 100644 --- a/doc/rule.md +++ b/doc/rule.md @@ -1,21 +1,23 @@ @function can-route.rule rule @parent can-route.static -@description Get the routing rule that matches a url. - -@signature `route.rule(url)` +@description Get the routing rule that matches a url. -```js -route.register( "recipes/{recipeId}" ); -route.register( "tasks/{taskId}" ); -route.rule( "recipes/5" ); //-> "recipes/{recipeId}" -``` +@signature `route.rule( url )` -@param {String} url A url or url fragment. + Returns a string that best matches the provided url. -@return {String|undefined} Returns the [can-route.register registered] routing rule -that best matches the provided url. If no rule matches, `undefined` is returned. + ```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 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. -@body diff --git a/doc/start.md b/doc/start.md index fbade87..3ee0799 100644 --- a/doc/start.md +++ b/doc/start.md @@ -2,20 +2,27 @@ @parent can-route.static @release 3.3 -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 + + + ``` + @codepen + @highlight 7 + + @return {can-route} The can-route object. @body @@ -24,7 +31,17 @@ route.data.page; // -> "home" After setting all your routes, call `route.start()`. ```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 diff --git a/doc/stop.md b/doc/stop.md index bf7c834..4a828a7 100644 --- a/doc/stop.md +++ b/doc/stop.md @@ -2,30 +2,90 @@ @parent can-route.static @release 4.1 -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 + + + ``` + @codepen + + @return {can-route} The can-route object. @body ## Use -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. -To reconnect, call [can-route.start] again. +```html + + + +``` +@codepen diff --git a/doc/url.md b/doc/url.md new file mode 100644 index 0000000..f71af49 --- /dev/null +++ b/doc/url.md @@ -0,0 +1,62 @@ +@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 + import {route} from "can"; + + const url = route.url( { page: "home" } ); + console.log( url ); //-> "#!&page=home" + ``` + @codepen + + @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. + ```js + import {route} from "can"; + + route.data.update( {type: "items", id: 5} ); + route.start(); + + setTimeout(() => { + const url = route.url( { page: "home" }, true ); + console.log( url ); //-> ""#!&type=test&id=5&page=home"" + }, 100); + ``` + @codepen + + @return {String} The route URL and query string. + +@body + +## Use + +`route.url` creates only the URL based on the route options passed into it. + +```js +import {route} from "can"; + +const url = route.url( { type: "videos", id: 5 } ); +console.log( url ); //-> "#!&type=videos&id=5" +``` +@codepen + +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 +import {route} from "can"; + +route.register("{type}/{id}"); + +const video = route.url( { type: "videos", id: 5 } ); +console.log( video ); //-> "#!videos/5" + +const notNewVideo = route.url( { type: "video", id: 5, isNew: false } ); +console.log( notNewVideo ); //-> "#!video/5&isNew=false" +``` +@codepen \ No newline at end of file diff --git a/doc/urlData.md b/doc/urlData.md index 8aff750..e5947e2 100644 --- a/doc/urlData.md +++ b/doc/urlData.md @@ -4,19 +4,17 @@ Specifies an observable value that represents the URL. Useful for changing what URL [can-route route] is cross-bound to. -@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]. 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; // begin binding route.start() @@ -24,7 +22,7 @@ what URL [can-route route] is cross-bound to. // simulate setting the URL routeMock.value = "foo=bar"; - routeData.foo //-> "bar"; + console.log( routeData.foo ); //-> "bar"; ``` @codepen @@ -36,7 +34,6 @@ what URL [can-route route] is cross-bound to. > future release. Please let us know if you are trying to create your own > observable and we will work with you to stabilize the API. - Besides implementing the standard `ValueObservable` symbols: - [can-reflect.getValue] diff --git a/src/deparam.js b/src/deparam.js index 74e622e..903fd91 100644 --- a/src/deparam.js +++ b/src/deparam.js @@ -43,58 +43,6 @@ function canRoute_getRule(url){ } } -/** - * @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) { var route = canRoute_getRule(url), diff --git a/src/param.js b/src/param.js index eb73e02..a3c94a0 100644 --- a/src/param.js +++ b/src/param.js @@ -107,39 +107,6 @@ function paramFromRoute(route, data) { return canReflect.size(data) === 0 ? "" :bindingProxy.call("querySeparator") + param(data); } - -/** - * @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) { return paramFromRoute(getMatchedRoute(data, currentRouteName), data); } diff --git a/src/url-helpers.js b/src/url-helpers.js index 0bf89a9..ac6f78c 100644 --- a/src/url-helpers.js +++ b/src/url-helpers.js @@ -38,147 +38,15 @@ function canRoute_url(options, merge) { return bindingProxy.call("root") +routeParam(options); } 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 (``) 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 ) - * // -> My videos - * ``` - * - * 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 ) - * // -> My Videos - * ``` - * - * 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 ) - * // -> The zoo - * - * location.hash = "#!type=pictures" - * route.link( "The zoo", { id: 5 }, true ) - * // -> The zoo - * ``` - */ + link: function canRoute_link(name, options, props, merge) { return "" + name + ""; }, - /** - * @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) { if(subsetMatch) { // everything in options shouhld be in baseOptions