What does pipe `|` operator do in case expression of elm-lang? - elm

I have this following code snippet in my Elm code:
type alias Model =
{ content : String
}
update : Msg -> Model -> Model
update msg model =
case msg of
Change newContent ->
{ model | content = newContent }
What does { model | content = newContent } do?
Does it assign (bind) the value of newContent to model as well as content? Is that why the | operator is placed there?

The pipe is not a part of the case expression. It's record update syntax, as described here: https://elm-lang.org/docs/records#updating-records.
{ model | content = newContent }
assigns the value of newContent to the content field in the model record.

Read | as 'with'.
{ model 'with' content (set to) = newContent }

Related

How to trigger Cmd from view in Elm

I'm new to Elm and I ran into this problem...
We get translations for our page using something like:
case (translate translation.id) of
Success: -> translation
Failure: -> translation.id
Where translate just finds translation.id in a dictionary and it may or may not be there.
There are no runtime errors because you get a string either way, but we would like to log the missing translation to a rest service logger. But Elm hates side effects in the view that doesn't stem from html events so I'm not sure how to handle this.
Obviously in regular JS you could just crowbar in a fetch inside the failure case block and then return a string afterwards but that doesn't seem to be possible in Elm?
You need to move the effects part of your code into the update function. In this case I suggest doing it when the translation arrives. There you will want something like
OnTranslation translation ->
( { model | translation = translation } -- attach to model
, case translate translation.id of
Ok _ ->
Cmd.none
Err err ->
-- register with the error logger
logMissingTranslation translation.id
)
#Odin Thorsen, use Html.node and Html.Attributes.attribute to reference a custom element in Elm:
-- VIEW
view : Model -> Html.Html ()
view model =
Html.div []
[ translation model "key"
, translation model "junk key"
]
translation : Model -> String -> Html.Html ()
translation { translations } id =
Html.node "translated-text"
[ Html.Attributes.attribute "translation-id" id
, Html.Attributes.attribute "translation-text" <| translate translations id
]
[]
translate : Dict.Dict String String -> String -> String
translate translations id =
case Dict.get id translations of
Just value ->
value
Nothing ->
""
And in Javascript, use customElements.define to create the custom element:
customElements.define("translated-text", class extends HTMLElement {
constructor() { super(); }
connectedCallback() { }
attributeChangedCallback() { this.setTextAndLogFailure(); }
static get observedAttributes() { return ["translation-id", "translation-text"]; }
setTextAndLogFailure() {
var id = this.getAttribute("translation-id");
var text = this.getAttribute("translation-text");
if (text === null) return;
this.textContent = text;
if (!text.length) alert("Unkown translation id: " + id);
}
});
You'd replace alert("Unkown translation id: " + id); with the logging fetch.
Here is an Ellie with a full solution.

How to update specific element in a list in Elm

There are couple apples ( in type of List ) which will expose themselvies in the web view. User can update any size attribute of an Apple. I have a msg type UpdateSize which will be triggered via onInput.
Editing any of the apples will only just trigger the message without knowing which apple to be updated.
Is that possible to pass an id attribute to UpdateSize message?
Thank you for reading this, Elm is great !
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick,onInput)
import String
type alias Apple = {
size: Int}
type alias Model = {
apples: List(Apple)}
initialModel : Model
initialModel =
{ apples = [ Apple 10, Apple 11, Apple 12] }
type Msg
= UpdateSize String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateSize s -> {model | apples = ??? } -- how to update a single Apple with new size
_ -> model
viewApple : Apple -> Html Msg
viewApple a =
input [ type_ "text" ,placeholder ""
, value (String.fromInt a.size)
, onInput UpdateSize]
[]
view : Model -> Html Msg
view model =
div []
(List.map viewApple model.apples)
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
Code link: https://ellie-app.com/ghd9jrcjKQQa1
With your current implementation it's not possible to know which apple to update since there's no unique attribute about the apples. What if two apples have the same size? If would be better if apples had IDs, or you used a dictionary type to keep track of the apples.
However, for the sake of demonstration, you could say that the list indeces of the apples are unique and you find them accordingly. In real life this will be a fragile solution.
Here's a naive approach using some helper functions from List.Extra.
-- ...
type alias Size =
Int
type Msg
= UpdateSize Int String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateSize index sizeStr ->
let
maybeSize =
String.toInt sizeStr
in
maybeSize
|> Maybe.withDefault (\size -> { model | apples = updateApple index size model.apples })
|> model
_ ->
model
updateApple : Int -> Size -> List Apple -> List Apple
updateApple index size apples =
let
maybeApple =
List.Extra.getAt index apples
in
maybeApple
|> Maybe.map (\apple -> List.Extra.setAt index { apple | size = size } apples)
|> Maybe.withDefault apples
-- ...
viewApple : Int -> Apple -> Html Msg
viewApple index a =
input
[ type_ "text"
, placeholder ""
, value (String.fromInt a.size)
, onInput (UpdateSize index)
]
[]
view : Model -> Html Msg
view model =
div []
(List.indexedMap viewApple model.apples)

This `div` call produces: Html (String -> Msg) But the type annotation on `view` says it should be: Html Msg

I'm currently learning elm, I just stumbled on this problem where the div returns a Html (String -> Msg) instead of Html Msg.
error message I'm receiving
This div call produces:
Html (String -> Msg)
But the type annotation on view says it should be:
Html Msg
type alias Model =
{
firstNum: String,
secondNum: String,
answer: String
}
init: Model
init = { firstNum = "",
secondNum = "",
answer = ""}
type Msg =
Add String| Minus String
update: Msg -> Model -> Model
update msg model =
case msg of
Add x -> { model | answer = x}
Minus y -> { model | answer = y}
view : Model -> Html Msg
view model =
div []
[
input [ placeholder "Text to reverse", value model.firstNum] [],
button [onClick Add] [text "add"],
div [] [text model.answer]
]
main =
Browser.sandbox
{ init = init,
update = update,
view = view
}
You define the Msg type as
type Msg =
Add String| Minus String
with Add taking a String argument, but when you use it here:
button [onClick Add] [text "add"],
you're not giving it any argument at all.
The underlying issue seems to be that your mental model of the Elm Architecture is wrong. You seem to consider messages as "operations" or function calls rather than events, where Add is a function that takes an argument to apply to the model.
You should instead consider a message as a description of what triggered it. Instead of Add String, you might call it AddButtonClicked, with no arguments (in this case). Then have the update function do what it should based on what's in the model alone, which I'm guessing is an arithmetic operation on firstNum and secondNum.
But you're also not populating those fields. To do so you need to use the onInput event, which does ask for a message that takes a String. You might add a new message FirstNumChanged String for example, then use it with input like this:
input [ placeholder "Text to reverse", onInput FirstNumChanged, value model.firstNum] [],
I'll leave it to you to figure out how to handle it in update.

Update record field from Collection

I am playing a little bit with Elm these days, but I stuck with a simple case where I want to update a record field. My code is like this:
-- MODEL
initialModel : Model
initialModel =
{ selectedLanguage = "german"
, allCards = Card.cards
}
type alias Msg =
{ description : String
, data : String
, id : String
}
The update function
update : Msg -> Model -> Model
update msg model =
case List.head (model.allCards) of
Just card ->
{ card | fliped = True }
Nothing -> model
but I see this:
Something is off with the 1st branch of this `case` expression:
50| { card | fliped = True }
^^^^^^^^^^^^^^^^^^^^^^^^
The 1st branch is a record of type:
{ back : String, fliped : Bool, front : String, id : String }
But the type annotation on `update` says it should be:
Model
Hint: Seems like a record field typo. Maybe back should be allCards?
Hint: Can more type annotations be added? Type annotations always help me give
more specific messages, and I think they could help a lot in this case!
Detected errors in 1 module.
I think I should always return a model from update function like my type says, but cannot figure out how. Any advice here?
You'll have update the allCards field of model too. You can nest the card update inside the model update if the former returns a list instead of just a single card:
update : Msg -> Model -> Model
update msg model =
{ model
| allCards =
case model.allCards of
card :: rest ->
{ card | fliped = True } :: rest
[] ->
[]
}
Or you can bind the new allCards to a name if you prefer:
update : Msg -> Model -> Model
update msg model =
let
newAllCards =
case model.allCards of
card :: rest ->
{ card | fliped = True } :: rest
[] ->
[]
in
{ model | allCards = newAllCards }
I pattern match directly on the list here instead of using List.head, as that also gives me the remainder of the list and I don't have to deal with an intermediary Maybe value (or two actually, since List.tail returns a Maybe as well). The card::rest branch hits if allCards contains at least one card, so the only remaining case is therefore [], which is easy enough to handle.
Also, flipped is spelled with two ps ;)

How can the `Msg` type be separated into many types in Elm?

The standard way to use model and update in Elm is to define the Model and Msg types, and the update function:
type alias Model = { ... }
type Msg = Msg1 | Msg2 | ...
update : Msg -> Model -> (Model, Cmd Msg)
...
When the application grows, all those types and functions become more complex. I'd like to separate them in the following way:
type alias Model1 = { ... }
type alias Model2 = { ... }
type alias Model = { model1 : Model1, model2 : Model2 }
type Msg1 = Msg1a | Msg1b | ...
type Msg2 = Msg2a | Msg2b | ...
type Msg = M1 Msg1 | M2 Msg2 | ...
Then, I'd like to handle all of them separately (and I know how to do it).
I have a problem with the view function, though. I define my view as follows:
view : Model -> Html Msg
view model =
let v1 = view1 model.model1
...
in ...
view1 : Model1 -> Html Msg1
view1 model = ...
The problem is, the result of view1 is Html Msg1, and the view function needs Html Msg.
Is there a way to convert the result from Html Msg1 to Html Msg?
You're looking for Html.map:
view : Model -> Html Msg
view model =
let v1 = view1 model.model1 |> Html.map M1
v2 = view2 model.model2 |> Html.map M2
in ...