Open mewa opened 6 years ago
This looks like we have the same issue https://github.com/rtfeldman/elm-spa-example/issues/41
You can just define a SetRoute
Msg in the page where you want to change the route, and use Route.modifyUrl
.
Yes, that's what I've meant by introducing redundancy.
There are pages that don't contain any logic and you'd have to implement all the updates and messages just for this purpose.
Ah, I see the crux of the issue now. You want to allow the user to change the route from a reused section of the page, e.g. the navbar in the header. The basic problem is that Views.Page.frame
assumes all the msgs in the content are going to be page-level msgs, whereas SetRoute
is a top-level msg and it would be really nice to be able to use it directly. A secondary problem is how to get SetRoute
into Views.Page
since it's a separate module from Main
.
On the first problem, one solution is to rewrite frame
in terms of the top-level msg type, and Html.map
the content inside frame
, instead of doing it in Main.viewPage
. This means passing in the page-to-top-level msg constructors (for example, HomeMsg
, SettingsMsg
, etc.):
frame : (contentMsg-> msg) -> Bool -> Maybe User -> ActivePage -> Html contentMsg -> Html msg
frame tagger isLoading user page content =
div [ class "page-frame" ]
[ viewHeader page user isLoading
, content |> Html.map tagger
, viewFooter
]
Now, viewHeader
and viewFooter
generate Html Main.Msg
directly, whereas the content has to be Html.map
'd to get it to Html Main.Msg
. This opens up the possibility for the navbar in viewHeader
to issue top-level SetRoute
msgs directly.
So then second problem becomes how to get SetRoute
inside the header.
You could use essentially the same technique, which is also layed out in the Elm Guide on reusability: inject a msg constructor function, in this case SetRoute
*:
frame : (contentMsg-> msg) -> (Route -> msg) -> Bool -> Maybe User -> ActivePage -> Html contentMsg -> Html msg
frame tagger setRoute isLoading user page content =
div [ class "page-frame" ]
[ viewHeader setRoute page user isLoading
, content |> Html.map tagger
, viewFooter
]
* Or to be precise, Just >> SetRoute
since SetRoute is Maybe Route -> Msg
.
Then you can pass this setRoute
function down into navBarLink
and use it in your link click handler.
I haven't tested this, but in principle something like this should work.
I've had to deal with the same problem a couple of weeks ago.
@ericgj Your solution is great when we would only need to send messages from the layout.
@mewa @ivanceras In the case of wanting to do path based routing, we need to do be able to send a "new url" message from every page, using a click handler with preventDefault. The solution I used is using Html Msg everywhere instead of using Html.map and calling the view with for example Html LoginMsg.
For more info about this, see the NoMap way of doing parent-child communication: https://medium.com/@_rchaves_/child-parent-communication-in-elm-outmsg-vs-translator-vs-nomap-patterns-f51b2a25ecb1
Keep in mind, this requires a big rewrite.
I'm still not too happy about the way it looks now, but it allows me to send global messages from everywhere in my app. If someone has a better architecture for this problem, would love to know more.
@mewa I didn't run into these issues with dwayne/elm-conduit even though I'm using path-based routing. I think one of the problems with the HTML provided by RealWorld is that it uses links where buttons would have been more appropriate. In my implementation I use buttons where it calls for button semantics. Using preventDefault
on a link works as suggested by @nimrev but it's a code smell and indicates you're probably using a link in place of a button.
@nimrev I'm using a form of child-parent communication that I'm calling "the dispatch pattern". See if that approach works better for you.
Current approach works well when you have hash-based routing.
However if you want to use path-based routing, you'd have to prevent default link behaviour in order to avoid loading the page every time, which in turn requires either:
SetRoute
message of the root module somehow orHomeMsg
and the like are wrapping the other sub-messages at the moment)