Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Path-based routing #42

Open
mewa opened this issue Mar 16, 2018 · 6 comments
Open

Path-based routing #42

mewa opened this issue Mar 16, 2018 · 6 comments

Comments

@mewa
Copy link

mewa commented Mar 16, 2018

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:

  1. breaking modularity, since you'd have to supply the SetRoute message of the root module somehow or
  2. introducing redundant branches that handle route changes on sub-components (much like the HomeMsg and the like are wrapping the other sub-messages at the moment)
@ivanceras
Copy link

This looks like we have the same issue #41

@ericgj
Copy link

ericgj commented Mar 17, 2018

You can just define a SetRoute Msg in the page where you want to change the route, and use Route.modifyUrl.

See: http://faq.elm-community.org/#how-do-i-navigate-to-a-new-route-from-within-a-nested-view-for-example-from-a-page-view-rather-than-the-top-level-of-my-app

@mewa
Copy link
Author

mewa commented Mar 18, 2018

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.

@ericgj
Copy link

ericgj commented Mar 18, 2018

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.

@nimrev
Copy link

nimrev commented Mar 24, 2018

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.

@dwayne
Copy link

dwayne commented Apr 23, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants