-
Notifications
You must be signed in to change notification settings - Fork 1
belas 2
This is PART II of the article on Basic Elm-Like App Structure (BELAS).
Let's look at handling routing with the app structure we've built. Our strategy will be to represent changes in the route as changes in the model, then to continue as before by using that model to render a view.
Specifically, we'll use m.route()
and a RouteResolver, in routeResolver.onmatch
we'll call a method of the actions
object (we consider route-changes "outside input"), the actions
object will update model
, which will then be rendered by view()
.
As an example let's create a small app which will display a "FRUITS" or "VEGETABLES" pane depending on whether we're on a "/fruits" or "/vegetables" route. We'll use the same index.html
from PART I. Here's app.js
:
// app.js
window.addEventListener("DOMContentLoaded", main)
function main() {
var settings = getSettings()
, model = createModel(settings)
, actions = createActions(model)
, view = createView()
, routeResolver = createRouteResolver(model, actions, view)
, defaultRoute = "/" + settings.paths[0]
m.route(document.body, defaultRoute, routeResolver)
}
function getSettings() {
return {
paths : ["fruits", "vegetables"]
}
}
function createModel(settings) {
return {
paths: settings.paths.slice()
, routeName: undefined
}
}
function createActions(model) {
return {
onNavigateTo: onNavigateTo
}
function onNavigateTo(routeName, params) {
model.routeName = routeName
}
}
function createRouteResolver(model, actions, view) {
return {
"/fruits": {
onmatch: function(params, route) {
actions.onNavigateTo("fruits", params)
}
, render: function() {
return view(model, actions)
}
}
, "/vegetables": {
onmatch: function(params, route) {
actions.onNavigateTo("vegetables", params)
}
, render: function() {
return view(model, actions)
}
}
}
}
function createView() {
return vwApp
function vwApp(model, actions) {
return m(".app"
, vwHeader(model)
, model.routeName == "fruits"
? vwFruits(model)
: vwVegetables(model)
)
}
function vwHeader(model) {
return m(".nav"
, model.paths.map(function(itm) {
return [
m("a"
, {href: "/" + itm, oncreate: m.route.link}
, itm
)
, " "
]
})
)
}
function vwFruits(model) {
return m(".fruits", "FRUITS" )
}
function vwVegetables(model) {
return m(".vegetables", "VEGETABLES")
}
}
Here's a Flems of this code.
What if our routes also had route- and maybe even query-params? As a contrived example let's say the /fruits
and /vegetables
routes have :name
and :color
respectively as optional route params, and qty
in each case as a query param. Here's app.js
:
Also, while we hand-crafted a RouteResolver
above, this time we "generate" one. If we have unique route names this is a "pattern" we can use with all routed apps.
// app.js
window.addEventListener("DOMContentLoaded", main)
function main() {
var settings = getSettings()
, model = createModel(settings)
, actions = createActions(model)
, view = createView()
, routeResolver = createRouteResolver(model, actions, view)
, defaultRoute = settings.defaultRoute
m.route(document.body, defaultRoute, routeResolver)
}
function getSettings() {
var routes = [
{ name: "fruits", route: "/fruits" }
, { name: "fruits-name", route: "/fruits/:name" }
, { name: "vegetables", route: "/vegetables" }
, { name: "vegetables-color", route: "/vegetables/:color" }
]
, settings = {
routes: routes
, defaultRoute: routes[0].route + "?qty=1"
}
return settings
}
function createModel(settings) {
return {
routes: settings.routes.slice()
, routeName: undefined
, params: undefined
}
}
function createActions(model) {
return {
onNavigateTo
}
function onNavigateTo(routeName, params) {
model.routeName = routeName
model.params = params
}
}
function createRouteResolver(model, actions, view) {
return model.routes.reduce(function(acc, itm) {
acc[itm.route] = {
onmatch: function(params, route) {
actions.onNavigateTo(itm.name, params)
}
, render: function() {
return view(model, actions)
}
}
return acc
}, {})
}
function createView() {
return vwApp
function vwApp(model, actions) {
return m(".app"
, vwHeader(model)
, model.routeName.indexOf("fruits") > -1
? vwFruits(model)
: vwVegetables(model)
)
}
function vwHeader(model) {
var sampleParams = {
"fruits-name" : "/fruits/apple"
, "vegetables-color" : "/vegetables/green"
}
return m(".nav"
, model.routes.map(function(itm, idx) {
var href = (sampleParams[itm.name] || ("/" + itm.name))
+ "?qty=" + (idx + 1)
return [
m("a"
, {href: href, oncreate: m.route.link}
, itm.name
)
, " "
]
})
)
}
function vwFruits(model) {
return m(".fruits"
, "FRUITS"
, m("", JSON.stringify(model.params))
)
}
function vwVegetables(model) {
return m(".vegetables"
, "VEGETABLES"
, m("", JSON.stringify(model.params))
)
}
}
Here's a Flems of this code.
Points to keep in mind:
- syncing route-to-model starts with routeResolver-
onmatch()
callingactions.onNavigateTo(...)
with arguments. What we pass as arguments depends on our routes and needs -- in our examples above it suffices to pass a "route name" since for us they're unique. The rule of thumb is to pass in something, even an artificial, generated value, that letsonNavigateTo()
uniquely identify the route, and thereby figure out expected params and set model state.
BTW, while it's possible for actions.onNavigateTo()
to get route information by calling m.route.get()
directly, we pass in that information so as to keep our Mithril-aware code, here createRouteResolver
, cohesive and decoupled from our Mithril-agnostic code, here actions.onNavigateTo()
.
Continue to PART III
The Mithril Diaries . Home
- Home
- Finding Mithril
- Coming from Elm
- why mithril (TBD)
- Basic Elm-Like App Structure (BELAS)
- foxdonut's "Meiosis" (TBD)
- Model-management with Meiosis
- Model- and Route-management with Meiosis
- using 3rd-party widgets (TBD)
- UI Animation
- Drag-and-Drop
- Gestures/Physics? (TBD)
- Server-Side Rendering (TBD)