I am writing a small program that handles and displays guitar chords.
But I got stuck: I don't know how to convert an Int value into Html.
My little render function looks like:
renderGuitarString : GuitarString -> Html Msg
renderGuitarString guitarString =
div [ class "string" ] --here I don't know what to do
and:
view : Model -> Html Msg
view model =
div [] (List.map renderGuitarString model.guitarStrings)
just for the complete picture, my types and my model:
type alias GuitarString =
{ number : Int
, frets : List Fret
}
and:
type alias Fret =
{ number : Int
, tone : ( String, Int )
}
and:
type alias Model =
{ guitarStrings : List GuitarString
}
I want to transform the Fret number value into real Html.
thanks for help!
For me, toString did not work and may be outdated. But String.fromInt did.
UPDATE: this will not work in Elm 0.19, use String.fromInt instead
You use Html.text to display a string, but the problem is that an integer is not a string, so you'll have to use toString. For example:
renderGuitarStringNum : Int -> Html Msg
renderGuitarStringNum num =
text (toString num)
You could also render that as
renderGuitarStringNum = toString >> text
Or
renderGuitarStringNum = text << toString
Related
The Elm Compiler gives me this error in my view function ...
TYPE MISMATCH - Something is off with the 2nd branch of this case expression:
div [] [
div [] [text (String.fromInt value)],
div [] [button [ onClick identity ] [ text "Enter number" ]]]
This div call produces:
Html #(a -> a)#
But the type annotation on view says it should be:
Html #Msg#Elm
Does anyone know how I can fix this issue?
Thank you!
module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model =
Result String Int
init : Model
init =
Result.Ok 0
-- UPDATE
type alias Msg = Int
update : Msg -> Model -> Model
update msg model =
collatz msg
collatz : Int -> Result String Int
collatz start =
if start <= 0 then
Result.Err "Only positive numbers are allowed"
else
Result.Ok (collatzHelper start 0)
collatzHelper : Int -> Int -> Int
collatzHelper start counter =
if start == 1 then
counter
else if modBy 2 start == 0 then
collatzHelper (start // 2) (counter + 1)
else
collatzHelper (3 * start + 1) (counter + 1)
-- VIEW
view : Model -> Html Msg
view model =
case model of
Err err ->
div [] [text err]
Ok value ->
div [] [
div [] [text (String.fromInt value)],
div [] [button [ onClick identity ] [ text "Enter number" ]]]
The compiler error message, as always with Elm, is fairly straightforward to understand. The problem is the onClick attribute in this line:
div [] [button [ onClick identity ] [ text "Enter number" ]]
As the compiler says, this has type a -> a, when it's expected to have type Msg, which in your case is the same as Int. That is, you've passed a function and it should be an integer.
Since value here is indeed an Int, that seems the natural choice to use here:
div [] [button [ onClick value ] [ text "Enter number" ]]
and this does indeed compile. However, this app doesn't seem particularly useful, because the user has no way to change the value displayed. You will need to include some sort of numeric input in your app to allow that - and then you will need a new alternative in your Msg type to handle the change of value.
Judging by this and your previous question (which is where I assume you got the idea of using identity as an onClick value - unfortunately that doesn't work in general), you seem to be struggling a bit with how the Elm Architecture works, so if you haven't already I would strongly recommend going through the tutorial.
I have the following type:
type alias SelList a =
{ list : List a
, selected : Maybe a
}
A Sel(ectable)List a is a list of a from which I can possibly choose an element.
In my application, all my objects have an id : Int field, so I've defined this type alias :
type alias HasId r = { r | id : Int}
Now I would like a function eventually selecting an element in the list, I've tried :
select : Int -> SelList (HasId r)-> Maybe (SelList (HasId r))
select id sl = find (\x-> x.id ==id) sl.list &> \ el ->
Just { sl | selected = el }
where (&>) = flip Maybe.andThen and find : (a -> Bool) -> List a -> Maybe a.
I've got the following message:
The type annotation for `select` says it always returns:
Maybe (SelList (HasId r))
But the returned value (shown above) is a:
Maybe { list : List (HasId r), selected : { r | id : Int } }
I'm confused because { r | id : Int } is the same than HasId, and then
{ list : List (HasId r), selected : HasId r }
is the same than SelList (HasId r). Why the compiler can not figure out that the types match?
The compiler's error is 90% of the way there, but I think mixing type aliases and records makes it harder to figure out what's wrong. (Future versions of Elm are going to improve this).
If it was a record and not a Maybe record, the compiler would tell you something like "I see a problem with the selected field`". Does that help?
Spoiler: The SelList type has a selected : Maybe a, but select returns selected : a
I want to change some deeply nested values inside my model.
type alias Tone = ( String, Int )
type alias Fret =
{ number : Int
, tone : Tone
, active : Bool
}
type alias GuitarString =
{ number : Int
, frets : List Fret
}
My model is called "Fretboard":
type alias Fretboard =
{ guitarStrings : List GuitarString
}
How could I change the value of the active field inside a certain fret?
The hierarchy is:
Fretboard > GuitarStrings > Frets
Thank you.
In your model, you have a few lists and you'll have to have a way of specifying which item in the list to update. The elm-community/list-extra package has some nice helpers for updating a value in a list, some by a conditional and another by specifying an index.
In this example, I'm using updateIf to check on the string number stored in the .number of the GuitarString. This will probably be fine, but it means you will have to be responsible to make sure the number exists and only exists once in that list. You could also update at an index by using updateIfIndex; it just depends on how you typically handle these in your app.
Here is an example set of update functions for your need using only the elm-community/list-extra package as a dependency.
setGuitarActiveFretTone : Int -> Tone -> Fretboard -> Fretboard
setGuitarActiveFretTone string tone fb =
{ fb
| guitarStrings =
updateIf
(\gs -> gs.number == string)
(setActiveFretTone tone)
fb.guitarStrings
}
setActiveFretTone : Tone -> GuitarString -> GuitarString
setActiveFretTone tone gs =
{ gs | frets = updateIf .active (setTone tone) gs.frets }
setTone : Tone -> Fret -> Fret
setTone tone fret =
{ fret | tone = tone }
If you plan on doing lots of nested updates, you may want to consider using lenses from a library like arturopala/elm-monocle (here's a small example on another StackOverflow answer).
type alias Model =
{ dieFace : Int
}
init : (Model, Cmd Msg)
init =
(Model 1, Cmd.none)
Why does the integer 1 get passed to the model ala Model 1?
The type alias seems to requiring a record?
There is not so much un-explained magic in Elm (for good reason), but one bit is the type and type alias constructors. Whenever you create a type (alias) you get a constructor function for free. So, to use your example,
type alias Model =
{ dieFace : Int
}
gives you a (somewhat weird-looking) constructor function
Model : Int -> Model
for free. If you add more entries to your record, like this
type alias Model =
{ dieFace : Int
, somethingElse : String
}
the constructor function takes more arguments.
Model : Int -> String -> Model
The order of these are the same order as the record entries, so if you change the order of your type aliases, you'll have to change the argument order to to the constructor function.
Union types work in a similar way.
type Shape
= Circle Int
| Square Int Int
quietly creates constructors:
Circle: Int -> Shape
Square : Int -> Int -> Shape
In Model 1 "Model" is used as positional record constructor. It is equal to {dieFace = 1}
Here is another example:
type alias Rcd =
{ first : String
, second : Int
}
Rcd can be constructed in two ways:
Rcd "some string" 4
{ first = "some string" , second = 4}
The former variant is just shorthand and often used for initialisation of Records.
Playing with the Elm checkboxes example. I am trying to move the following repetative code in view
, label []
[ br [] []
, input [ type' "checkbox", checked model.underline, onCheck Underline ] []
, text "underline"
]
into a separate function and use it three times. So far I have ...
makeLabel : String -> Bool -> Msg -> Html Msg
makeLabel caption bool msg =
label []
[ br [] []
, input [ type' "checkbox", checked bool, onCheck msg ] []
, text caption
]
and I would use it like
makeLabel "underline" model.underline Underline
but then I receive the following error
Function `makeLabel` is expecting the 3rd argument to be:
Msg
But it is:
Bool -> Msg
How do I pass my makeLabel function the correct action to take when a user changes the checkbox?
type Msg
= Red Bool
| Underline Bool
| Bold Bool
I don't understand how I can pass to a function the union type (Underline) without including the tag (Underline Bool)
The problem is in your type signature, rather than the code. Try this:
makeLabel : String -> Bool -> (Bool -> Msg) -> Html Msg