push better docs

This commit is contained in:
Ryan Haskell-Glatz 2021-03-01 11:53:59 -06:00
parent a9fced3fda
commit 4f350ffcbc
5 changed files with 238 additions and 103 deletions

View File

@ -1,8 +1,8 @@
# Routing
One of the best reasons to use __elm-spa__ is the automatic routing! Inspired by popular JS frameworks like _NuxtJS_, we use file names to determine routes in your application.
One of the best features in __elm-spa__ is the automatic routing system! Inspired by popular JS frameworks, the names of your files determine the routes in your application.
Every __elm-spa__ project will have a `Pages` folder with all the pages in the application.
Every __elm-spa__ project will have a `src/Pages` folder containing all the pages in your app:
URL | File
--- | ---
@ -12,17 +12,17 @@ URL | File
`/about-us` | `src/Pages/AboutUs.elm`
`/settings/users` | `src/Pages/Settings/Users.elm`
In this section, we'll cover the 3 kinds of routes you can find in an __elm-spa__ application.
In this section, we'll cover the different kinds of routes you'll find in every __elm-spa__ application.
## The homepage
The `src/Pages/Home_.elm` is a reserved filename that handles requests to /. The easiest way to make a new homepage is with the [`add` command](/docs/cli#adding-a-homepage) covered in the CLI section:
`Home_.elm` is a reserved filename that handles requests to your homepage. The easiest way to add a new homepage is with the [`elm-spa add`](/docs/cli#elm-spa-add) covered in the CLI docs:
```terminal
elm-spa add /
```
__Note:__ Without the trailing underscore, __elm-spa__ will treat `Home.elm` as a route to `/home`! This is called a "static route", and will be covered next.
> `Home.elm` (without the underscore) is seen as a route to `/home`! To handle requests to the homepage, make sure to include the trailing underscore.
## Static routes
@ -32,9 +32,9 @@ Most pages will be __static routes__, meaning the filepath will translate to a s
elm-spa add /people
```
This command creates a page at `src/Pages/People.elm` that will be shown when the user visits `/people` in your app!
This command creates a file called `People.elm` that will be shown when the user visits `/people` in your application.
These are more examples of static routes:
These are a few more examples of static routes:
URL | File
--- | ---
@ -43,15 +43,23 @@ URL | File
`/about-us` | `src/Pages/AboutUs.elm`
`/settings/users` | `src/Pages/Settings/Users.elm`
### Capitalization matters
Notice how the filename `AboutUs.elm` translated to `/about-us`?
If we named the file `Aboutus.elm` (with a lowercase "u"), then we'd have a path to `/aboutus` (without the dash between words).
> In __elm-spa__, we use "kebab-case" rather than "snake_case" as the convention for separating words.
### Nested static routes
You can use folders to have __nested static routes__:
You can even have __nested static routes__ within folders:
```terminal
elm-spa add /settings/users
```
This example creates a file at `src/Pages/Settings/Users.elm`, which will handle all requests to `/settings/user`. You can nest things multiple levels by creating even more nested folders:
This example creates a file at `Settings/Users.elm`, which will handle all requests to `/settings/user`. This pattern continues, supporting nesting things multiple levels deep:
```terminal
elm-spa add /settings/user/contact
@ -60,47 +68,63 @@ elm-spa add /settings/user/contact
## Dynamic routes
Sometimes a 1:1 mapping is what you need, but other times, its useful to have a route that handles requests to many items.
Sometimes a 1:1 mapping is what you need, but other times, its useful to have a single page that will handles requests to similar URL structures.
A common example is providing a different ID for a blog post, user, or another item in a collection.
```terminal
elm-spa add /people/:name
```
This will create a file at `src/Pages/People/Name_.elm`. In __elm-spa__, this is called a __dynamic route__. It will handle requests to any URLs that match `/people/____` and provide the dynamic part in the parameters.
This creates a file at `People/Name_.elm`. In __elm-spa__, we call this a __dynamic route__. It handles requests to any URLs that match `/people/____` and provides the dynamic bit in the `req.params` value passed into your page!
URL | Params
URL | `req.params`
--- | ---
`/people/ryan` | `{ name = "ryan" }`
`/people/alexa` | `{ name = "alexa" }`
`/people/erik` | `{ name = "erik" }`
The __trailing underscore__ at the end of the filename (`Name_.elm`) indicates that this route is __dynamic__. Without the underscore, it would only handle requests to `/people/name`
> The __underscore__ at the end of the filename (`Name_.elm`) indicates that this route is __dynamic__. Without the underscore, it would only handle requests to a single URL: `/people/name`
The name of the route parameter variable (`name` in this example) is determined by the name of the file! If we named the file `Id_.elm`, the dynamic value would be available at `params.id` instead.
Every page gets access to these dynamic parameters, via the [`Request params`](/docs/pages#requests) value passed in. We'll cover that in the next section!
The name of the `req.params` variable (`name` in this example) is determined by the name of the file! If we named the file `Id_.elm` instead, the dynamic value would be at `req.params.id`.
### Nested dynamic routes
Just like we saw with __nested static routes__, you can use nested folders to create nested dynamic routes!
Just like we saw earlier with nested static routes, you can use nested folders to create __nested dynamic routes__!
```terminal
elm-spa add /users/:name/posts/:id
```
This creates a file at `src/Users/Name_/Posts/Id_.elm`
This creates a file at `src/Users/Name_/Posts/Id_.elm`, which handles any request that matches `/users/___/posts/___`:
URL | Params
URL | `req.params`
--- | ---
`/users/ryan/posts/123` | `{ name = "ryan", id = "123" }`
`/users/alexa/posts/456` | `{ name = "alexa", id = "456" }`
`/users/erik/posts/789` | `{ name = "erik", id = "789" }`
It will handle any request to `/users/___/posts/___`
## Not found page
If a user visits a URL that doesn't have a corresponding page, it will redirect to the `NotFound.elm` page. This is generated for you by default in the `.elm-spa/defaults/Pages` folder. When you are ready to customize it, move it into `src/Pages` and customize it like you would any other page!
If a user visits a URL that doesn't have a corresponding page, it will redirect to the `NotFound.elm` page.
By default, the not found page is generated for you in the `.elm-spa/defaults/Pages` folder. When you are ready to customize your 404 page, move it from the defaults folder into `src/Pages`:
```elm
.elm-spa/
|- defaults/
|- Pages/
|- NotFound.elm
-- move into
src/
|- Pages/
|- NotFound.elm
```
Once you have a `NotFound.elm` within your `src/Pages` folder, __elm-spa__ will stop generating the other one, and use your custom 404 file instead.
The technique of moving a file from the `.elm-spa/defaults` folder is known as "ejecting a default file". Throughout the guide, we'll find more examples of files that we might want to move into our `src` folder.
In __elm-spa__, this technique is called "ejecting" a default file. Throughout the guide, we'll find more default files that we might want to control in our project.

View File

@ -1,49 +1,102 @@
# Shared state
With __elm-spa__, every time you navigate from one page to another, the `init` function for that page is called. This means that the `Model` for the page you we're previously looking at has been cleared out. Most of the time, that's a good thing!
With __elm-spa__, any time we move from one page to another, the `init` function for that new page is called. This means that the state of the previous page you were looking at has been replaced by the new page.
Other times, it makes sense to __share state between pages__! Maybe you have a signed-in user, an API token, or settings like "dark mode" that you want to persist from one page to another. This section of the guide will show you how to do that!
So if we sign in a user at the `SignIn` page, we'll need a place to store the user before navigating over to the `Dashboard`.
This is where the `Shared` module comes in the perfect place to store things that every page needs to access!
### Ejecting the default file
Default files are automatically generated for you in the `.elm-spa/defaults`, and when you need to tweak them, you can move them into your project's `src` folder. This process is known as "ejecting default files", and comes up for advanced features.
By default, an empty `Shared.elm` file is generated for us in `.elm-spa/defaults`. When you are ready to share data between pages move that file from the defaults folder to the `src` folder.
__To get started__ with shared state between pages, move the `.elm-spa/defaults/Shared.elm` file into your `src` folder! After you move that file, `src/Shared.elm` will be the place to make changes!
```elm
.elm-spa/
|- defaults/
|- Shared.elm
The rest of this section walks through the different functions in the `Shared` module, so you know what's going on.
-- move into
src/
|- Shared.elm
```
Once you've done that, `src/Shared.elm` is under your control and __elm-spa__ will stop generating the old one. Let's dive into the different parts of that file!
## Shared.Flags
The first thing you'll see is a `Flags` type exposed from the top of the file. If we need to load some initial data from Javascript when our Elm app starts up, we can pass that data in as flags.
When you have the need to send in initial JSON data, take a look at [Elm's official guide on JS interop](https://guide.elm-lang.org/interop/).
## Shared.Model
## Shared.Msg
By default, our `Model` is just an empty record:
```elm
type alias Model =
{}
```
If we wanted to store a signed-in user, adding it to the model would make it available to all pages:
```elm
type alias Model =
{ user : Maybe User
}
type alias User =
{ name : String
, email : String
, token : String
}
```
As we saw in the [pages guide](/docs/pages), this `Shared.Model` will be passed into every page so we can check if `shared.user` has a value or not!
## Shared.init
```elm
init : Flags -> Request () -> Model -> ( Model, Effect Msg )
init : Flags -> Request -> ( Model, Cmd Msg )
init flags req =
...
```
The `init` function is called when your page loads for the first time. It takes in two inputs:
The `init` function is called when your application loads for the first time. It takes in two inputs:
- `Flags` - initial JSON value passed in from `public/main.js
- `Request ()` - a [Request](/docs/request) value with the current URL information
- `Flags` - initial JS values passed in on startup.
- `Request` - the [Request](/docs/request) value with current URL information.
The `init` function returns the initial `Model`, as well as any `Effect`s you'd like to run (like initial HTTP requests, etc)
The `init` function returns the initial `Shared.Model`, as well as any side effect's you'd like to run (like initial HTTP requests, etc)
__Note:__ The [Effect msg] type is just an alias for `Cmd msg`, but adds support for [elm-program-test]()
## Shared.Msg
Once you become familiar with [the Elm architecture](https://guide.elm-lang.org/architecture/), you'll recognize the `Msg` type as the only way to update `Shared.Model`.
Maybe it looks something like this for our user example
```elm
type Msg
= SignedIn User
| SignedOut
```
These are used in the next section on `Shared.update`!
## Shared.update
```elm
update : Request () -> Msg -> Model -> ( Model, Effect Msg )
update : Request -> Msg -> Model -> ( Model, Cmd Msg )
```
The `update` function allows you to respond when one of your pages or this module send a `Shared.Msg`. Just like pages, you define `Msg` types and handle how they update the shared state here.
The `update` function allows you to respond when one of your pages or this module send a `Shared.Msg`. Just like pages, you define a `Msg` type to handle how they update the shared state here.
## Shared.subscriptions
```elm
subscriptions : Request () -> Model -> Sub Msg
subscriptions : Request -> Model -> Sub Msg
```
If you want all pages to listen for keyboard events, window resizing, or other external updates, this `subscriptions` function is a great place to wire those up! It also has access to the current URL request value, so you can conditionally subscribe to events.
If you want all pages to listen for keyboard events, window resizing, or other external updates, this `subscriptions` function is a great place to wire those up!
It also has access to the current URL request value, so you can conditionally subscribe to events.

View File

@ -1,44 +1,35 @@
# Requests
Every URL that a user visits in your application contains useful information. When __elm-spa__ gets an updated URL, it passes that information to every [Page](/docs/pages) as a `Request` value.
This section of the guide breaks down the [Request](https://package.elm-lang.org/packages/ryannhg/elm-spa/latest/ElmSpa-Request) type exposed by the official Elm package:
Every page in your application gets access to a `Request` value, containing details about the current URL.
```elm
type alias Request params =
{ params : params
, query : Dict String String
, url : Url
, route : Route
, key : Nav.Key
}
page : Shared.Model -> Request -> Page
page shared req =
...
```
## URL Parameters
This might be useful when you need to show the active link in your navbar, or navigate to a different page programmicatically. Let's look at the properties on `req` that you might find useful!
Every request has parameters that you can rely on. If you are on a [dynamic route](/docs/routing#dynamic-routes), you have access to that route's URL parameters:
## req.params
URL | Params
Every [dynamic route](/docs/routing#dynamic-routes) has parameters that you'll want to get access to. For [static routes](/docs/routing@static-routes), those parameters will be `()`:
URL | Request
--- | ---
`/` | `()`
`/about-us` | `()`
`/people/:name` | `{ name : String }`
`/posts/:post/comments/:comment` | `{ post : String, comment : String }`
`/` | `Request`
`/about-us` | `Request`
`/people/:name` | `Request.With { name : String }`
`/posts/:post/users/:user` | `Request.With { post : String, user : String }`
The first two examples from that table are __static routes__, so there are no dynamic parameters available. The last two examples are guaranteed to have values at `req.params`.
Here's an example for a file at `People/Name_.elm`:
All dynamic parameters are `String` types, so feel free to validate them at the page level.
URL | `req.params`
--- | ---
`/people/alexa` | `{ name = "alexa" }`
`/people/erik` | `{ name = "erik" }`
`/people/ryan` | `{ name = "ryan" }`
```elm
greet : Request { name : String } -> String
greet req =
"Hello, " ++ req.params.name ++ "!"
```
__Note:__ When working with [shared state](/docs/shared-state), all requests are `Request ()`.
## Query Parameters
## req.query
For convenience, query parameters are automatically turned into a `Dict String String`, making it easy to handle common query URL parameters like these:
@ -52,9 +43,26 @@ Dict.get "ascending" req.query == Just ""
Dict.get "name" req.query == Nothing
```
__Note:__ If you need ever access to the raw URL query string, you can with the `req.url.query` value!
> If you need ever access to the raw query string, you can with the `req.url.query` value!
## Raw URLs
## req.route
The `req.route` value has the current `Route`, so you can safely check if you are on a specific page.
All the routes generated by __elm-spa__ are available at `Gen.Route`.
```elm
-- "/"
req.route == Gen.Route.Home_
-- "/about-us"
req.route == Gen.Route.AboutUs
-- "/people/ryan"
req.route == Gen.Route.People.Name_ { name = "ryan" }
```
## req.url
If you need the `port`, `hostname`, or anything else it is available at `req.url`, which contains the original [elm/url](https://package.elm-lang.org/packages/elm/url/latest/Url) URL value.
@ -69,22 +77,7 @@ type alias Url =
}
```
This is less common than `req.params` and `req.query`, but can be useful for getting the `hash` at the end of a URL too!
## Getting the current route
The `Request` type also has access to the `Route` value, so you can easily do comparisons against the current route!
```elm
-- "/"
req.route == Gen.Route.Home_
-- "/about-us"
req.route == Gen.Route.AboutUs
-- "/people/ryan"
req.route == Gen.Route.People.Name_ { name = "ryan" }
```
This is less commonly used than `req.params` and `req.query`, but can be useful in certain cases.
## Programmatic Navigation
@ -101,7 +94,7 @@ In that case we store `req.key` in order to use `Request.pushRoute` or `Request.
```elm
type Msg = SignedIn User
update : Request Params -> Msg -> Model -> ( Model, Effect Msg )
update : Request Params -> Msg -> Model -> ( Model, Cmd Msg )
update req msg model =
case msg of
SignedIn user ->
@ -110,4 +103,5 @@ update req msg model =
)
```
When the `SignedIn` message is fired, this code will redirect the user to the `Dashboard` route.
When the `SignedIn` message is fired, this code will redirect the user to the `Dashboard` route.

View File

@ -1,8 +1,27 @@
# Views
With __elm-spa__, you can choose any Elm view library you like. Whether it's
[elm/html](#), [Elm UI](#), or even your own custom library, the `View` module
has got you covered!
With __elm-spa__, you can choose any Elm view library you like. Whether it's [elm/html](https://package.elm-lang.org/packages/elm/html/latest/), [Elm UI](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/), or even your own custom library, the `View` module has you covered!
### Ejecting the default view
If you would like to switch to another UI library you can move the `View.elm` file from `.elm-spa/defaults` into your `src` folder:
```elm
.elm-spa/
|- defaults/
|- View.elm
-- move into
src/
|- View.elm
```
From here on out, __elm-spa__ will use your `View` module as the return type for all `view` functions across your pages!
## View msg
By default, a `View` lets you set the tab title as well as render some `Html` in the `body` value.
```elm
type alias View msg =
@ -11,33 +30,76 @@ type alias View msg =
}
```
By default, a `View` lets you set the tab title as well as render some `Html` in
the `body` value.
### Using Elm UI
### Ejecting the default view
If you wanted to use Elm UI, a popular HTML/CSS alternative in the community, you would tweak this `View msg` type to not use `Html msg`:
TODO
```elm
import Element exposing (Element)
## View msg
type alias View msg =
{ title : String
, element : Element msg
}
```
TODO
## View.toBrowserDocument
TODO
Whichever library you use, Elm needs a way to convert it to a `Browser.Document` type. Make sure to provide this function, so __elm-spa__ can convert your UI at the top level.
Here's an example for Elm UI:
```elm
toBrowserDocument : View msg -> Browser.Document msg
toBrowserDocument view =
{ title = view.title
, body =
[ Element.layout [] view.element
]
}
```
## View.map
TODO
When connecting pages together, __elm-spa__ needs a way to map from one `View msg` to another. For `elm/html`, this is the `Html.map` function.
But when using a different library, you'll need to specify the `map` function for things to work.
Fortunately, most UI libraries ship with their own! Here's another example with Elm UI:
```elm
map : (a -> b) -> View a -> View b
map fn view =
{ title = view.title
, element = Element.map fn view.element
}
```
## View.empty
TODO
When loading between pages, __elm-spa__ also needs a `View.empty` to be specified for your custom `View` type.
For Elm UI, that is just `Element.none`:
```elm
empty : View msg
empty =
{ title = ""
, element = Element.none
}
```
## View.placeholder
TODO
The last thing you need to provide is a `View.placeholder`, used by the __elm-spa add__ command to provide a stubbed out `view` function implementation.
### Defining custom templates
Here's an example of a `placeholder` with Elm UI:
TODO
```elm
placeholder : String -> View msg
placeholder pageName =
{ title = pageName
, element = Element.text pageName
}
```

View File

@ -224,6 +224,7 @@ hr { border: 0; }
color: var(--color--grey-100);
padding: 1rem;
font-size: 0.9em;
font-family: var(--font--monospace);
}
.markdown pre.language-terminal {
@ -253,6 +254,7 @@ hr { border: 0; }
overflow: hidden;
background-color: var(--color--white);
border-color: var(--color--grey-200);
font-size: 0.85em;
}
.markdown tr {