Elm: Parent update Child component via ports - elm

Is it possible for a parent component to pass the result of it's incoming port, to a child component.
I've been looking at this example of composing Parent/Child components (https://www.elm-tutorial.org/en/02-elm-arch/06-composing.html). This is example is great - but I want like to also update the child's model via Ports. Is this possible?
Example Gist.
index.js
var num = 0;
setInterval(function () {
num = num + 1
app.ports.tick.send(num)
},1000);
Ports.elm
port module Ports exposing (..)
port tick : (Int -> msg) -> Sub msg
Main.elm (Parent)
import Widget
type Msg
= WidgetMsg Widget.Msg
update : Msg -> AppModel -> ( AppModel, Cmd Msg )
update message model =
case message of
WidgetMsg subMsg ->
let
( updatedWidgetModel, widgetCmd ) =
Widget.update subMsg model.widgetModel
in
( { model | widgetModel = updatedWidgetModel }, Cmd.map WidgetMsg widgetCmd )
subscriptions : AppModel -> Sub Msg
subscriptions model =
Ports.tick WidgetMsg
Widget.elm (Child)
initialModel : Model
initialModel =
{ count = 0
, tickValue = 0
}
type Msg
= Increase
| Tick Int
update : Msg -> Model -> ( Model, Cmd Msg )
update message model =
case message of
Increase ->
( { model | count = model.count + 1 }, Cmd.none )
Tick val ->
({model | tickValue = val}, Cmd.none)

You would need to specify which child Msg receives the int value from the port. If you update your parent subscriptions function to this, it will send the Widget Tick message:
Ports.tick (WidgetMsg << Widget.Tick)

Related

Transform string in key variable name

There is the possibility of converting a string to a key, eg:
type alias Model =
{ sun : Int -- Init with 0
, moon : Int -- Init with 0
}
What I want to achieve:
let
userSelect = "sun";
in
({ model | userSelect = 1 }, Cmd.none) -- ugly to be easy to understand
After model.sun should 1
You won't be able to do exactly what you want as access to records does not work that way. In your case i would recommend a Dictionary
type alias Model =
{ planets : Dict String Int
}
planets =
Dict.fromList
[ ("sun", 0)
, ("moon": 0)
]
model = { planets = planets }
and then
let
userSelect = "sun";
in
({ model | planets = Dict.insert userSelect 1 model.planets }, Cmd.none)

How do I pattern match on a Cmd msg inside a Test API module?

How do I pattern match on a Cmd msg inside a Test API module?
I have a Test API that serves as an alternative to a web service.
sources : Id -> (Result Http.Error (List Source) -> msg) -> Cmd msg
sources profileId msg =
[ { platform = "WordPress", username = "bizmonger", linksFound = 0 }
, { platform = "YouTube", username = "bizmonger", linksFound = 0 }
, { platform = "StackOverflow", username = "scott-nimrod", linksFound = 0 }
]
|> Result.Ok
|> msg
|> Task.succeed
|> Task.perform identity
Issue:
I receive a compile error for the code below:
addSource : Id -> Source -> (Result Http.Error (List Source) -> msg) -> Cmd msg
addSource profileId source msg =
let
result =
sources profileId msg
in
case result of
Ok sources ->
(source :: sources)
|> Result.Ok
|> msg
|> Task.succeed
|> Task.perform identity
Err _ ->
Cmd.none
Ok sources -> ^^^^^^^^^^ The pattern matches things of type:
Result error value
But the values it will actually be trying to match are:
Cmd msg
Note:
I understand that these functions return a Cmd msg and that I need to pattern match on a Cmd msg. However, this code is in a TestAPI module and not a typical UI module. Thus, I don't think I should have to define a discriminated union for various messages that have already been defined in the UI client that relies on this TestAPI module.
Appendix:
type alias Source =
{ platform : String, username : String, linksFound : Int }
Since this is about "mocking" an API endpoint, I'll refrain from my usual "don't box data in a command if you're not triggering side-effects" spiel.
Instead, let me propose splitting up your sources function:
sourceData : List Source
sourceData =
[ { platform = "WordPress", username = "bizmonger", linksFound = 0 }
, { platform = "YouTube", username = "bizmonger", linksFound = 0 }
, { platform = "StackOverflow", username = "scott-nimrod", linksFound = 0 }
]
mockResponse : a -> (Result Http.Error a -> msg) -> Cmd msg
mockResponse data tagger =
Result.Ok data
|> Task.succeed
|> Task.perform tagger
sources : Id -> (Result Http.Error (List Source) -> msg) -> Cmd msg
sources profileId msg =
mockResponse sourceData msg
Now, implementing your addSource function becomes a reasonably simple call like so:
addSource : Id -> Source -> (Result Http.Error (List Source) -> msg) -> Cmd msg
addSource profileId source msg
mockResponse (source :: sourceData) msg
I had a realization that I'm still doing functional programming.
Thus, why not compose functions together from core essentials to more involved.
Thus, I did the following:
addSourceBase : Id -> Source -> List Source
addSourceBase profileId source =
source :: (profileId |> sourcesBase)
addSource : Id -> Source -> (Result Http.Error (List Source) -> msg) -> Cmd msg
addSource profileId source msg =
source
|> addSourceBase profileId
|> Result.Ok
|> msg
|> Task.succeed
|> Task.perform identity

Refactoring update fuction in a basic Elm app

I've recently started with Elm and I run into a problem with the update function. My goal is to split up my big Main.elm file into multiple smaller files, but to do this I first try to split up the main components into smaller components in the same file. For this I rely heavily on this very informative guide.
It is fairly straightforward to split the Model and init (which I already did for DiceRoller) and it is trivial for the View. Not so much for the Update unfortunately.
Currently, it looks like this (in the master branch of the Main.elm file)
type Msg = Change String
| Name String
| Password String
| PasswordAgain String
| Roll
| NewFace Int
| SearchImages
| NewSearchResult (Result Http.Error (List String))
| ChangeTermInput String
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Change newContent ->
({ model | content = newContent }, Cmd.none)
Name name ->
({ model | name = name }, Cmd.none)
Password password ->
({ model | password = password }, Cmd.none)
PasswordAgain password ->
({ model | passwordAgain = password }, Cmd.none)
Roll ->
(model, Random.generate NewFace (Random.int 1 100))
NewFace newFace ->
({ model | diceRoller = { dieFace = newFace} }, Cmd.none)
SearchImages ->
(model, getSearchResult model.termInput)
NewSearchResult (Ok newResult) ->
( { model | termResult = newResult }, Cmd.none )
NewSearchResult (Err _) ->
(model, Cmd.none)
ChangeTermInput term ->
({ model | termInput = term}, Cmd.none)
And I managed to get it a bit more refined, but this does not compile (also see this Main.elm in the refactoring branch):
type DiceRollerMsg = Roll
| NewFace Int
type Msg = Change String
| Name String
| Password String
| PasswordAgain String
| MsgForDiceRoller DiceRollerMsg
| SearchImages
| NewSearchResult (Result Http.Error (List String))
| ChangeTermInput String
updateDiceRoller : DiceRollerMsg -> DiceRoller -> DiceRoller
updateDiceRoller msg model =
case msg of
Roll ->
model
NewFace newFace ->
{ model | dieFace = newFace}
updateDiceRollerCmd : Msg -> Cmd Msg
updateDiceRollerCmd msg =
case msg of
Roll ->
Random.generate NewFace (Random.int 1 100)
NewFace newFace ->
Cmd.none
updateCmd : Msg -> Model -> Cmd Msg
updateCmd msg model =
Cmd.batch
[ updateDiceRollerCmd msg
, getSearchResult model.termInput
]
updateModel : Msg -> Model -> Model
updateModel msg model =
case msg of
Change newContent ->
{ model | content = newContent }
Name name ->
{ model | name = name }
Password password ->
{ model | password = password }
PasswordAgain password ->
{ model | passwordAgain = password }
MsgForDiceRoller msg ->
{ model | diceRoller = updateDiceRoller msg model.diceRoller}
SearchImages ->
model
NewSearchResult (Ok newResult) ->
{ model | termResult = newResult }
NewSearchResult (Err _) ->
model
ChangeTermInput term ->
{ model | termInput = term}
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
(updateModel msg model, updateCmd msg model)
It fails to compile with a type mismatch on the line Role in updateDiceRoller, because the pattern matches DiceRollerMsg, but it is trying to match Msg. If I just change the input and return types to DiceRollerMsg I get: Function updateDiceRollerCmd is expecting the argument to be: DiceRollerMsg But it is: Msg
Also I do not think that Cmd.batch in updateCmd is the best solution here.
I appreciate any input to making a better Elm app, also outside these questions.
Your compile errors originate from using Msg as input and return values for updateDiceRollerCmd while the case statement is using DiceRollerMsg. You can fix this function by pattern matching from, and mapping to, MsgForDiceRoller.
updateDiceRollerCmd : Msg -> Cmd Msg
updateDiceRollerCmd msg =
case msg of
MsgForDiceRoller Roll ->
Random.generate NewFace (Random.int 1 100)
|> Cmd.map MsgForDiceRoller
_ ->
Cmd.none
There is one more compile error in your view where you will need to change onClick Roll to onClick (MsgForDiceRoller Rool)

Elm - Get JSON List Data

I'm trying to get the JSON data list from this URL : https://raw.githubusercontent.com/raywenderlich/recipes/master/Recipes.json
I couldn't understand what to do in this situation
...
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias Model =
{ name : String
, imageURL: String
}
init =
(Model "" "", Cmd.none)
-- UPDATE
type Msg
= Recipes
| FetchSucceed (List Model)
| FetchFail Http.Error
update msg model =
case msg of
Recipes ->
(model, fetchRecipes)
FetchSucceed recipe ->
(recipe, Cmd.none)
FetchFail _ ->
(model, Cmd.none)
-- VIEW
view model =
div []
[ ul [] (List.map getItem model)
]
getItem item =
li [] [ text item.name ]
-- HTTP
fetchRecipes =
let
url =
"https://raw.githubusercontent.com/raywenderlich/recipes/master/Recipes.json"
in
Task.perform FetchFail FetchSucceed (Http.get decodeListRecipes url)
decodeRecipes =
Json.object2 Model
("name" := Json.string)
("imageURL" := Json.string)
decodeListRecipes =
Json.list decodeRecipes
But I keep getting this error :
Function `program` is expecting the argument to be:
{ ...,
update :
Msg
-> { imageURL : String, name : String }
-> ( { imageURL : String, name : String }, Cmd Msg ) ,
view : { imageURL : String, name : String } -> Html Msg
}
But it is:
{ ...
, update : Msg -> List Model -> ( List Model, Cmd Msg )
, view : List { a | name : String } -> Html b
}
Your FetchSucceed tag is defined as having a list of models (FetchSucceed (List Model)), but in your update function you are treating it as if it is a single model rather than a list. If I change the value to plural, it should emphasize the problem area:
FetchSucceed recipes ->
(recipes, Cmd.none)
Without knowing exactly what you are trying to achieve, I can only offer a hint at a potential solution, such as, if you merely wanted to take the first element of the list and fall back on the current model if no recipes are returned, you could do something like this:
FetchSucceed recipes ->
let recipe =
case List.head recipes of
Just r -> r
Nothing -> model
in
(recipe, Cmd.none)

Error with Maybe and nested resources in Elm

I'm building my very first Elm SPA, and I'm organizing my components in different folders and modules. Everything worked fine until I changed the main model from this:
type alias Model =
{ contacts : Contacts.Model.Model
, contact : Contact.Model.Model
, route : Routing.Route
}
To this:
type alias Model =
{ contacts : Contacts.Model.Model
, contact : Maybe Contact.Model.Model
, route : Routing.Route
}
I've made all the necessary changes in the codebase for this to work, but I'm missing something I can't find because in the main Update module I'm constantly getting this compile error:
The type annotation for `update` does not match its definition. - The type annotation is saying:
Msg
-> { ..., contact : Maybe Contact.Model.Model }
-> ( { contact : Maybe Contact.Model.Model
, contacts : Contacts.Model.Model
, route : Routing.Route
}
, Cmd Msg
)
But I am inferring that the definition has this type:
Msg
-> { ...
, contact :
{ birth_date : String
, email : String
, first_name : String
, gender : Int
, headline : String
, id : Int
, last_name : String
, location : String
, phone_number : String
, picture : String
}
}
-> ( { contact : Maybe Contact.Model.Model
, contacts : Contacts.Model.Model
, route : Routing.Route
}
, Cmd Msg
)
It looks like I'm missing to pass the Maybe Model somewhere but I can't find it. This is how the update function looks like:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ContactsMsg subMsg ->
let
( updatedContacts, cmd ) =
Contacts.Update.update subMsg model.contacts
in
( { model | contacts = updatedContacts, contact = Nothing }, Cmd.map ContactsMsg cmd )
ContactMsg subMsg ->
let
( updatedContact, cmd ) =
Contact.Update.update subMsg model.contact
in
( { model | contact = updatedContact }, Cmd.map ContactMsg cmd )
Here's the full repo, with the error: https://github.com/bigardone/phoenix-and-elm/tree/feature/routes-refactoring/web/elm
What am I doing wrong? Thank you very much in advance!
The problem is caused by a type mismatch in Update.update function.
update : Msg -> Model -> ( Model, Cmd Msg )
You have to process the Maybe value in your Update.elm:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ContactsMsg subMsg ->
let
( updatedContacts, cmd ) =
Contacts.Update.update subMsg model.contacts
in
( { model | contacts = updatedContacts, contact = Nothing }
, Cmd.map ContactsMsg cmd
)
ContactMsg subMsg ->
case model.contact of
Just contact ->
let
( updatedContact, cmd ) =
Contact.Update.update subMsg contact
in
( { model | contact = updatedContact }
, Cmd.map ContactMsg cmd
)
Nothing ->
( model, Cmd.none )