Elm img attribute too many arguments - elm

I am extremely new to Elm, and I am encountering a problem which frustrates me to no end.
Here is my code:
view : Model -> Html Msg
view model =
img [src "Img1.png", width 300, height 300] []
div []
[ input [ onInput ChangeUserInput ] []
, button [ onClick SaveText ] [ text "Save" ]
, button [ onClick Clear ] [ text "Clear" ]
, h1 [] [text model.userInput]
]
And the error I receive is that
The `img` function expects 2 arguments, but it got 5 instead
I think its parsing the div tag arguments as img arguments, but I can't figure out how to fix it.

Your view function needs to return a single HTML element. At the moment it seems you are trying to return two elements: an img and a div. The div and its two arguments are being picked up as arguments to the img because there is nothing in your code that Elm can use to identify the end of the list of arguments to pass to the img function.
You will need to wrap them both in an element that contains them, for example, another div:
view : Model -> Html Msg
view model =
div []
[ img [ src "Img1.png", width 300, height 300 ] []
, div []
[ input [ onInput ChangeUserInput ] []
, button [ onClick SaveText ] [ text "Save" ]
, button [ onClick Clear ] [ text "Clear" ]
, h1 [] [ text model.userInput ]
]
]

Related

How to add secondary text in elm?

I have a dropdown which shows text as moredetails with a keyboard-down-arrow, on clicking it expands and i can see the required fields. What i am trying to achieve is once on clicking the button when the secondary field expands i want to change the text to 'less details', i am able to achieve till like on expansion the keyboard downarrow mark becomes keyboard up arrow mark.
this is my code
Html.span
[ Attributes.class "details" ]
[ Html.a
[ Attributes.href "#", Events.onClick (ExpandDetails tx.id)]
[ Html.text "More detail"
, Html.i
[ Attributes.class "material-icons" ]
[ if expanded then
Html.text "keyboard_arrow_up"
else
Html.text "keyboard_arrow_down"
]
]
]
i am able to make it till if expands show downarrow but along with it i need text to change from more details to less details and on clicking less details it should be back to more details.
If I'm understanding correctly, you could define both the button and text in a let expression before the dropdown code. Something like:
let
( dropdownButton, dropdownText ) =
if expanded then
( "keyboard_arrow_up", "Less details" )
else
( "keyboard_arrow_down", "More details" )
in
Html.span [ Attributes.class "details" ]
[ Html.a
[ Attributes.href "#"
, Events.onClick (ExpandDetails tx.id)
]
[ Html.text dropdownText
, Html.i [ Attributes.class "material-icons" ]
[ Html.text dropdownButton ]
]
]
Here's an alternative.
Move the link into a function that takes the Strings as arguments.
viewDetails : Tx -> Bool -> Html Msg
viewDetails tx expanded =
let
lessOrMoreLink =
if expanded then
viewExpandLink "Less" "keyboard_arrow_up"
else
viewExpandLink "More" "keyboard_arrow_down"
in
Html.span
[ Attributes.class "details" ]
[ lessOrMoreLink tx.id ]
viewExpandLink : String -> String -> Int -> Html Msg
viewExpandLink txt arrow id =
Html.a
[ Attributes.href "#"
, Events.onClick (ExpandDetails id)
]
[ Html.text (txt ++ " details")
, Html.i
[ Attributes.class "material-icons" ]
[ Html.text arrow ]
]

Passing multiple complex parent actions to deeply nested child views

Disclaimer: I realized this was a maybe stupid question after I finished writing it. Please don't spend too much time reading it. I am very new to Elm, functional programming, and not a UI buff.
I have a view in Elm that returns Html Msg and takes in a model. Using the simple increment demo as en example, I have this typical setup:
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 = Int
init : Model
init =
0
-- UPDATE
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
I have a button component that's quite complex which I would like to extract into a separate function. I'm able to do this with normal Html, i.e.
-- VIEW
some_html : Html msg
some_html =
text "FOO"
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
, some_html
]
I can also pass the Msg type I've defined and have the "sub-function" call the action:
-- VIEW
make_button : Msg -> Html Msg
make_button msg =
button [ onClick msg ] [ text "-" ]
view : Model -> Html Msg
view model =
div []
[ make_button Decrement
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
My problem and question is:
I would like to be able to have my make_button function be able to handle multiple actions. One way I have found that works is to pass all possible actions and then a key, i.e.
-- VIEW
make_button : Msg -> Msg -> String -> Html Msg
make_button decr incr which =
if which == "Decrement" then
button [ onClick decr ] [ text "-" ]
else button [ onClick incr ] [ text "+" ]
view : Model -> Html Msg
view model =
div []
[ make_button Decrement Increment "Decrement"
, div [] [ text (String.fromInt model) ]
, make_button Decrement Increment "Increment" -- doesn't matter here.
]
But this becomes cumbersome when the number of actions is large (in my use case I have 20 or so actions).
Should I create a dictionary of sorts? Is there a way this is done? Is this a bad thing to do? Please give me grief.
I am imaging scenarios where many nested child components might want to have the ability to call any Action of the parent component on the fly without this being hard-coded, which is why I decided to still ask the question.
Thanks.
You're definitely over thinking things! The way you would do this is
-- camel case is the convention in Elm ;)
makeButton : Msg -> Html Msg
makeButton msg =
button
[ onClick msg ]
[ text <|
-- an if statement would also work in this case
case msg of
Increment ->
"+"
Decrement ->
"-"
]
view : Model -> Html Msg
view model =
div []
[ makeButton Decrement
, div [] [ text (String.fromInt model) ]
, makeButton Increment
]

How can I make a table striped using elm-bootstrap 4.1.0?

I have been trying several things and cannot work out how to style this Elm table with Bootstrap so it's striped. I am trying to use elm-bootstrap and have installed rundis/elm-bootstrap 4.1.0
Bootstrap.Table is currently unused:
module Players.List exposing (..)
import Html exposing (..)
import Html.Attributes exposing (class)
import Msgs exposing (Msg)
import Models exposing (Player)
import RemoteData exposing (WebData)
import Bootstrap.Table as Table
view : WebData (List Player) -> Html Msg
view response =
div []
[ nav
, maybeList response
]
nav : Html Msg
nav =
div [ class "clearfix mb2 white bg-black" ]
[ div [ class "left p2" ] [ text "Players" ] ]
maybeList : WebData (List Player) -> Html Msg
maybeList response =
case response of
RemoteData.NotAsked ->
text ""
RemoteData.Loading ->
text "Loading..."
RemoteData.Success players ->
list players
RemoteData.Failure error ->
text (toString error)
list : List Player -> Html Msg
list players =
div [ class "col-md-4" ]
[ table [ class "table table-striped" ]
[ thead []
[ tr []
[ th [] [ text "Id" ]
, th [] [ text "Initials" ]
, th [] [ text "Time" ]
, th [] [ text "Score" ]
]
]
, tbody [] (List.map playerRow players)
]
]
playerRow : Player -> Html Msg
playerRow player =
tr []
[ td [] [ text player.id ]
, td [] [ text player.initials ]
, td [] [ text (toString player.time) ]
, td [] [ text (toString player.score) ]
]
As this should be ultra simple I'm clearly missing something here. How can I make this table striped?
Your example uses HTML functions from Elm's HTML library but you're probably going to have a better time using the appropriate Bootstrap types and functions. For example, using the table options defined in the documentation, you could rewrite the view functions like this:
list : List Player -> Html msg
list players =
Table.table
{ options = [ Table.striped ]
, thead = Table.thead []
[ Table.tr []
[ Table.th [] [ text "Id" ]
, Table.th [] [ text "Initials" ]
, Table.th [] [ text "Time" ]
, Table.th [] [ text "Score" ]
]
]
, tbody = Table.tbody [] (List.map playerRow players)
}
playerRow : Player -> Table.Row msg
playerRow player =
Table.tr []
[ Table.td [] [ text player.id ]
, Table.td [] [ text player.initials ]
, Table.td [] [ text (Debug.toString player.time) ]
, Table.td [] [ text (Debug.toString player.score) ]
]
That will give you the right HTML but you may still need to import the Bootstrap styles. The documentation gives an example of including the stylesheet, which you could do in some wrapping function, for example:
import Bootstrap.Grid as Grid
import Bootstrap.Table as Table
import Bootstrap.CDN as CDN
view : Model -> Html Msg
view model =
Grid.container []
[ CDN.stylesheet -- creates an inline style node with the Bootstrap CSS
, Grid.row []
[ Grid.col []
[ list model.players ]
]
]
Here is a slimmed down Ellie example to play with.

Do we need to manage the state of radio buttons in Elm?

I am referring to this example in http://elm-lang.org/examples/radio-buttons. I don't see anywhere whereby the state of the buttons is being managed.
In my own little Elm project I need to do something like
label []
[ input
[ type_ "radio"
, checked (model.choosenSize == size)
, onClick (SetSize size)
] []
, text (sizeToString size)
]
Without managing the checked attribute, all the radio buttons will remain checked after you click on it.
So what is the magic in the example?
The example you are referring is very simple. It doesn't explicitly manage the state of the buttons. Instead, their state is managed by the browser. In a real application, of course, you would better manage it explicitly. Something like:
view : Model -> Html Msg
view model =
div []
[ fieldset []
[ radio "Small" (model.fontSize == Small) (SwitchTo Small)
, radio "Medium" (model.fontSize == Medium) (SwitchTo Medium)
, radio "Large" (model.fontSize == Large) (SwitchTo Large)
]
, Markdown.toHtml [ sizeToStyle model.fontSize ] model.content
]
radio : String -> Bool -> msg -> Html msg
radio value isChecked msg =
label
[ style [("padding", "20px")]
]
[ input [ type_ "radio", checked isChecked, name "font-size", onClick msg ] []
, text value
]
(I added a Bool argument to radio)

Looping over a list to create elements

This seems to be set up correct, but it clearly is not and I cannot see where it's going wrong. I'm trying to loop over a "list of objects" and create a ul with lis for each item in the list and put those inside of a div. Ignore everything involving the ID. I have a feeling I'm not entirely sure how List.map returns.
type alias Product =
{ a : String
, b : String
, c : Int
, d : String
, e : String
}
type alias Model =
{ id : String
, products : List Product}
view : Model -> Html Msg
view model =
div []
[ input [ type' "text", onInput UpdateText ] []
, button [ type' "button", onClick GetProduct ] [ text "Search" ]
, br [] []
, code [] [ text (toString model.products) ]
, div [] [ renderProducts model.products ]
]
renderProduct product =
let
children =
[ li [] [ text product.a ]
, li [] [ text product.b ]
, li [] [ text (toString product.c) ]
, li [] [ text product.d ]
, li [] [ text product.e ] ]
in
ul [] children
renderProducts products =
List.map renderProduct products
The error is as follows:
The 2nd argument to function `div` is causing a mismatch.
78| div [] [ renderProducts model.products ]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Function `div` is expecting the 2nd argument to be:
List (VirtualDom.Node a)
But it is:
List (List (Html a))
renderProducts returns a list of elements. The second parameter of div takes a list of elements. By enclosing the second parameter in brackets, you are creating a list containing a single list of elements. That's why the error message says
But it is:
List (List (Html a))
You should instead do this:
div [] (renderProducts model.products)