This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.
A simple nested router for Elm SPA
version 5.0.0
license MIT
native-modules False
elm-version 0.18.0 <= v < 0.19.0
Tag 5.0.0
Committed At 2017-03-29 13:46:56 UTC
jvoigtlaender/elm-memo 2.0.5 <= v < 3.0.0 2.0.5
elm-lang/navigation 2.1.0 <= v < 3.0.0 2.1.0
elm-lang/http 1.0.0 <= v < 2.0.0 1.0.0
elm-lang/html 2.0.0 <= v < 3.0.0 2.0.0
elm-lang/core 5.1.1 <= v < 6.0.0 5.1.1
Bogdanp/elm-combine 3.1.1 <= v < 4.0.0 3.1.1

README

Elm nested router Build Status

A simple router for single page applications, written in Elm.

Elm nested router allows to separate application logic by specific routes. For every Route you can provide a list of messages that has to be performed on entering to Route, and a render function that renders Route specific HTML parts.

Example

To create a routeable application we have to keep Router state (Current route and arguments) in application state. In order to do this we create our application state by using a helper WithRouter:

import Router
import Router.Types exposing (WithRouter)

-- We use `WithRouter` to define application state
type alias State = WithRouter Route
  {
    categories: List Category,
    posts: List Post,
    post: Maybe Post
  }

-- construct initialState with initial values
initialState : State
initialState = {
    router      = Router.initialState
  , categories  = []
  , posts       = []
  , post        = Nothing
  }

Next step will be the definition of application routes:

type Route = Home | NotFound | Static String | Category | Post

-- Route list that is required by router.
-- Note that routes which registered first have higher priority to be matched. So when you have concurrent routes, order of this list is important.
routes : List Route
routes = [
    NotFound
  , Static "about"
  , Static "contacts"
  , Home
  , Category
  , Post
  ]

Another thing that we need to define is a mapping between routes and route configurations:

import Router.Types exposing (RouteConfig)
import URL.Segments as Segments exposing ((</>))
import URL.Route exposing ((//>))

routeConfig : Route -> RouteConfig Route State
routeConfig route = case route of
  Home -> {
    route = Nothing //> Segments.end,
  , render = renderHome
  , actions = [LoadCategories]
  }
  NotFound -> {
    route = Nothing //> Segments.static "404",
  , render = notFound
  , actions = []
  }
  Static page -> {
    route = Nothing //> Segments.static page
  , render = renderStatic page
  , actions = []
  }
  -- `category` and `subcategory` is dynamic route params
  -- `category` param match only "animals", "flowers", "colors" because of its constraints
  -- `subcategory` is optional and might be omitted
  Category -> {
    route = Just Home //> Segments.enum "category" ["animals", "flowers", "colors"] </> Segments.maybe (Segments.string "subcategory")
  , render = renderCategory
  , actions = [LoadPosts]
  }
  -- `postId` argument must be an integer
  Post -> {
    route = Just Route.Category //> Segments.static "post" </> Segments.int "postId"
  , render = renderPost
  , actions = [LoadPost]
  }

Route Post on the example above is rely on routes Category and Home - that means all actions binded to routes Home, Category and Post will be executed to enter to Post route. Full URL template that will match Post route will also be combined with its parent routes.

Each handler provides named views: Dict String Html - these HTML parts are finally combined and rendered in application layout:

layout : Router Route State -> State -> Dict String (Html (Action State)) -> Html (Action State)
layout router _ views =
  let
    defaultHeader = Html.header [] [Html.text "Default header"]
    defaultFooter = Html.footer [] [Html.text "Default footer"]
    defaultBody = Html.div [] []
  in Html.div [] [
    Maybe.withDefault defaultHeader <| Dict.get "header" views
  , Maybe.withDefault defaultBody   <| Dict.get "body" views
  , Maybe.withDefault defaultFooter <| Dict.get "footer" views
  ]

Finally we could dispacth our application:

import App.Actions exposing (update)

main = Router.dispatch
  (noFx initialState)
  (RouterConfig {
    html5 = False
  , removeTrailingSlash = True
  , update = update
  , onTransition = []
  , layout = layout
  , routes = routes
  , routeConfig = routeConfig
  , subscriptions = always Sub.none
  })

See Example (Live demo) and Tests for more details.

Advanced example

Currently supports

  • [x] HTML5 push state
  • [x] Route params
  • [x] Params constraints (String, Int, Enum, Regexp)
  • [x] Optional params
  • [x] Named views

Future thoughts

  • [ ] Query string parameters support