This router uses a list of tuples to configure routes e.g. (route, action). When a route is matched the router will call the action specified with the appropiate parameters.
To navigate to a different route you call Hop.navigateTo, this will return an effect that your application must run via ports.
This router is made to work with StartApp.
At the moment only hash routes are supported i.e. #/users/1.
Although a proper url should have the query before the hash e.g. ?keyword=Ja#/users/1,
in Hop query parameters are appended after the hash path e.g. #/users/1?keyword=Ja.
This is done for aesthetics and so the router is fully controlled by the hash fragment.
import Hoptype Action
= HopAction Hop.Action
| ShowUsers Hop.Payload
| ShowUser Hop.Payload
| ShowNotFound Hop.PayloadHop.Payload is the payload that your action will receive when called. See about Payload below.
You need to define an action for when a route is not found e.g. ShowNotFound.
routes : List (String, Hop.Payload -> Action)
routes = [
("/users", ShowUsers),
("/users/:id", ShowUser)
]This is a list of tuples with: (route to match, action to call).
To define dynamic parameter use :, this parameters will be filled by the router e.g. /posts/:postId/comments/:commentId.
router : Hop.Router Action
router =
Hop.new {
routes = routes,
notFoundAction = ShowNotFound
}routes is your list of routes defined above. notFoundAction is the action to call when a route is not found.
Hop.new will give you back a Hop.Router record:
{
signal,
payload,
run
}signal is the signal that will carry changes when the browser location changes.
payload is an initial payload when the router is created.
run is a task to match the initial route, this needs to be send to a port, more details later.
Your start app configuration should include the router signal:
app =
StartApp.start {
init = init,
update = update,
view = view,
inputs = [router.signal]
}This will allow the router to send signal to your application when the location changes.
Your model needs to store the router payload and an attribute for the current view to display:
type alias Model {
routerPayload: Hop.Payload,
currentView: String
}See more details about Hop.Payload below.
Add entries to update for actions related to routing:
update action model =
case action of
ShowUsers payload ->
({model | currentView = "users", routerPayload = payload}, Effects.none)It is important that you update the router payload, this is used to store the current url and the current router parameters.
Your views need to decide what to show. Use the attribute model.currentView for this. E.g.
subView address model =
case model.currentView of
"users" ->
usersView address model
"user" ->
userView address modelGet information about the current route from routerPayload. e.g.
userId =
model.routerPayload.params
|> Dict.get "userId"
|> Maybe.withDefault ""In order to match the initial route when the application is loaded you will need to create a port specifically for this.
port routeRunTask : Task () ()
port routeRunTask =
router.runYour actions are called with a Payload record. This record has:
{
params: Dict.Dict String String,
url: Hop.Url
}params Is dictionary of String String.
When a route matches the route params will be populated in this dictionary. Query string values will also be added here.
E.g. given the route "/users/:userId/projects/:projectId",
when the current url is #/users/1/projects/2?color=red, params will contain:
Dict {
"userId" => "1",
"projectId" => "2",
"color" => "red"
}You have two way to navigate:
a [ href "#/users/1" ] [ text "User" ]Note that you must add the # in this case.
Add two actions
type Action
= ...
| HopAction Hop.Action
| NavigateTo StringHopAction is necessary so effects from the router can be run.
Call the action from your view
button [ onClick address (NavigateTo "/users/1") ] [ text "User" ]You don't need to add # in this case.
Respond to the action in update
update action model =
case action of
...
NavigateTo path ->
(model, Effects.map HopAction (Hop.navigateTo path))Hop.navigateTo will respond with an effect that needs to be run by your application. When this effect is run the hash will change. After that your application will receive a location change signal as described before.
Add actions for changing the query string
type Action
= ...
| AddQuery (Dict.Dict String String)
| SetQuery (Dict.Dict String String)
| ClearQueryChange update to respond to these actions
update action model =
case action of
...
AddQuery query ->
(model, Effects.map HopAction (Hop.addQuery query model.routerPayload.url))
SetQuery query ->
(model, Effects.map HopAction (Hop.setQuery query model.routerPayload.url))
ClearQuery ->
(model, Effects.map HopAction (Hop.clearQuery model.routerPayload.url))Call these actions from your views
button [ onClick address (SetQuery (Dict.singleton "color" "red")) ] [ text "Set query" ]Adds the given Dict to the existing query.
Replaces the existing query with the given Dict.
Removes that key / value from the query string.
Removes the whole query string.
See examples app in ./Examples/. To run the example apps:
- Clone this repo
- Run
elm reactor - Open
http://localhost:8000/Examples/Basic/App.elm - Open
http://localhost:8000/Examples/Advanced/App.elm
elm reactor
Open /localhost:8000/TestRunner.elm
- Change hash without changing query
- Navigate without adding to history
- Support routes without hashes
- Named routes maybe (Using the given action)
- More tests
-
In order to match the initial route we need to manually send tasks to a port. Done via
route.run. This is one more thing for the user to do. Is this really necessary, can this be removed? e.g. Try to channel the initial match through the existingrouter.signal. -
Remove the need to pass the current url to query methods. At the moment we need to send
setQuery url dictbecause Hop cannot figure out the current query by itself. This project could be the solution.
2.1.1Remove unnecessary dependency to elm-test2.1.0ExposeQueryandUrltypes2.0.0Remove dependency onErl. Change order of arguments onaddQuery,clearQuery,removeQueryandsetQuery1.2.1Url is normalized before navigation i.e append#/if necessary1.2.0AddedaddQuery, changed behaviour ofsetQuery.1.1.1Fixed issue where query string won't be set when no hash wash present