Looping over a list to create elements - elm

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)

Related

Find item in list and add to other list

I have a model containing a list of items that are rendered in a select as options.
The user can select an item, enter a number and click add to add the selected item and a "quantity" to a list.
My model looks like this:
type alias Drink =
{ id: String
, name: String
}
type alias Item =
{ id: String
, quantity: Int
}
type alias Model =
{ drinks: List Drink
, selected: List Item
, inputDrink: String
, inputQuantity: Int
}
I then want to render the selected list in a table. My main struggle right now is figuring out how I map over the array of selected items, based on the id of the current item find the name of the Drink to render in the table.
I've made this itemRow view:
itemRow : (Item, Drink) -> Html Msg
itemRow tuple =
-- This bit not updated to work with a Tuple yet.
tr [ id item.id ]
[ td []
[ button [] [ text "x" ]
]
, td [] [ text drink.name ]
, td []
[ input [ type_ "number", value (String.fromInt item.quantity) ] []
]
]
So what I'd like is to do something like:
model.selected
|> List.map (\selected -> (selected, List.Extra.find (\drink -> drink.id == selected.id)) )
|> List.map itemRow
But to do this I need to get rid of the Maybe I get from List.Extra.find and I don't know how… 😅
Any other tips or tricks on how I might better solve this by modelling the data differently very welcome. New to Elm :)
Here's how you remove the Nothings. Although you know that the find must always succeed, Elm requires you to handle the case where it does not. Here I just ignore those cases.
model.selected
|> List.filterMap (\selected ->
case List.Extra.find (\drink -> drink.id == selected.id) of
Just x -> Just (selected, x)
Nothing -> Nothing
)
|> List.map itemRow

Elm img attribute too many arguments

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 ]
]
]

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.

Transform Html DOM

I am new to Elm and I really love it so far, but I've run into a problem that I cannot seem to wrap my head around.
I have an Html DOM, for example
div []
[ h1 [] [text "Headline 1"]
, p [] [text "Some text"]
, h2 [] [text "Headline 2"]
]
I would like to add a-links inside each h[1-6] element and so transform it to something like (keeping it simple)
div []
[ h1 [] [ text "Headline 1"
, [a [name "headline"] [text "#"]
]
, p [] [text "Some text"]
, h2 [] [text "Headline 2"
, [a [name "headline"] [text "#"]
]
]
This is conceptually not very hard. Look through the DOM, if element is h[1-6] add an a-link as child element. However my understanding of Elm is not well enough to get it to work.
Here is what I've been trying so far.
transform : Html a -> Html a
transform node =
-- check if the tag is h1-h6
case node.tag of
-- add a-link to h1 children
"h1" -> { node | children = (a [name "headline"] [text "#") :: node.children }
"h2" -> { node | children = (a [name "headline"] [text "#") :: node.children }
-- do this for all nodes in the tree
_ -> { node | children = List.map transform node.children }
This doesn't work.
The type annotation for `transform` does not match its definition.
40| transform : Html a -> Html a
^^^^^^^^^^^^^^^^
The type annotation is saying:
VirtualDom.Node a -> VirtualDom.Node a
But I am inferring that the definition has this type:
{ b | tag : String, children : List (Html a) }
-> { b | children : List (Html a), tag : String }
I understand that I can't do node.tag because the generic type a might not have that field. It wouldn't be type safe. For example the text node doesn't have a tag field, but is still an instance of Html.Html a.
> text "Hello World"
{ type = "text", text = "Hello World" } : Html.Html a
My question is, how can I do this? Can I do this? or shouldn't I be doing this?
It is not possible to modify existing values of Html msg type.
They are final internal structures, which are rendered by Virtual DOM in to actual HTML Nodes as an output of your program.
Html msg is an alias for VirtualDom.Node a
You are attempting to use them as Records, but that's just a JavaScript object.
Elm REPL outputs String presentation of an abstract data structure here:
> text "Hello World"
{ type = "text", text = "Hello World" } : Html.Html a -- not a record
Instead of attempting to transform Html msg -> Html msg, you should try something like:
-- Input example: [ "#", "http://google.com/", "http://package.elm-lang.org/" ]
linksView : List String -> Html msg
linksView links =
links
|> List.map (\link -> a [ href link ] [ text link ])
|> div [] -- Expected output: <div> with thre links
In Elm, Html a is really only useful as output. You're never going to use it as input in the way that your transform function is attempting.
You will be better served by creating a model to describe your domain, then passing that to a view function to render html.
type alias Article =
{ priority : Priority
, headline : String
, body : String
}
type alias Model =
List Article
type Priority = First | Second
Your view could then look something like this:
view : Model -> Html msg
view =
div [] << List.map viewArticle
viewArticle : Article -> Html msg
viewArticle article =
let
priorityTag =
case article.priority of
First -> h1
Second -> h2
in
div []
[ priorityTag []
[ text article.headline
, a [ name "headline" ] [ text "#" ]
]
, p [] [ text article.body ]
]

In a select element, how do I designate the initially selected option from my model in Elm?

Let's say I have a select element to choose a person, and I want to have a certain person, say with id = 3, to be initially selected. How do I pass this id down into my options, and then set the selected attribute to True in that options?
Some sample code:
personSelect : List Person -> String -> Html Msg
personSelect : personList selectedId =
div []
[ select [] (List.map personOption personList) ]
personOption : Person -> Html Msg
personOption : person =
option [ value (toString person.id) ] [ text person.name ]
Specifically, how do I get "selectedId" passed to "personOption"? Can I even do this using List.map?
Thanks very much!
Provide selectedId as an argument to personOption and exploit that you can partially apply functions in Elm. That is, when you give a function some but not all of the arguments that it needs, you get back a function waiting for the remaining arguments.
First, add selectedId to personOptions and render the option as selected if it matches.
personOption : String -> Person -> Html Msg
personOption selectedId person =
option
[ selected (selectedId == person.id)
, value (toString person.id)
]
[ text person.name ]
Then partially apply personOption by giving it its first argument before passing it on to map:
personSelect : List Person -> String -> Html Msg
personSelect personList selectedId =
div []
[ select []
(List.map (personOption selectedId) personList)
-- personOption selectedId : String -> Html Msg
]