more router fixes
This commit is contained in:
parent
b4f1f35c54
commit
f1f52445ec
3 changed files with 190 additions and 9 deletions
|
|
@ -12,13 +12,16 @@ module.exports = function($window, redrawService) {
|
|||
var route = function(root, defaultRoute, routes) {
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var update = function(routeResolver, comp, params, path) {
|
||||
component = comp || "div", attrs = params, currentPath = path, resolve = null
|
||||
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs = params, currentPath = path, resolve = null
|
||||
render = (routeResolver.render || identity).bind(routeResolver)
|
||||
run()
|
||||
}
|
||||
var run = function() {
|
||||
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
|
||||
}
|
||||
var bail = function() {
|
||||
routeService.setPath(defaultRoute)
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path) {
|
||||
if (payload.view) update({}, payload, params, path)
|
||||
else {
|
||||
|
|
@ -30,14 +33,12 @@ module.exports = function($window, redrawService) {
|
|||
}
|
||||
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
|
||||
if (resolve != null) resolve(resolved)
|
||||
})
|
||||
}, bail)
|
||||
}
|
||||
}
|
||||
else update(payload, "div", params, path)
|
||||
}
|
||||
}, function() {
|
||||
routeService.setPath(defaultRoute)
|
||||
})
|
||||
}, bail)
|
||||
redrawService.subscribe(root, run)
|
||||
}
|
||||
route.set = function(path, data, options) {
|
||||
|
|
|
|||
|
|
@ -313,6 +313,125 @@ o.spec("route", function() {
|
|||
})
|
||||
})
|
||||
|
||||
o("accepts RouteResolver with onmatch that returns Promise<undefined>", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
o(this).equals(resolver)
|
||||
return Promise.resolve()
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
o(this).equals(resolver)
|
||||
|
||||
return vnode
|
||||
},
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : resolver
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("accepts RouteResolver with onmatch that returns Promise<any>", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
o(this).equals(resolver)
|
||||
return Promise.resolve([])
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
o(this).equals(resolver)
|
||||
|
||||
return vnode
|
||||
},
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : resolver
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("accepts RouteResolver with onmatch that returns rejected Promise", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var spy = o.spy()
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
matchCount++
|
||||
return Promise.reject(new Error("error"))
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
return vnode
|
||||
},
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/test/1"
|
||||
route(root, "/default", {
|
||||
"/default" : {view: spy},
|
||||
"/test/:id" : resolver
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o("accepts RouteResolver without `render` method as payload", function(done) {
|
||||
var matchCount = 0
|
||||
var Component = {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
- [Advanced component resolution](#advanced-component-resolution)
|
||||
- [Wrapping a layout component](#wrapping-a-layout-component)
|
||||
- [Authentication](#authentication)
|
||||
- [Preloading data](#preloading-data)
|
||||
- [Code splitting](#code-splitting)
|
||||
|
||||
---
|
||||
|
|
@ -107,7 +108,7 @@ A RouterResolver is an object that contains an `onmatch` method and/or a `render
|
|||
|
||||
##### routeResolver.onmatch
|
||||
|
||||
The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic or analytics tracking)
|
||||
The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic, data preloading, redirection analytics tracking, etc)
|
||||
|
||||
This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. To render a component asynchronously return a promise that resolves to a component.
|
||||
|
||||
|
|
@ -117,7 +118,11 @@ Argument | Type | Description
|
|||
--------------- | ------------------------------ | ---
|
||||
`args` | `Object` | The [routing parameters](#routing-parameters)
|
||||
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
|
||||
**returns** | `Promise<Component>|undefined` | Returns a promise that resolves to a component, or undefined
|
||||
**returns** | `Component|Promise<Component>` | Returns a component or a promise that resolves to a component
|
||||
|
||||
If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`.
|
||||
|
||||
If `onmatch` returns a promise that gets rejected, the router redirects back to `defaultRoute`.
|
||||
|
||||
##### routeResolver.render
|
||||
|
||||
|
|
@ -127,8 +132,8 @@ The `render` method is called on every redraw for a matching route. It is simila
|
|||
|
||||
Argument | Type | Description
|
||||
------------------- | --------------- | -----------
|
||||
`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode's `tag` field defaults to a `div`
|
||||
`vnode.attrs` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode defaults to a `div`
|
||||
`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"`
|
||||
`vnode.attrs` | `Object` | A map of URL parameter values
|
||||
**returns** | `Vnode` | Returns a vnode
|
||||
|
||||
---
|
||||
|
|
@ -382,6 +387,62 @@ For the sake of simplicity, in the example above, the user's logged in status is
|
|||
|
||||
---
|
||||
|
||||
### Preloading data
|
||||
|
||||
Typically, a component can load data upon initialization. Loading data this way renders the component twice (once upon routing, and once after the request completes).
|
||||
|
||||
```javascript
|
||||
var state = {
|
||||
users: [],
|
||||
loadUsers: function() {
|
||||
return m.request("/api/v1/users").then(function(users) {
|
||||
state.users = users
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
m.route(document.body, "/user/list", {
|
||||
"/user/list": {
|
||||
oninit: loadUsers,
|
||||
view: function() {
|
||||
return state.users.length > 0 ? state.users.map(function() {
|
||||
return m("div", user.id)
|
||||
}) : "loading"
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, on the first render, the UI displays `"loading"` since `state.users` is an empty array before the request completes. Then, once data is available, the UI redraws and a list of user ids is shown.
|
||||
|
||||
RouteResolvers can be used as a mechanism to preload data before rendering a component in order to avoid UI flickering and thus bypassing the need for a loading indicator:
|
||||
|
||||
```javascript
|
||||
var state = {
|
||||
users: [],
|
||||
loadUsers: function() {
|
||||
return m.request("/api/v1/users").then(function(users) {
|
||||
state.users = users
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
m.route(document.body, "/user/list", {
|
||||
"/user/list": {
|
||||
onmatch: loadUsers,
|
||||
render: function() {
|
||||
return state.users.length > 0 ? state.users.map(function() {
|
||||
return m("div", user.id)
|
||||
}) : "loading"
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Above, `render` only runs after the request completes, making the ternary operator redundant.
|
||||
|
||||
---
|
||||
|
||||
### Code splitting
|
||||
|
||||
In a large application, it may be desirable to download the code for each route on demand, rather than upfront. Dividing the codebase this way is known as code splitting or lazy loading. In Mithril, this can be accomplished by returning a promise from the `onmatch` hook:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue