I’m working on my 2nd app based of rtfeldman/elm-spa-example. It’s a wonderful way to get started and I’m pretty happy how the 1st one turned out. This time I got stuck.
I apologize in advance for assuming familiarity with elm-spa-example. I’m a new user around here so I can’t put in too many links so I’ll try to stick to the most relevant ones
I have a few requirements that require interactivity in my menu bars. For this I need Html Msg in https://github.com/rtfeldman/elm-spa-example/blob/master/src/Page.elm#L42 and handle those in Main.elm. I’ve been failing at getting it to work while keeping the design, which I’m quite fond of, the same.
Tried defining Msg in Page.elm directly but I run into the same problem as my first attempt.
I’m now trying to pull out navigation into a separate module similar to viewErrors in Page.elm and I think that would work. Not sure if I’m liking it though.
I’m a little worried about every Page having to implement/provide all the messages that I’ll need which wouldn’t be too bad I guess. What bothers me more is having to include navigation in the view of every Page, losing the nice encapsulation provided by Page.elm and viewPage in Main.elm
Has anyone tried something similar and found a nice elegant way to handle this? Am I just overlooking something basic?
Why does it need to be handled in Main.elm? I think the trick might be handling it inside each page module instead.
I think this approach might work. In Richard’s SPA example, see how theres a Session type passed between every page? Thats kind of like a global state of information that persists between all the pages. When the user goes from page to page, the Session is just taken out of the current page and given to the other. If you have a complex menu bar, that has its own persistent global state like that, try doing the same thing. Encapsulating your MenuBar in a module, with its own Model, Msg, and view, and then have every page contain one of these MenuBar.Models in it. When you move from page to page, take the MenuBar.Model out of the current page, and initialize the next page with it.
That totally worked. I did end up having to call the menu bar in the view of every page and while I wanted to avoid that, it turned out not too bad really.
I haven’t made an Elm SPA yet (currently have Elm living in separate pages in an AngularJs SPA) but this method of passing Session from page to page seems a little odd.
Why not just have a global model like this?
type alias Model =
{ session : Session
, page : Page
, menuBar : MenuBar.Model
, otherGlobalStuff : Stuff
}
And have your top view be like :
view : Model -> Html Msg
view model = div []
[ MenuBar.view MenuBar.model
, case model.page of
Home pageModel ->
Home.view model pageModel
etc...
]
Just seems like the most intuitive way to do it, and you wouldn’t have to call menu, footer and such on each page.
This top view would also handle showing a login page if the user is not logged in and the current route requires login etc.
When I came across this, I moved the messages and update for the Dropdown into a module that I could include in both Page.elm and Main.elm which mean’t I could deal with the dropdown messages in the update function in Main.elm.
Unfortunately the codebase isn’t open source so I’ve extracted some parts of it into a gist which (hopefully) gives you enough to see what I’ve done…
I think theres a straightforward answer to your question.
Firstly, I think your code snippet is a pretty good default approach to the situation. There are two complications however, that I think should move you away from that default:
Pages usually needs stuff in the session. In my projects, I put information like the backend’s url, and the window dimensions in things like Session. At the very least, Session needs to get passed into view and update functions.
Pages often need to edit the information in Session. A login page, for example, will login, and subsequently want to add an authentication token, or a user to the Session. I usually store Random.Seed in my Session types, and so I need to consume and update the Random.Seed in the Session from within a page. Somehow changes to Session need to get communicated out of the child update function…
type OutMsg
= UpdateUser User
| UpdateSeed Random.Seed
update : Session -> Msg -> Home.Model -> (Home.Model, Home.Msg, OutMsg)
Both of those work, but they can also get quite clunky. You need to deliberately pass out a value in every case of the child update function, and then the value needs to be handled on the outside. Its a lot of wiring, and it starts to feel like the modular separation of the Session and Page.Model is quite larger than the practical Separation they actually have in your software (especially if there are multiple layers below the Page).
At some scale, it becomes cheaper to have pages hand off the Session when they transition between each other, than it is for parents to hand off the Session to the child. In part because page transitions are less frequent that Msg.