Code Monkey home page Code Monkey logo

hop's Introduction

Hop: A router for Elm SPAs

alt Hope

How this works

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.

Setup

Import Hop

import Hop

Define actions to be called when a route matches:

type Action
  = HopAction Hop.Action
	| ShowUsers Hop.Payload
	| ShowUser Hop.Payload
	| ShowNotFound Hop.Payload

Hop.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.

Define your routes:

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.

Create the router

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.

Add the router signal to StartApp inputs

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.

Add fields to your model

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 update actions

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.

Wire up your views

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 model

Get information about the current route from routerPayload. e.g.

userId =
	model.routerPayload.params
		|> Dict.get "userId"
		|> Maybe.withDefault ""

Run the router

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.run

About Hop.Payload

Your actions are called with a Payload record. This record has:

{
	params: Dict.Dict String String,
	url: Erl.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"
}

Navigation

You have two way to navigate:

1. Using plain a tags

	a [ href "#/users/1" ] [ text "User" ]

Note that you must add the # in this case.

2. Using effects

Add two actions

type Action
	= ...
	| HopAction Hop.Action
	| NavigateTo String

HopAction 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.

Changing the query string

Add actions for changing the query string

type Action
	= ...
	| SetQuery (Dict.Dict String String)
  | ClearQuery

Change update to respond to these actions

update action model =
	case action of
		...
		SetQuery query ->
			(model, Effects.map HopAction (Hop.setQuery model.routerPayload.url query))
		ClearQuery ->
			(model, Effects.map HopAction (Hop.clearQuery model.routerPayload.url))

Hop.setQuery Takes the current url as the first argument and a dictionary of key, values as second argument. Hop.clearQuery Takes the current url

Call these actions from your views

button [ onClick address (SetQuery (Dict.singleton "color" "red")) ] [ text "Set query" ]

Example

See example app in ./example folder. To run the example app:

  • Clone this repo
  • Run elm reactor
  • Open http://localhost:8000/example/App.elm

Test

elm reactor

Open /localhost:8000/TestRunner.elm

Running in Docker

docker-machine ip name-of-machine

docker-compose build docker-compose up

Open in ip:8000

TODO:

  • ! SetQuery fails when the hash is empty
  • Change hash without changing query
  • Navigate without adding to history
  • Named routes maybe (Using the given action)
  • More tests
  • Docs

Improvements

  • Can router.run be sent to the startApp inputs? i.e. remove the specific port for this.

Changelog

1.1.1 Fixed issue where query string won't be set when no hash wash present

hop's People

Contributors

gdotdesign avatar

Watchers

 avatar James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.