Successfully compiled Elm code shows "too much recursion" warning - elm

I'm using a Toolbox module I wrote myself, and it does compile without any error or warning. However, when the menubar function is called, the console shows an "too much recursion" warning.
Toolbox.elm
module Toolbox exposing (..)
import List exposing (head, tail)
import Maybe exposing (withDefault)
import Html exposing (..)
import Html.Attributes exposing (..)
menubar : Html Never
menubar =
div
[ class "relative bg-dark-blue firasans white ttu pa2" ]
[ img
[ src "i/balloon.png"
, width 45
, height 73
]
[]
, ul
[ class "dt absolute bl-l b--white list pa1 ml3 mt2"
, style
[ ( "left", "45px" )
, ( "top", "0" )
, ( "height", "73px" )
]
]
(imgList
[ class "dtc v-mid" ]
[]
[ "i/home.png"
, "i/tutorials.png"
]
)
]
imgList : List (Attribute msg) -> List (Maybe (Attribute msg)) -> List String -> List (Html msg)
imgList attrs events srcs =
case (head events) of
Nothing ->
(li
attrs
[ img [ src (wdEmptyStr (head srcs)) ] [] ]
)
:: (imgList attrs [] (wdEmpty (tail srcs)))
Just maybe ->
case maybe of
Nothing ->
(li
attrs
[ img [ src (wdEmptyStr (head srcs)) ] [] ]
)
:: (imgList attrs (wdEmpty (tail events)) (wdEmpty (tail srcs)))
Just event ->
(li
(event :: attrs)
[ img [ src (wdEmptyStr (head srcs)) ] [] ]
)
:: (imgList attrs (wdEmpty (tail events)) (wdEmpty (tail srcs)))
wdEmpty =
withDefault []
wdEmptyStr =
withDefault ""

Just went to look in the Javascript console and saw a "too much recursion" warning.
Fixed by rewriting Toolbox.imgList this way:
imgList : List (Attribute msg) -> List (Maybe (Attribute msg)) -> List String -> List (Html msg)
imgList attrs events srcs =
case srcs of
[] ->
[]
_ ->
case (head events) of
Nothing ->
(li
attrs
[ img [ src (wdEmptyStr (head srcs)) ] [] ]
)
:: (imgList attrs [] (wdEmpty (tail srcs)))
Just maybe ->
case maybe of
Nothing ->
(li
attrs
[ img [ src (wdEmptyStr (head srcs)) ] [] ]
)
:: (imgList attrs (wdEmpty (tail events)) (wdEmpty (tail srcs)))
Just event ->
(li
(event :: attrs)
[ img [ src (wdEmptyStr (head srcs)) ] [] ]
)
:: (imgList attrs (wdEmpty (tail events)) (wdEmpty (tail srcs)))

Related

Reset an Elm select dropdown value

I basically have a little select dropdown like this:
viewDropdown : Model -> Html Msg
viewDropdown model =
let
options =
[ ( "", "-- Select --" )
, ( "not_available", "Unavailable" )
, ( "available", "Available" )
]
buildOption ( k, v ) =
option [ value k, selected (k == model.isAvailable) ] [ text v ]
viewOptions =
options
|> List.map
(\( k, v ) ->
buildOption ( k, v )
)
in
div [ class "styled-select" ]
[ select
[ on "change" (Decode.map (UpdateAvailability) targetValue)
]
viewOptions
]
If the user selects "Available", a modal pops up and they are prompted to confirm. If they hit "Cancel", I want the Select dropdown's value to reset to the value of "". This is not the case and although my model reflects the a Nothing val, the dropdown selection option is still on "Available". Any idea on what I can do to reset the DOM state?
While this doesn't answer why your code doesn't work, it seems that if you add the step of confirming the selection, then code will work:
Ellie example, with full code below: https://ellie-app.com/3P5TTM9YqVWa1
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text, option, select)
import Html.Events exposing (onClick, on, targetValue)
import Html.Attributes exposing (value, selected, class)
import Json.Decode as Decode
import Task
type alias Model =
{ isAvailable : String , showConfirm: Bool }
init : Model
init =
{ isAvailable = "-", showConfirm = False }
type Msg
= UpdateAvailability String
| ConfirmYes
| Reset
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateAvailability v ->
{ model | isAvailable = v, showConfirm = (v == "available") }
ConfirmYes ->
{ model | showConfirm = False }
Reset ->
{ model | isAvailable = "-", showConfirm = False }
viewDropdown : Model -> Html Msg
viewDropdown model =
let
options =
[ ( "-", "-- Select --" )
, ( "not_available", "Unavailable" )
, ( "available", "Available" )
]
buildOption ( k, v ) =
option [ value k, selected (k == model.isAvailable) ] [ text v ]
viewOptions = List.map buildOption options
in
div []
[ select
[ on "change" (Decode.map (UpdateAvailability) targetValue) ]
viewOptions
]
viewConfirm model =
if model.showConfirm then
div []
[ text "Really available?"
, button [ onClick ConfirmYes ] [ text "Yes" ]
, button [ onClick Reset ] [ text "No" ]
]
else
div [] []
view : Model -> Html Msg
view model =
div []
[ viewDropdown model
, viewConfirm model
, button [ onClick Reset ] [ text "Reset" ]
]
main : Program () Model Msg
main =
Browser.sandbox
{ init = init
, view = view
, update = update
}
Turns out, adding a value attribute to the select html will override the option selected state, so this is pretty important if you need your select input to be in sync with model state.
https://ellie-app.com/3NZgYQYKv2Fa1
Are you setting model.isAvailable to "" when hitting the close button? Your code should work as is https://ellie-app.com/3NYRvgwkHWPa1

How Can I Dynamically Create Table Rows From A List Of Records?

My Model type is:
type alias Model =
{ freeSyllables : List FreeSyllable
, freeSyllableInput : String
, usageStartInput : Bool
, usageMidInput : Bool
, usageEndInput : Bool
}
The FreeSyllable type looks like:
type alias FreeSyllable =
{ syllable : String
, usage : Usage
}
The Usage type has three Boolean fields:
type alias Usage =
{ start : Bool
, mid : Bool
, end : Bool
}
I tried to render each item of the model's FreeSyllables-List to a table.
I didn't succeed.
So my question is how I can dynamically render each of the model's "FreeSyllables" into a proper html table with these columns:
syllable (textbox)
start usage (checkbox)
mid usage (checkbox)
end usage (checkbox)
actions (save-Button)
You'll have to attach event handlers (that fire Msg), but here is example view:
view : Model -> Html Msg
view model =
table [] <|
[ tr []
[ th [] [ text "syllable" ]
, th [] [ text "start" ]
, th [] [ text "mid" ]
, th [] [ text "end" ]
, th [] [ text "actions" ]
]
]
++ (List.map viewItem model.freeSyllables)
viewItem : FreeSyllable -> Html Msg
viewItem s =
tr []
[ th [] [ text s.syllable ]
, th [] [ input [ type_ "checkbox", checked s.usage.start ] [] ]
, th [] [ input [ type_ "checkbox", checked s.usage.mid ] [] ]
, th [] [ input [ type_ "checkbox", checked s.usage.end ] [] ]
, th [] [ button [] [ text "save" ] ]
]

mdl-lite dialog not displaying correct information

I'm trying to make a list of buttons, each of which opens up a dialog which displays a different number. For example, the first button says '10' and then when it is clicked a dialog opens which also says '10'. The second says '20', and when it is clicked a dialog is opened that also says '20', etc. However, all of the dialogs say '10' when they are opened.
Here is the code:
module Main exposing (..)
import Html exposing (Html, div, text, p)
import Html.App as App exposing (program)
import Material
import Material.Button as Button
import Material.Scheme as Scheme
import Material.Dialog as Dialog
-- MODEL
type alias Model =
{ buttons : List Int, mdl : Material.Model }
init : ( Model, Cmd Msg )
init =
( { buttons = [ 10, 20, 30, 40, 50, 60, 70 ], mdl = Material.model }, Cmd.none )
-- MESSAGES
type Msg
= Log Int
| Mdl (Material.Msg Msg)
--VIEW
element : Int -> Model -> Html Msg
element int model =
Dialog.view
[]
[ Dialog.title [] [ text "Greetings" ]
, Dialog.content []
[ p [] [ text "What is this insanity?" ]
, p [] [ text (toString int) ]
]
, Dialog.actions []
[ Button.render Mdl
[ 0 ]
model.mdl
[ Dialog.closeOn "click" ]
[ text "Close" ]
]
]
view : Model -> Html Msg
view model =
div []
(List.map (\b -> button b model) model.buttons)
|> Scheme.top
button : Int -> Model -> Html Msg
button int model =
div []
[ Button.render
Mdl
[ 1 ]
model.mdl
[ Button.raised
, Button.ripple
, Button.onClick (Log int)
, Dialog.openOn "click"
]
[ text (toString int) ]
, element int model
]
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Log int ->
let
check =
Debug.log "Int" int
in
model ! []
Mdl msg' ->
Material.update msg' model
-- MAIN
main : Program Never
main =
program
{ init = init
, view = view
, update = update
, subscriptions = always Sub.none
}
I read mdl-lite only supports one dialog per application, so the problem must have been calling element multiple times. The solution is to call element once in the view function, and with each button click to update a dialogInt value in the model, and then to display this value in the dialog.
Here's the code:
module Main exposing (..)
import Html exposing (Html, div, text, p)
import Html.App as App exposing (program)
import Material
import Material.Button as Button
import Material.Scheme as Scheme
import Material.Dialog as Dialog
-- MODEL
type alias Model =
{ ints : List Int, dialogInt : Int, mdl : Material.Model }
init : ( Model, Cmd Msg )
init =
( { ints = [ 10, 20, 30, 40, 50, 60, 70 ], dialogInt = 0, mdl = Material.model }, Cmd.none )
-- MESSAGES
type Msg
= Log Int
| UpdateDialogInt Int
| Mdl (Material.Msg Msg)
--VIEW
element : Model -> Html Msg
element model =
-- let
-- check =
-- Debug.log "int" int
-- in
Dialog.view
[]
[ Dialog.title [] [ text "Greetings" ]
, Dialog.content []
[ p [] [ text "What is this insanity?" ]
, p [] [ text (toString model.dialogInt) ]
]
, Dialog.actions []
[ Button.render Mdl
[ 1 ]
model.mdl
[ Dialog.closeOn "click" ]
[ text "Close" ]
]
]
view : Model -> Html Msg
view model =
div []
((element
model
)
:: (List.map (\b -> button b model) model.ints)
)
|> Scheme.top
button : Int -> Model -> Html Msg
button int model =
div []
[ Button.render
Mdl
[ int ]
model.mdl
[ Button.raised
, Button.ripple
, Button.onClick (UpdateDialogInt int)
, Dialog.openOn "click"
]
[ text (toString int) ]
]
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Log int ->
let
check =
Debug.log "int" int
in
model ! []
UpdateDialogInt int ->
{ model | dialogInt = int } ! []
Mdl msg' ->
let
check =
Debug.log "msg" msg'
in
Material.update msg' model
-- MAIN
main : Program Never
main =
program
{ init = init
, view = view
, update = update
, subscriptions = always Sub.none
}

how to play audio in elm

I am getting some data from my api.
{
"message":"",
"data":[
{
"title":"Test Rock Song",
"artist":"Music Artist Test",
"audioUrl":"https://xyz/radio/03+-+HS3+-+Wajah+Tum+Ho+%5BDJMaza.Info%5D.mp3",
"stateName":"California",
"cityName":"Los Angles",
"businessId":32
},
{
"title":"R&B S1",
"artist":"Music Artist Test",
"audioUrl":"https://xyz/radio/1463469171175_Ba_khuda_tumhi.mp3",
"stateName":"California",
"cityName":"Los Angles",
"businessId":32
},
{
"title":"R&B S2",
"artist":"Music Artist Test",
"audioUrl":"https://xyz/radio/1465890934054_01+-+HS3+-+Tumhe+Apna+Banane+Ka+%5BDJMaza.Info%5D.mp3",
"stateName":"California",
"cityName":"Los Angles",
"businessId":32
}
],
"count":3
}
So i have iterate that data in list, each have one mp3 url. I want when i click on link then particular mp3 will play.
Please any one help me to implement this feature.
If you are looking for the simplest of solutions, I would recommend rendering <audio> tags with the src attribute set to the audio URL. This can give you standard audio controls. Here is a full example along with some JSON decoders:
import Html exposing (..)
import Html.Attributes exposing (..)
import Json.Decode as Json
main =
case Json.decodeString myDecoder exampleJsonInput of
Err err -> text err
Ok data -> div [] <| List.map toAudioBlock data
toAudioBlock entry =
div []
[ div [] [ strong [] [ text entry.title ] ]
, div [] [ text "By: ", text entry.artist ]
, div [] (List.map text ["From: ", entry.cityName, ", ", entry.stateName])
, audio
[ src entry.audioUrl
, controls True
] []
]
type alias Entry =
{ title : String
, artist : String
, audioUrl : String
, stateName : String
, cityName : String
, businessId : Int
}
entryDecoder : Json.Decoder Entry
entryDecoder =
Json.map6
Entry
(Json.field "title" Json.string)
(Json.field "artist" Json.string)
(Json.field "audioUrl" Json.string)
(Json.field "stateName" Json.string)
(Json.field "cityName" Json.string)
(Json.field "businessId" Json.int)
myDecoder : Json.Decoder (List Entry)
myDecoder =
Json.at ["data"] <| Json.list entryDecoder
exampleJsonInput =
"""
{
"message":"",
"data":[
{
"title":"Test Rock Song",
"artist":"Music Artist Test",
"audioUrl":"https://archive.org/download/SuperMarioBros.ThemeMusic/SuperMarioBros.mp3",
"stateName":"California",
"cityName":"Los Angles",
"businessId":32
},
{
"title":"R&B S1",
"artist":"Music Artist Test",
"audioUrl":"http://216.227.134.162/ost/super-mario-bros.-1-3-anthology/pnfhmccstp/1-02-underworld.mp3",
"stateName":"California",
"cityName":"Los Angles",
"businessId":32
},
{
"title":"R&B S2",
"artist":"Music Artist Test",
"audioUrl":"https://ia800504.us.archive.org/33/items/TetrisThemeMusic/Tetris.mp3",
"stateName":"California",
"cityName":"Los Angles",
"businessId":32
}
],
"count":3
}
"""
You can paste this into http://elm-lang.org/try. I have substituted your audio examples with actual mp3s from the internet.
If instead you are looking for a complete audio library port to Elm, there is currently no version compatible with Elm 0.17.
I had the same problem, the other solutions in the thread are also good, but I think that I have a simpler example. Elm does not have a pure solution for sounds,
therefore you need use ports and some javascript, if you want to use more than the
pure html audio component.
You can find the whole example project under Github: Elm sound by example
Or here the pure elm and html/javascript code.
(Main.elm)
port module Main exposing (..)
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
-- MAIN
main : Program () Model Msg
main =
Browser.element
{ init = \_ -> init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- PORTS
port playMusic : String -> Cmd msg
port stopMusic : String -> Cmd msg
port setSource : String -> Cmd msg
-- MODEL
type alias Model =
{ song : String
}
init : ( Model, Cmd Msg )
init =
( { song = "" }
, Cmd.none
)
-- UPDATE
type Msg
= Play
| Stop
| Set String
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Play ->
( model
, playMusic "play"
)
Stop ->
( model
, stopMusic "stop"
)
Set message ->
( { model | song = message }
, setSource message
)
-- VIEW
view : Model -> Html Msg
view model =
div []
[ h1 [ id "song-header" ] [ text ("Current song: " ++ model.song) ]
, div [ Html.Attributes.class "song-holder" ]
[ button [ onClick (Set "song1.mp3") ] [ Html.text "Song1" ]
, button [ onClick (Set "song2.mp3") ] [ Html.text "Song2" ]
, button [ onClick (Set "song3.wav") ] [ Html.text "Song3" ]
]
, div [ Html.Attributes.class "audio-holder" ]
[ button [ onClick Play ] [ Html.text "Play" ]
, button [ onClick Stop ] [ Html.text "Stop" ]
]
, audio [ src "./assets/sounds/song2.mp3", id "audio-player", controls True ] []
]
(javascript in index.html)
document.getElementById("audio-player").volume = 0.1;
app.ports.playMusic.subscribe(function() {
document.getElementById("audio-player").play();
});
app.ports.stopMusic.subscribe(function() {
document.getElementById("audio-player").pause();
});
app.ports.setSource.subscribe(function(source) {
document.getElementById("audio-player").setAttribute("src", "assets/sounds/" + source);
});

ELM - List to table rows

In the examples, when mapping a list to html I always see something like
ul []
List.map toHtmlFunction myList
But what if the list is only a partial part of the child html elements like
...
table []
[
thead []
[
th [][text "Product"],
th [][text "Amount"]
],
List.map toTableRow myList,
tr []
[
td [][text "Total"],
td [][text toString(model.total)]
]
]
toTableRow: MyListItem -> Html Msg
toTableRow myListItem =
tr []
[
td[][text myListItem.label],
td[][text toString(myListItem.price)]
]
With this code I'm getting
The 1st element has this type:
VirtualDom.Node a
But the 2nd is:
List (Html Msg)
The problem is that thead and tr are of type Html a, while List.map returns a List (Html a), and they can't be combined just by using commas.
You could have a look a the functions for putting list together in the List package. For example you could do something like
table []
List.concat [
[ thead []
[ th [][text "Product"]
, th [][text "Amount"]
]
],
List.map toTableRow myList,
[ tr []
[ td [][text "Total"]
, td [][text toString(model.total)]
]
]
]
IMO the cleanest solution lies in #wintvelt's first suggestion: table [] ([ myHeader ] ++ List.map ...). For new users of elm, this seems the most intuitive. (BTW, I am a new user.)
Essentially, here, the takeaway realization is that the elm compiler doesn't group table [] [] ++ [] as table [] ([] ++ []) (for example). Instead, elm groups it as (table [] []) ++ []. This makes sense, if you think about it.
Thus, elm's evaluation of table [] [] ++ [] produces, at first, something of type Html msg (in Elm 0.18). Thereafter, the ++ function balks when it tries to combine that Html msg with a List.
(Naturally, also, if you try to append a List in the wrong way to some of your Html attributes, by coding table [] ++ [] [], you'll get a similar error message.)
Here's a fleshed out solution, tested with elm-make 0.18 (elm Platform 0.18.0):
module Main exposing (main)
import Html exposing (..)
main : Program Never Model Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ messages : List String }
init : ( Model, Cmd Msg )
init =
( Model [], Cmd.none )
-- UPDATE
type Msg
= None
update : Msg -> Model -> ( Model, Cmd Msg )
update msg { messages } =
case msg of
None ->
( Model messages, Cmd.none )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[]
-- VIEW
type alias MyListItem =
{ label : String
, price : Float
}
total : Float
total =
5.0
myList : List MyListItem
myList =
[ { label = "labelA", price = 2 }
, { label = "labelB", price = 3 }
]
toTableRow : MyListItem -> Html Msg
toTableRow myListItem =
tr []
[ td [] [ text myListItem.label ]
, td [] [ text (toString myListItem.price) ]
]
view : Model -> Html Msg
view model =
table
[]
([ thead []
[ th [] [ text "Product" ]
, th [] [ text "Amount" ]
]
]
++ List.map toTableRow myList
++ [ tr
[]
[ td [] [ text "Total" ]
, td [] [ text (toString total) ]
]
]
)