How do you render to root page in Elm? - elm

I am super Elm beginner and struggling with a following thing.
I want to render to root page after I get a HTTP response from server. (Not right after DOM event happens)
I am using Browser.application and trying to goTo function but I don’t know how it can be done.
-- I want to render to root page after I get a HTTP response. (Maybe this Cmd.none can be replaced by some function that renders to root page?)
GotResponse result ->
case result of
Ok response ->
( { model | response = response }, Cmd.none )
Err _ ->
( model, Cmd.none )
Does anybody have any ideas?

You're correct! You'll need to do that with a command.
You probably want to use Browser.Navigation.pushUrl. Here's how it would look in your code:
GotResponse result ->
case result of
Ok response ->
( { model | response = response }
, Browser.Navigation.pushUrl model.key "/"
)
Err _ ->
( model, Cmd.none )
You should have the Key in your model, which is required by the Elm runtime to make sure you're using Browser.application (which is explained here)

Related

onClick message causes unwanted refreshing of the page

I'm trying to build a website in Elm and it includes a couple links to change the language:
div [ class "language" ][ a [ class englishClass, onClick (ChangeLanguage English) ] [ text "EN" ]
, a [ class germanClass, onClick (ChangeLanguage German) ] [ text "DE" ]
, a [ class italianClass, onClick (ChangeLanguage Italian) ] [ text "IT" ]
]
My Update looks like this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
... -- Stuff for URLs
ChangeLanguage language ->
( { model | language = language }
, Cmd.none
)
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url.Url
| ChangeLanguage Language
This is my model:
type alias Model =
{ key : Nav.Key
, url : Url.Url
, language : Language
}
And this is my init function, which (at least in my mind) defaults to English as a language:
init : flags -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init _ url key = ( Model key url English, Cmd.none )
The problem:
Whenever I click the links that change the language, they do seem to work: I see the language of the paragraphs in the page change, but then several unwanted things happen:
The page refreshes. Since I already saw the language change across the page, it's clear this is not needed so I'd like to avoid it.
As a consequence of 1, the viewport is brought all the way to the top of the page again.
The language changes to the default English again!
How could I avoid 1 (and thus 2) from happening?
And what am I missing to make it so the change is maintained across the site (at least when refreshing the page, since I haven't tried working with sessions or cookies yet)?
I consider this behaviour a bug in Elm, and have filed an issue for it, as have others with associated PRs. But in the year and a half since have received no attention from anyone in a position to actually do something about it, which is unfortunately par for the course with Elm.
The problem is that Elm "hijacks" onClick on a elements to create navigation events so that anchors can be handled inside Elm. When using Browser.application, clicking an a element will have Elm call onUrlReuqest with a value of Browser.UrlRequest, which will class the URL as either Internal or External and require you to make a decision on what to do with it.
The problem at the root of this is that omitting href will generate an External UrlRequest with the URL being an empty string. By default, and by the usual handling of External, this will tell the browser to load the URL as usual, thus refreshing the page. But it also suggests a possible workaround is to special-case External "":
update msg model =
case msg of
UrlRequested (Browser.Internal _) ->
( model, Cmd.none )
UrlRequested (Browser.External "") ->
( model, Cmd.none )
UrlRequested (Browser.External url) ->
( model, Browser.Navigation.load url )
UrlChanged _ ->
( model, Cmd.none )
Another workaround is to add href="#" to the a elements, which will correctly classify them as Internal

Sending messages between modules

Can't quite figure out how to go about sending messages between modules when working with reusable components.
I have an expanding text area that I'd like to use on a number of different sections on a site. The Text Area accepts a portion of HTML that makes up the user actions. Typically handling submit, cancel, upload icons, etc..
Tried to write up a quick example of what I'm talking about without throwing a ton of code on here. So essentially, I'd like to just plug and play peices of HTML that are already attached.
I'm assuming CancelNote is getting fired as a TextArea msg, so it never sees a Cancel Note msg. Not sure how I would use Html.map here (or even if I would).....feel like plug and play method is probably a bad approach, but not sure how else I could achieve decent reusability .
SEPERATE MODULE
update model msg =
case msg of
CancelText ->
( { model | note = (Just "") }
, Cmd.none
)
view: stuff
view stuff =
......
TextArea.view
(button [ Html.Events.onClick CancelText] [])
TEXT AREA MODULE
view : Html.Html msg -> Html msg
view actionHtml =
div [ class "extended_text_area_container" ] [
textarea [] [
]
, actionHtml
]
Messages are just values like any other. You can pass them around directly:
-- SEPERATE MODULE
view: stuff
view stuff =
......
TextArea.view CancelText
-- TEXT AREA MODULE
view : msg -> Html msg
view msg =
div [ class "extended_text_area_container" ]
[ textarea [] []
, button [ onClick msg ] []
]
Edit: If you need to also maintain internal state, just use another message to tell the parent to update the state:
-- Main module
type msg =
...
SetTextAreaState TextArea.state
update model msg =
case msg of
...
SetTextAreaState state ->
{ model | textAreaState = state }
view : Model -> Html msg
TextArea.view SetTextAreaState model.textAreaState
-- TextArea module
type State =
...
type Msg =
Clicked
update : State -> Msg -> State
update state msg =
case msg of
Clicked ->
{ state | clicked = True }
view : (State -> msg) -> State -> Html msg
view toMsg state =
let
updateAndWrap msg =
toMsg (update state msg)
in
div [ class "extended_text_area_container" ]
[ textarea [] []
, button [ onClick (updateAndWrap Clicked) ] []
]
Here, instead of passing a msg to onClick directly in TextArea.view, call a function that updates the state and then wraps it in the msg constructor passed in from the parent, which will produce a message of a type that we don't know anything about.
Also, while I use an internal Msg type and update function similarly to the overall Elm architecture, that is in no way mandatory. It's just a nice way of doing it since it's familiar and scales well.

Elm: How to transform Html FooMsg to Html Msg

I'm new to Elm. I have a site with two pages: Home and Signup.
Home has it's own view and Signup has it's own view, but they both return Html Msg. I'd like to change it so that Home returns Html HomeMsg and Signup returns Html SignupMsg.
Writing these view functions is easy of course, but I think my top-level view function needs to transform the result into Html Msg.
Here is the Msg type.
type Msg
= Home HomeMsg
| Signup SignupMsg
| OnLocationChange Location
I think I need some sort of map function to do this like
view : Model -> Html Msg
view model =
case model.route of
Model.HomeRoute ->
map Home (homeView model)
Model.SignupRoute ->
map Signup (signupView model)
Model.NotFoundRoute ->
notFoundView
Yes, there is a map function. It belongs to Html module.
http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html#map
Your code becomes:
view : Model -> Html Msg
view model =
case model.route of
Model.HomeRoute ->
Html.map Home (homeView model)
Model.SignupRoute ->
Html.map Signup (signupView model)
Model.NotFoundRoute ->
notFoundView

How to integrate multiple msg types from different elm features into a top-level app

I have a feature called Editor that I'm trying to plug into my app. Its view returns a the type Html EditorMsg. I plug it in here:
edit : Editor.Types.Model -> Html EditorMsg
edit editorModel =
div [ class "edit" ] [ Editor.view editorModel ]
In the app, I have routing and so edit is called by way of my main view function, plus a couple of functions that manage which route to show:
-- VIEW
view : Model -> Html Msg
view model =
div []
[ RoutingMsg navBar
, EditorMsg (render model)
]
render : Model -> Html EditorMsg
render model =
case model.route of
Nothing ->
li [] [ text "Invalid URL" ]
Just route ->
showRoute route model
showRoute : Route -> Model -> Html EditorMsg
showRoute route model =
case route of
Home ->
home
Editor ->
edit model.editor
My view also contains a navBar as you can see, and that returns Html possibly containing a different type of message, RoutingMsg:
navBar : Html RoutingMsg
navBar =
NavBarState.config
|> NavBarState.items
[ navItem "/" "Home" False
, navItem "/editor" "Edit" False
]
|> NavBar.view
This setup didn't compile because in view I have a list of two different types, one returning Html RoutingMsg and the other Html EditorMsg, so I set up a union type to try to contain that difference:
type Msg
= RoutingMsg (Html RoutingMsg)
| EditorMsg (Html EditorMsg)
This seems to work, but then I run into trouble in my update method, where the matching doesn't quite work. Long and short of it is that it feels as though I've just got the wrong pattern. My goal was to make my Editor somewhat independent, like a module, that can be plugged in different places. But it's hard for me to understand in Elm, how to integrate things.
To illustrate the problem more simply, I created this example on ellie-app that approximates what I tried to do but can't get working: https://ellie-app.com/PKmzsV3PC7a1/1
Is my approach here just incorrect, or if not, how can I get this code to work?
You should use Html.map to map children messages to the top-level Msg
Here's what you've been missing:
view : Model -> Html Msg
view model =
div []
[ Html.map ButtonDecMsg buttonDec
, div [] [ text (toString model) ]
, Html.map ButtonIncMsg buttonInc
]
Also the type annotation definition of child update functions should include the message type:
buttonDecUpdate : ButtonDecMsg -> Model -> Int
buttonDecUpdate msg model =
model - 1
Here is an example of working app: https://ellie-app.com/PM4H2dpFsfa1/0

how to implement loading image when any http request occure

I have implemented the HTTP service request using elm architecture.but i am also want to implement any loading gif image so that user will know that something is happen in the back ground.
can any please help me to implement to this implementation.
You can add a property in your model, whether it is loading or not.
Then you can let your view reflect the status by show spinner or text, or whatever.
In case of the example at http example, you can modify
following code, for example by adding iswaiting property in the Model ...
In this example, FetchSucceed message gets fired when ajax call is complete.
type alias Model =
{ topic : String
, gifUrl : String
, iswaiting : Bool
}
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
MorePlease ->
({ model | iswaiting = True }, getRandomGif model.topic)
FetchSucceed newUrl ->
(Model model.topic newUrl False, Cmd.none)
FetchFail _ ->
(model, Cmd.none)
view : Model -> Html Msg
view model =
div []
[ h2 [] [text model.topic]
, text <| if model.iswaiting then "waiting" else ""
, button [ onClick MorePlease ] [ text "More Please!" ]
, br [] []
, img [src model.gifUrl] []
]
You'll have to adjust other parts as well.
But, I hope you can see what is going on.
You can add a loading gif image on body and make it appear on top of the page by z-index and after http request is completed then delete that image from body.