elm / url

Build and parse URLs. Useful for HTTP and "routing" in single-page apps (SPAs)
https://package.elm-lang.org/packages/elm/url/latest/
BSD 3-Clause "New" or "Revised" License
74 stars 43 forks source link

Support Hash-Routing (again) #24

Open andys8 opened 5 years ago

andys8 commented 5 years ago

Issue

evancz/url-parser had parseHash. It was removed.

In my case "Url-Routing" is not an option, because the http server is not under my control and will issue 404.

Parsing a url like http://localhost:3000/#/mydata/abe8e05f-e58a-4bfc-81e8-2081c7638407 is hacky. There is fragment. It returns Maybe String. There is no way to parse String with the library. Therefore the workaround shown is working, but not what anybody would want.

Are there alternatives without adding the functionality? If not, can we support hash routing again?

Workaround

parseUrl : Url -> Maybe Route
parseUrl url =
    UrlParser.parse parseRoute (urlFragmentToPath url)

urlFragmentToPath : Url -> Url
urlFragmentToPath url =
    { url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
Erudition commented 5 years ago

I have the same problem: I may eventually have a server that can redirect the url path back to elm, but until then, I'd like to use a hash to be able to keep my url stuff (including it's own fragment!) without redirecting to a 404 page.

But from there. I'd like to keep using my normal path parsers like normal!

In that light, you may be interested in the function I wrote for this purpose:

{-| This dense function lets us pretend we have a server redirecting sub-urls (like app/task/57) to our app, even when we don't (like when running as a simple local file). Simply insert a "#" in the address bar, right before the path that gets passed into our app (inc. query&fragment) and this function will make it disappear before our app parses its url.

Example: `http://localhost:8000/www/index.html#/sub/path?hey=there#yo`

is normally parsed as
`url: { fragment = Just "/sub/path?hey=there#yo", host = "localhost", path = "/www/index.html", port_ = Just 8000, protocol = Http, query = Nothing }`

but with this function we can pretend it was:
`url: { fragment = Just "yo", host = "localhost", path = "/www/index.html/sub/path", port_ = Just 8000, protocol = Http, query = Just "hey=there" }`

even though that path may have resulted in a 404 on any host without fancy redirection set up (such as the development environment). Sweet!

-}
bypassFakeFragment : Url.Url -> Url.Url
bypassFakeFragment url =
    case Maybe.map String.uncons url.fragment of
        -- only if "#" is immediately followed by a "/" (path)
        Just (Just ( '/', fakeFragment )) ->
            -- take the url and drop the first "#", then re-parse it
            case String.split "#" (Url.toString url) of
                front :: _ ->
                    -- Url.fromString can fail, but it shouldn't here
                    Maybe.withDefault url <|
                        -- include all the rest (even later "#"s)
                        Url.fromString (front ++ "/" ++ fakeFragment)

                _ ->
                    url

        _ ->
            url

So stick that sucker in right before your Url parser!

Then, when you want to use a URL as it would be with proper server redirection, just stick a hash after the filename. The path that follows will be treated as the path!