Skip to content

Commit

Permalink
Wrapping routes with custom data
Browse files Browse the repository at this point in the history
This changed the syntax of the route pre-condition functions as well as the data passed to the routeLoaded and conditionsFailed callbacks.

Fixes #44, #57
Breaking change
  • Loading branch information
ItalyPaleAle committed Nov 17, 2019
1 parent 90d25cb commit dfaaa1e
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 39 deletions.
62 changes: 40 additions & 22 deletions Advanced Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ The event listener receives an `event` object that contains the following `detai
````js
event.detail = {
// The name of the Svelte component that was loaded
component: 'Book',
name: 'Book',
// The actual Svelte component that was loaded (a function)
component: function() {...},
// The current path, equivalent to the value of the $location readable store
location: '/path',
// The "querystring" from the page's hash, equivalent to the value of the $querystring readable store
querystring: 'foo=bar'
querystring: 'foo=bar',
// User data passed with the wrap function; can be any kind of object/dictionary
customData: {...}
}
````

Expand All @@ -37,13 +41,17 @@ For example:
<script>
function routeLoaded(event) {
console.log('routeLoaded event')
console.log('Component', event.detail.component)
console.log('Component', event.detail.component) // This is a Svelte component, so a function
console.log('Name', event.detail.name)
console.log('Location', event.detail.location)
console.log('Querystring', event.detail.querystring)
console.log('User data', event.detail.userData)
}
</script>
````

For help with the `wrap` function, check the [route pre-conditions](#route-pre-conditions) section.

## Querystring parsing

As the main documentation for svelte-spa-router mentions, you can extract parameters from the "querystring" in the hash of the page. This allows you to build apps that navigate to pages such as `#/search?query=hello+world&sort=title`.
Expand Down Expand Up @@ -95,17 +103,22 @@ You can define pre-conditions on routes, also known as "route guards". You can d

Pre-conditions are defined in the routes object, using the `wrap` method exported by the router rather than the Svelte component directly.

The pre-condition functions receive two arguments:
The pre-condition functions receive a dictionary `detail` with the same structure as the `routeLoaded` event:

- `location`: the current path (just like the `$location` readable store)
- `querystring`: the current "querystring" parameters from the page's hash (just like the `$querystring` readable store)
- `detail.component`: the Svelte component that is being evaluated (this is a JavaScript function)
- `detail.name`: name of the Svelte component (a string)
- `detail.location`: the current path (just like the `$location` readable store)
- `detail.querystring`: the current "querystring" parameters from the page's hash (just like the `$querystring` readable store)
- `detail.userData`: custom user data passed with the `wrap` function (see below)

The pre-condition functions must return a boolean indicating wether the condition succeeded (true) or failed (false).

You can define any number of pre-conditions for each route, and they're executed in order. If all pre-conditions succeed (returning true), the route is loaded.

If one condition fails, the router stops executing pre-conditions and does not load any route.

The `wrap` method can also be used to add a dictionary with custom user data, that will be passed to all pre-condition functions, and to the `routeLoaded` and `conditionsFailed` events. This is useful to pass custom callbacks (as properties inside the dictionary) that can be used by the `routeLoaded` and `conditionsFailed` event listeners to take specific actions.

Example:

````svelte
Expand All @@ -116,6 +129,7 @@ import Router from 'svelte-spa-router'
import {wrap} from 'svelte-spa-router'
import Lucky from './Lucky.svelte'
import Hello from './Hello.svelte'
// Route definition object
const routes = {
Expand All @@ -124,37 +138,41 @@ const routes = {
// The Svelte component used by the route
Lucky,
// Custom data: any JavaScript object
// This is optional and can be omitted
{foo: 'bar'},
// First pre-condition function
(location, querystring) => {
(detail) => {
// Pre-condition succeeds only 50% of times
return (Math.random() > 0.5)
},
// Second pre-condition function
(location, querystring) => {
(detail) => {
// This pre-condition is executed only if the first one succeeded
console.log('Pre-condition 2 executed', location, querystring)
console.log('Pre-condition 2 executed', detail.location, detail.querystring)
// Always succeed
return true
}
),
// This route doesn't have pre-conditions, but we're wrapping it to add custom data
'/hello': wrap(
// The Svelte component used by the route
Hello,
// Custom data object
{hello: 'world', myFunc: () => {
console.log('do something!')
}}
)
}
</script>
````

In case a condition fails, the router emits the `conditionsFailed` event, with the following detail object:

````js
event.detail = {
// The name of the Svelte component that matched the path (but wasn't loaded)
component: 'Lucky',
// The current path, equivalent to the value of the $location readable store
location: '/lucky',
// The "querystring" from the page's hash, equivalent to the value of the $querystring readable store
querystring: 'foo=bar'
}
````
In case a condition fails, the router emits the `conditionsFailed` event, with the same `detail` dictionary.

You can listen to the `conditionsFailed` and perform actions in case no route wasn't loaded because of a failed pre-condition:

Expand All @@ -167,7 +185,7 @@ function conditionsFailed(event) {
console.error('conditionsFailed event', event.detail)
// Perform any action, for example replacing the current route
if (event.detail.component == 'Lucky') {
if (event.detail.name == 'Lucky') {
replace('/hello/world')
}
}
Expand Down
48 changes: 38 additions & 10 deletions Router.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@
import {readable, derived} from 'svelte/store'
export function wrap(route, ...conditions) {
/**
* Wraps a route to add route pre-conditions.
*
* @param {SvelteComponent} route - Svelte component for the route
* @param {Object} [userData] - Optional object that will be passed to each `conditionsFailed` event
* @param {...Function} conditions - Route pre-conditions to add, which will be executed in order
* @returns {Object} Wrapped route
*/
export function wrap(route, userData, ...conditions) {
// Check if we don't have userData
if (userData && typeof userData == 'function') {
conditions = (conditions && conditions.length) ? conditions : []
conditions.unshift(userData)
userData = undefined
}
// Parameter route and each item of conditions must be functions
if (!route || typeof route != 'function') {
throw Error('Invalid parameter route')
Expand All @@ -18,7 +33,7 @@ export function wrap(route, ...conditions) {
}
// Returns an object that contains all the functions to execute too
const obj = {route}
const obj = {route, userData}
if (conditions && conditions.length) {
obj.conditions = conditions
}
Expand Down Expand Up @@ -219,10 +234,12 @@ class RouteItem {
if (typeof component == 'object' && component._sveltesparouter === true) {
this.component = component.route
this.conditions = component.conditions || []
this.userData = component.userData
}
else {
this.component = component
this.conditions = []
this.userData = undefined
}
this._pattern = pattern
Expand Down Expand Up @@ -256,16 +273,25 @@ class RouteItem {
return out
}
/**
* Dictionary with route details passed to the pre-conditions functions, as well as the `routeLoaded` and `conditionsFailed` events
* @typedef {Object} RouteDetail
* @property {SvelteComponent} component - Svelte component
* @property {string} name - Name of the Svelte component
* @property {string} location - Location path
* @property {string} querystring - Querystring from the hash
* @property {Object} [userData] - Custom data passed by the user
*/
/**
* Executes all conditions (if any) to control whether the route can be shown. Conditions are executed in the order they are defined, and if a condition fails, the following ones aren't executed.
*
* @param {string} location - Location path
* @param {string} querystring - Querystring
*
* @param {RouteDetail} detail - Route detail
* @returns {bool} Returns true if all the conditions succeeded
*/
checkConditions(location, querystring) {
checkConditions(detail) {
for (let i = 0; i < this.conditions.length; i++) {
if (!this.conditions[i](location, querystring)) {
if (!this.conditions[i](detail)) {
return false
}
}
Expand Down Expand Up @@ -308,13 +334,15 @@ $: {
const match = routesList[i].match($loc.location)
if (match) {
const detail = {
component: routesList[i].component.name,
component: routesList[i].component,
name: routesList[i].component.name,
location: $loc.location,
querystring: $loc.querystring
querystring: $loc.querystring,
userData: routesList[i].userData
}
// Check if the route can be loaded - if all conditions succeed
if (!routesList[i].checkConditions($loc.location, $loc.querystring)) {
if (!routesList[i].checkConditions(detail)) {
// Trigger an event to notify the user
dispatchNextTick('conditionsFailed', detail)
break
Expand Down
26 changes: 19 additions & 7 deletions example/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,29 @@ if (!urlParams.has('routemap')) {

// Wildcard parameter
'/wild': Wild,
// Special route that has custom data that will be passed to the `routeLoaded` event
'/wild/data': wrap(Wild, {hello: 'world'}),
'/wild/*': Wild,

// This route has a pre-condition function that lets people in only 50% of times, and a second pre-condition that is always true
// The second argument is a custom data object that will be passed to the `conditionsFailed` event if the pre-conditions fail
'/lucky': wrap(Lucky,
(location, querystring) => {
{foo: 'bar'},
(detail) => {
// If there's a querystring parameter, override the random choice (tests need to be deterministic)
if (querystring == 'pass=1') {
if (detail.querystring == 'pass=1') {
return true
}
else if (querystring == 'pass=0') {
else if (detail.querystring == 'pass=0') {
return false
}
// Random
return (Math.random() > 0.5)
},
(location, querystring) => {
(detail) => {
// This pre-condition is executed only if the first one succeeded
console.log('Pre-condition 2 executed', location, querystring)
// eslint-disable-next-line no-console
console.log('Pre-condition 2 executed', detail.location, detail.querystring, detail.userData)

// Always succeed
return true
Expand All @@ -73,14 +78,21 @@ else {

// Wildcard parameter
routes.set('/wild', Wild)
// Special route that has custom data that will be passed to the `routeLoaded` event
routes.set('/wild/data', wrap(Wild, {hello: 'world'}))
routes.set('/wild/*', Wild)

// This route has a pre-condition function that lets people in only 50% of times (and a second pre-condition that is always true)
// The second argument is a custom data object that will be passed to the `conditionsFailed` event if the pre-conditions fail
routes.set('/lucky', wrap(Lucky,
() => {
{foo: 'bar'},
(detail) => {
return (Math.random() > 0.5)
},
() => {
(detail) => {
// This pre-condition is executed only if the first one succeeded
// eslint-disable-next-line no-console
console.log('Pre-condition 2 executed', detail.location, detail.querystring, detail.userData)
return true
}
))
Expand Down

0 comments on commit dfaaa1e

Please sign in to comment.