I’m trying to make the transition from Browser.element to Browser.application, but I’m struggling to get started.
In the Guide under Structuring Web Apps it says, “I would have a module for each page, centered around the Model type. Those modules follow The Elm Architecture with the typical Model, init, update, view, and whatever helper functions you need.”
A Model in each module? Does this not conflict with the idea of keeping all state in one place?
In Main.elm we tie the main functions together in a record in a Browser.* function. What would tie them together in a (sub-)module?
Under Navigation it says, " When someone clicks a link, like <a href="/home">Home</a>, it is intercepted as a UrlRequest. So instead of loading new HTML with all the downsides, onUrlRequest creates a message for your update where you can decide exactly what to do next."
But my Browser.element app doesn’t have any links. All of my ‘links’ are of the form onClick or onPress which generate messages, sometimes leading to an new view. How, and where, should I replace them?
The Document type
type alias Document msg =
{ title : String
, body : List (Html msg)
}
Why does body have the type List (Html msg) and not just simply Html msg?
How should we handle this? There is the obvious [header, main, footer] structure, but there must be a more fundamental reason why it’s a list.
I have tried looking through Richard Feldman’s Elm spa Example, but it’s very complex.
Among other things, it makes extensive use of Html.map. But the documentation for Html.map says, “This should not come in handy too often. Definitely read this before deciding if this is what you want.”
(ps this is a broken link).
And the let section of the top level view function has my head spinning.
You start by writing elm-spa new, this will generate the main page located at Pages/Home_.elm. And yes, a page should have its own Model, init, update and view. But i would let elm-spa create the wiring for you.
To add a new “about” page, just write elm-spa add /about. You can even pass values like this: elm-spa add /user/:id.
you use a model for each page (PageA.Model, PageB.Model, …) and then have a main-model in your Main.elm with something like this:
type Model
= PageA PageA.Model
| PageB PageB.Model
so you have all your state here - depending on which page you are showing one of those.
I would recommend you switch to pages with their own URLs - this is much nicer for the users as they can bookmark or share individual URLs to your pages. So have a URL for each page and decide based on the URL which page you should be showing.
Don’t know but having a List is much nicer - at least I always have more than one element inside body so this keeps you from having a dummy-div [] [ ... ] to wrap this up.
Yes Html.map, Cmd.map and maybe Sub.map is needed to push the pageModel : PageA.Models into PageA pageModel : Model to keep then all on the same main-model - this is boilerplate you really circumvent with the type-system Elm gives you when you want to use the obvious and often used page as components with their own Model, Msg, update, view TEA-architecture.
I would expect this to be a list. If body is the html body tag, then I see the value of body as the children of the body tag, which I would expect to be a list.
type alias Model =
{ pageA : PageA.Model
, pageB : PageB.Model
...
// Any other fields your App needs
...
}
This approach is useful if you want to maintain the state of each page, or if you have additional, non-page-specific ‘settings’ that your app utilizes from time to time.
I guess it depends on your need but for my needs (where I want to get to a certain page via a URL and the URL has all the needed initial information to setup that page) this would not work very well and I would not recommend it for an architecture in my teams.
If I need shared state across different pages (I usually do) I add a session : Session field to each pages-model and copy this from the current page to the new on page transitions.
That involves a bit of boiler-plate code but it’s a no-brainer and usually I don’t mind - at least not when working with Elm
How will that not work very well with a Type Alias? It’s exactly what I do without any issues.
That’s not meant as a criticism, just curious. I’m a self-taught hobbyist that’s been using Elm since 0.18, I’ve got a, very close to, 50k loc Elm project I’ve been working on, and like to keep up with the various ‘how-to’ discussions around best practices etc. One such discussion related to preferring flat models.
I started my current project as you have described, with an Enum Type as the Main model, and passed an additional Session type around. Eventually, I found that there was ‘stuff’ being stored on the Session that I didn’t think should really be there, and it was becoming a bit of a dumping ground. Plus I wanted my pages to maintain their state for the life of the App, for this project that makes sense. So I refactored from an Enum Type to a Type Alias on the Main model, and feel that it is a much better design now.
I don’t mean that to sound like “A Type Alias is better than an Enum Type”, not at all, but for my particular use case, it works for me. Maybe if I refactored again - back to using an Enum Type, the design would improve again…
I’d be interested to learn what you have against a Type Alias for the Main Model, though - might be something the OP would be interested in too.
it’s not about the type alias - it’s about having to construct the full tuple (with all models for each page) for/on every requested page/url - so you need some “invalid” or “empty” page-models for all the other pages.
Overall it’s a representable state (all pages at once) that feels a bit invalid to me - I’d rather have the obvious modeling of the model representing exactly the page shown.
IMHO this is just as flat (meaning no more wrappers) then what the tuple approach would give you.
In the end I don’t think there is a objective right or wrong way to do it (or at least I did not really bother to try and come up with some objective measure of “good” architecture and then went to compare the to approaches against each other) - it’s just what I prefer and the reasoning is more or less what I would give a peer that would ask me my opinion.