Skip to content

Commit

Permalink
Update from Elm 0.18's Navigation.program to Elm 0.19's Browser.appli…
Browse files Browse the repository at this point in the history
…cation

* adapt to the new Browser.application API in the following ways:

  * mirror the two-phase handling of URL changes in
    Browser.application's 'onUrlRequest' and 'onUrlChange' by
    bifurcating the RouterMsg variant of WrappedMsg into
    RouterMsgOnUrlChange and RouterMsgOnUrlRequestInternal variants.

  * add a slot to RouterModel to store the new Browser.Navigation.Key,
    and in the update function, use it to invoke
    Browser.Navigation.pushUrl in response to a urlRequestInternal.

  * create a new 'onExternalUrlRequest' function for the user to
    implement, since RouteUrl can handle internal requests for the
    user, but can't do anything sensible with external requests (as
    suggested by @basti1302).

  * eliminate the distinction between App and AppWithFlags, and all
    related duplication, as there is no variant of the new
    Browser.application that doesn't support flags.

* make UrlChange more strongly typed, mirroring the structure of the
  Url.Url record type from elm/url, and rework the way UrlChanges are
  converted to Cmds with a new 'apply : Url -> UrlChange -> Url'
  function.

* update all examples to work with the new API and 0.19 generally

  * include work-arounds for a couple of elm/url bugs
    (elm/url#37 and
    elm/url#17)

  * store the base path in ExampleViewer.Model to illustrate absolute
    path requirement of UrlChange

  * build the examples with '--debug' so users can get an idea for how
    they work under the hood

* update README

  * remove references to complementary packages that aren't compatible
    with 0.19 (which is all of them)

* remove the RouteUrl.Builder module and the use of the sporto/erl
  package, as this functionality is now largely provided by elm/url.

* remove the older RouteHash module, as it was only present to ease
  the transition from elm-route-hash to elm-route-url.  also remove
  example code illustrating its use.
  • Loading branch information
jerith666 committed Jul 28, 2020
1 parent 4e8247d commit 2b43d1b
Show file tree
Hide file tree
Showing 31 changed files with 1,120 additions and 2,525 deletions.
75 changes: 23 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# elm-route-url

This is a module for routing single-page-apps in Elm, building on the
[`elm-lang/navigation`](http://package.elm-lang.org/packages/elm-lang/navigation/latest)
[`elm/browser`](https://package.elm-lang.org/packages/elm/browser/latest)
package.

## Rationale
Expand All @@ -18,30 +18,18 @@ So, there are two things going on here:
* Mapping changes in the browser's location to changes in our app's state.

Now, you can already arrange for these things to happen using
[`elm-lang/navigation`](http://package.elm-lang.org/packages/elm-lang/navigation/latest).
Furthermore, there are already a wealth of complementary packages,
such as:

* [evancz/url-parser](http://package.elm-lang.org/packages/evancz/url-parser/latest)
* [Bogdanp/elm-combine](http://package.elm-lang.org/packages/Bogdanp/elm-combine/latest)
* [Bogdanp/elm-route](http://package.elm-lang.org/packages/Bogdanp/elm-route/latest)
* [etaque/elm-route-parser](http://package.elm-lang.org/packages/etaque/elm-route-parser/latest)
* [poyang/elm-router](http://package.elm-lang.org/packages/poying/elm-router/latest)
* [pzingg/elm-navigation-extra](http://package.elm-lang.org/packages/pzingg/elm-navigation-extra/latest)
* [sporto/erl](http://package.elm-lang.org/packages/sporto/erl/latest)
* [sporto/hop](http://package.elm-lang.org/packages/sporto/hop/latest)

[`elm/browser`](http://package.elm-lang.org/packages/elm/browser/latest).
So, what does elm-route-url do differently than the others? First, I'll
address this practically, then philosophically.


### Mapping changes in the app state to a possible location change

If you were using [`elm-lang/navigation`](http://package.elm-lang.org/packages/elm-lang/navigation/latest)
If you were using [`elm/browser`](https://package.elm-lang.org/packages/elm/browser/latest)
directly, then you would make changes to the URL with ordinary commands.
So, as you write your `update` function, you would possibly return a command,
using [`modifyUrl`](http://package.elm-lang.org/packages/elm-lang/navigation/1.0.0/Navigation#modifyUrl)
or [`newUrl`](http://package.elm-lang.org/packages/elm-lang/navigation/1.0.0/Navigation#newUrl).
using [`replaceUrl`](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Navigation#replaceUrl)
or [`pushUrl`](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Navigation#pushUrl).

Now, you can make this work, of course. However, the `update` function isn't
really the perfect place to do this. Your update function looks like this:
Expand Down Expand Up @@ -89,11 +77,13 @@ use elm-route-url, you don't have to.

### Mapping location changes to messages our app can respond to

If you use the official [navigation](http://package.elm-lang.org/packages/elm-lang/navigation/latest)
package in Elm 0.18 directly, you react to location changes by providing
an argument to `Navigation.program` which converts a `Location` to a message
your app can deal with. Those messages are then fed into your `update` function
as the `Location` changes.
If you use the official [browser](https://package.elm-lang.org/packages/elm/browser/latest/)
package in Elm 0.19 directly, you react to location changes by providing
two arguments to `Browser.application`: `onUrlRequest`, which converts a
`UrlRequest` to a message your app can use to decide whether to permit the
requested URL change; and `onUrlChange`, which converts a new `Url` to a
message your app can deal with. Those messages are then fed into your
`update` function as the `Url` changes.

On the surface, elm-route-url works in a similar manner, except that it
asks you to implement a function which returns a list of messages.
Expand All @@ -108,8 +98,8 @@ location2messages : Location -> List Message
`location2messages` will also be called when your `init` function is invoked,
so you will also get access to the very first `Location`.

So, that is similar to how `Navigation` works. The difference is that
`Navigation` will send you a message even when you programmatically change
So, that is similar to how `Browser` works. The difference is that
`Browser` will send you a message even when you programmatically change
the URL. By contrast, elm-route-url only sends you messsages for **external**
changes to the URL -- for instance, the user clicking on a link, opening
a bookmark, or typing in the address bar. You won't get a message when you've
Expand All @@ -133,40 +123,21 @@ by Amitai Burstein, under the heading

## API

For the detailed API, see the documentation for `RouteUrl` and `RouteHash`
(there are links to the right, if you're looking at the Elm package site).

The `RouteUrl` module is now the "primary" module. It gives you access to the
whole `Location` object, and allows you to use the path, query and/or hash, as
you wish.
For the detailed API, see the documentation for `RouteUrl` (there's a link to
the right, if you're looking at the Elm package site).

The main thing that elm-route-url handles is making sure that your
`location2messages` and `delta2url` functions are called at the appropriate
moment. How you parse the `Location` (and construct a `UrlChange`) is pretty
much up to you. Now, I have included a `RouteUrl.Builder` module that could
help with those tasks. However, you don't need to use it -- many other
approaches would be possible, and there are links to helpful packages above.
For my own part, I've been using [evancz/url-parser](http://package.elm-lang.org/packages/evancz/url-parser/latest)
recently to implement `location2messages`.

The `RouteHash` module attempts to match the old API of elm-route-hash as
closely as possible. You should be able to re-use your old `delta2update` and
`location2action` functions without any changes. What will need to change is
the code in your `main` module that initializes your app. The `RouteHash`
module will probably be removed in a future version of elm-route-url, so you
should migrate to using `RouteUrl` at an appropriate moment.
moment. How you parse the `Url` (and construct a `UrlChange`) is pretty
much up to you. You can use [`elm/url`](https://package.elm-lang.org/packages/elm/url/latest/)
to help with those tasks.


## Examples

I've included [example code](https://github.com/rgrempel/elm-route-hash/tree/master/examples/elm-architecture-tutorial)
which turns the old Elm Architecture Tutorial (upgraded to Elm 0.18) into
a single-page app. I've included three variations:

* Using the new `RouteUrl` API with the full path.
* Using the new `RouteUrl` API with the hash only.
* Using the old `RouteHash` API.
which turns the old Elm Architecture Tutorial (upgraded to Elm 0.19) into
a single-page app. I've included two variations:

Note that the example code makes heavy use of the `RouteUrl.Builder` module.
However, as noted above, you don't necessarily need to use that -- a variety
of alternative approaches are possible.
* Using the `RouteUrl` API with the full path.
* Using the `RouteUrl` API with the hash only.
23 changes: 0 additions & 23 deletions elm-package.json

This file was deleted.

21 changes: 21 additions & 0 deletions elm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"type": "package",
"name": "rgrempel/elm-route-url",
"summary": "Router for single-page-apps in Elm",
"license": "MIT",
"version": "5.0.0",
"exposed-modules": [
"RouteUrl"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"ccapndave/elm-update-extra": "4.0.0 <= v < 5.0.0",
"elm/browser": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "1.0.0 <= v < 2.0.0",
"elm/http": "1.0.0 <= v < 2.0.0",
"elm/regex": "1.0.0 <= v < 2.0.0",
"elm/url": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
}
91 changes: 31 additions & 60 deletions examples/elm-architecture-tutorial/Example1/Counter.elm
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ module Example1.Counter exposing (..)
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (onClick)
import RouteHash exposing (HashUpdate)
import RouteUrl.Builder exposing (Builder, builder, path, replacePath)
import RouteUrl exposing (HistoryEntry(..), UrlChange(..))
import String exposing (toInt)
import Url exposing (Url)



-- MODEL
Expand Down Expand Up @@ -56,20 +57,19 @@ view : Model -> Html Action
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [ countStyle ] [ text (toString model) ]
, div countStyle [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]


countStyle : Attribute any
countStyle : List (Attribute any)
countStyle =
style
[ ( "font-size", "20px" )
, ( "font-family", "monospace" )
, ( "display", "inline-block" )
, ( "width", "50px" )
, ( "text-align", "center" )
]
[ style "font-size" "20px"
, style "font-family" "monospace"
, style "display" "inline-block"
, style "width" "50px"
, style "text-align" "center"
]


{-| We add a separate function to get a title, which the ExampleViewer uses to
Expand All @@ -83,61 +83,32 @@ title =



-- Routing (Old API)
-- Routing (New API)


{-| For delta2update, we provide our state as the value for the URL.
-}
delta2update : Model -> Model -> Maybe HashUpdate
delta2update previous current =
Just <|
RouteHash.set [ toString current ]

delta2builder : Model -> Model -> Maybe UrlChange
delta2builder previous current =
Just <| NewPath NewEntry <| { path = String.fromInt current, query = Nothing, fragment = Nothing }

{-| For location2action, we generate an action that will restore our state.
-}
location2action : List String -> List Action
location2action list =
case list of
first :: rest ->
case toInt first of
Ok value ->
[ Set value ]

Err _ ->
-- If it wasn't an integer, then no action ... we could
-- show an error instead, of course.
[]

_ ->
-- If nothing provided for this part of the URL, return empty list
builder2messages : (Url -> Maybe String) -> Url -> List Action
builder2messages extractPath url =
case extractPath url of
Nothing ->
[]

Just path ->
case String.split "/" path of
first :: rest ->
case toInt first of
Just value ->
[ Set value ]

Nothing ->
-- If it wasn't an integer, then no action ... we could
-- show an error instead, of course.
[]

-- Routing (New API)


delta2builder : Model -> Model -> Maybe Builder
delta2builder previous current =
builder
|> replacePath [ toString current ]
|> Just


builder2messages : Builder -> List Action
builder2messages builder =
case path builder of
first :: rest ->
case toInt first of
Ok value ->
[ Set value ]

Err _ ->
-- If it wasn't an integer, then no action ... we could
-- show an error instead, of course.
_ ->
-- If nothing provided for this part of the URL, return empty list
[]

_ ->
-- If nothing provided for this part of the URL, return empty list
[]
Loading

0 comments on commit 2b43d1b

Please sign in to comment.