I have an elm 0.18 web app with a number of pages and routes. In main.elm I define my update function.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FirstUpdateAction ->
...
Every action goes through this function and it's getting big. Is it possible to create an update function to a smaller module that is nested within the overall structure?
For example, I have a settings page that gives the user the ability to change password. There are three fields/states (passwordOld, passwordNew, passwordConfirm) which have update actions associated with onInput and onBlur events. Those states and actions are only relevent to the user settings page, and become irrelevent to the rest of the model when the user leaves the page.
How could I go about setting up a scope for the user settings?
You could break down your code into independent submodules, each with it's own Msg type, update and view functions.
For example you could have a file SubmoduleA.elm looking like this:
module SubmoduleA exposing (Model, Msg, update, view)
type Msg = SubMessageA
| SubMessageB
[..]
type alias model =
{ fieldA : TypeA
, fieldB : TypeB
, [..]
}
update msg model =
case msg of
MessageA ->
{model | fieldA = [..] } ! []
[..]
view model =
div [id "submoduleAView"]
[..]
this module would be connected to your main program like this:
module Main exposing (..)
import SubmoduleA exposing (Model, Msg, update, view)
type Msg = MessageA
| MessageB
| ToSubmoduleA (SubmoduleA.Msg)
[..]
type alias model =
{ fieldA : TypeA
, fieldB : TypeB
, [..]
, subModuleA : SubmoduleA.Model
}
update msg model =
case msg of
MessageA ->
{model | fieldA = [..] } ! []
[..]
ToSubmoduleA msg =
let (newSubmoduleA, newSubmoduleACmd) = SubmoduleA.update msg (.subModuleA model)
in { model | subModuleA = newSubmoduleA } ! [Cmd.map ToSubmoduleA newSubmoduleACmd]
view model =
div [id "mainView"]
[ ..
, Html.map ToSubmoduleA <| SubmoduleA.view (.subModuleA model)
]
this way all the information and state that are relevant to your sub module stay encapsulated in your sub module, and you just have one case in your main update function responsible for the correct routing of messages.
Related
There are couple apples ( in type of List ) which will expose themselvies in the web view. User can update any size attribute of an Apple. I have a msg type UpdateSize which will be triggered via onInput.
Editing any of the apples will only just trigger the message without knowing which apple to be updated.
Is that possible to pass an id attribute to UpdateSize message?
Thank you for reading this, Elm is great !
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick,onInput)
import String
type alias Apple = {
size: Int}
type alias Model = {
apples: List(Apple)}
initialModel : Model
initialModel =
{ apples = [ Apple 10, Apple 11, Apple 12] }
type Msg
= UpdateSize String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateSize s -> {model | apples = ??? } -- how to update a single Apple with new size
_ -> model
viewApple : Apple -> Html Msg
viewApple a =
input [ type_ "text" ,placeholder ""
, value (String.fromInt a.size)
, onInput UpdateSize]
[]
view : Model -> Html Msg
view model =
div []
(List.map viewApple model.apples)
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
Code link: https://ellie-app.com/ghd9jrcjKQQa1
With your current implementation it's not possible to know which apple to update since there's no unique attribute about the apples. What if two apples have the same size? If would be better if apples had IDs, or you used a dictionary type to keep track of the apples.
However, for the sake of demonstration, you could say that the list indeces of the apples are unique and you find them accordingly. In real life this will be a fragile solution.
Here's a naive approach using some helper functions from List.Extra.
-- ...
type alias Size =
Int
type Msg
= UpdateSize Int String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateSize index sizeStr ->
let
maybeSize =
String.toInt sizeStr
in
maybeSize
|> Maybe.withDefault (\size -> { model | apples = updateApple index size model.apples })
|> model
_ ->
model
updateApple : Int -> Size -> List Apple -> List Apple
updateApple index size apples =
let
maybeApple =
List.Extra.getAt index apples
in
maybeApple
|> Maybe.map (\apple -> List.Extra.setAt index { apple | size = size } apples)
|> Maybe.withDefault apples
-- ...
viewApple : Int -> Apple -> Html Msg
viewApple index a =
input
[ type_ "text"
, placeholder ""
, value (String.fromInt a.size)
, onInput (UpdateSize index)
]
[]
view : Model -> Html Msg
view model =
div []
(List.indexedMap viewApple model.apples)
New to Elm, so I may be missing something obvious.
I'm working on an Elm application that uses annaghi/dnd-list. I'm encountering an infinite loop of calls to update. This happens when clicking on one element, then another one. Here's the code:
config : DnDList.Config Player
config =
{ beforeUpdate = \_ _ list -> list
, movement = DnDList.Free
, listen = DnDList.OnDrag
, operation = DnDList.Swap
}
system : DnDList.System Player Msg
system =
DnDList.create config DndMsg
type alias Model =
{ navKey : Nav.Key
, room : WebData Room
, dnd : DnDList.Model
, startError : Maybe String
}
type Msg
= RoomReceived (WebData Room)
| DndMsg DnDList.Msg
...
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
RoomReceived room ->
( { model | room = room }, Cmd.none )
DndMsg message ->
let
room = model.room
in
case room of
RemoteData.Success actualRoom ->
let
( dnd, players ) =
system.update message model.dnd actualRoom.players
updatedRoom = RemoteData.map
(\roomData ->
{ roomData | players = players }
) room
in
( { model | dnd = dnd, room = updatedRoom }
, system.commands model.dnd
)
_ ->
( model, Cmd.none )
When I change the line system.commands model.dnd to Cmd.none, then there is no infinite looping call to the update function, but also nothing happens. The message that keeps getting called in the dnd-list library is GotDropElement (Ok dropElement)
Again, new to Elm, so this may be a poorly formed question, but any help is appreciated.
Thanks!
Figured it out. Had to add a subscription to listen to mouse events
currentSubs : Model -> Sub Msg
currentSubs model =
case model.page of
GameRoomPage pageModel ->
GameRoom.subscriptions pageModel
|> Sub.map GameRoomMsg
_ ->
always Sub.none model
...
main : Program () Model Msg
main =
Browser.application
{ view = view
, init = init
, update = update
, subscriptions = currentSubs
, onUrlRequest = LinkClicked
, onUrlChange = UrlChanged
}
Try following the flow from system.commands. Probably it is eventually sending again the message DndMsg and this is what is causing the issue.
It is usually considered not a good practice to send messages from commands.
In case you cannot solve the issue, having a working example of the problematic code in Ellie (https://ellie-app.com/new) would help.
I am trying to partition my Msg values in Elm (0.18) into different types. I'd like it to conform to this kind of typing:
type MsgSession
= LogIn
| LogOut
type MsgSettings
= SetUsername String
= SetPassword String
type Msg
= SessionMsg MsgSession
| SettingsMsg MsgSettings
...
My update function looks like this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SessionMsg sessionMsg ->
sessionUpdate sessionMsg model
SettingsMsg settingsMsg ->
settingsUpdate settingsMsg model
...
...which means that I have to define the child updates as well...
sessionUpdate : MsgSession -> Model -> ( Model, Cmd Msg )
sessionUpdate msg model =
case msg of
LogIn ->
-- do stuff
LogOut ->
-- do stuff
My LogOut event looks like this, and works fine:
button [onClick (SessionMsg LogOut)] [text "Log Out"]
But once there is a variable involved with the event, it doesn't work. I have a similar setup for settingsUpdate:
settingsUpdate : MsgSettings -> Model -> ( Model, Cmd Msg )
settingsUpdate msg model =
case msg of
SetUsername string ->
...
SetPassword string ->
...
But I can't get onInput to send as a variable. For example, this code
input [onInput SettingsMsg SetUsername] []
Yields this error:
Function `onInput` is expecting 1 argument, but was given 2.
483| onInput SettingsMsg SetUsername
Parentheses also don't work.
input [onInput (SettingsMsg SetUsername)] []
yields
The argument to function `SettingsMsg` is causing a mismatch.
483| SettingsMsg SetUsername)
^^^^^^^^^^^
Function `SettingsMsg` is expecting the argument to be:
MsgSettings
But it is:
String -> MsgSettings
What do I have to pass into onInput to make this work?
You should be able to use composition:
input [onInput (SettingsMsg << SetUsername)] []
Which, if you're more comfortable with explicit lambdas, looks like this:
input [onInput (\name -> SettingsMsg (SetUsername name))] []
I've finished the Elm guide and noticed on very simple examples, the update function grows to 3 cases and the Msg type can have 3 constructors. I imagine on an intermediate project, this would grow to 20 and on an advance project, it might be hundreds. How do you manage this? I foresee this being a source of version control contention if every developer needs to add a new constructor for their feature.
I worked on a react-redux project and it has a concept of combining reducers to solve this problem. I did not run across that concept in Elm. Does it have one?
You can define msg type consists of child/sub msg types, and of course, updater can be combined with sub functions. ie.
-- Counter
type CounterMsg
= Increment
| Decrement
type alias CounterModel =
Int
updateCounter : CounterMsg -> CounterModel -> ( CounterModel, Cmd msg )
updateCounter msg model =
case msg of
Increment ->
( model + 1, Cmd.none )
Decrement ->
( model - 1, Cmd.none )
-- Todo
type TodoMsg
= AddTodo String
type alias TodoModel =
List String
updateTodo : TodoMsg -> TodoModel -> ( TodoModel, Cmd msg )
updateTodo msg model =
case msg of
AddTodo str ->
( str :: model, Cmd.none )
-- unified
type alias Model =
{ counter : CounterModel
, todos : TodoModel
}
type Msg
= Counter CounterMsg
| Todo TodoMsg
initModel =
{ counter = 0, todos = [] }
update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
case Debug.log "message" msg of
Counter countermsg ->
let
( newmodel, cmd ) =
updateCounter countermsg model.counter
in
( { model | counter = newmodel }, cmd )
-- etc...
_ ->
( model, Cmd.none )
Take a look at Richard's implementation for RealWorld/Conduit. It provides a realistic way to structure a large enough app (few thousands lines of code).
In short, on complex projects there is the idea of a Page that can have its own model and update and view.
Within each page you could have a large Msg but that is not really an issue. 20 tags is actually quite manageable. 50 is also manageable as discovered by NoRedInk programmers in their production code.
There's a decent tutorial on the topic here: https://www.elm-tutorial.org/en-v01/02-elm-arch/07-composing-2.html
I wish it showed the source of the Widget, but I can imagine what it looks like. Inlining for posterity.
module Main exposing (..)
import Html exposing (Html, program)
import Widget
-- MODEL
type alias AppModel =
{ widgetModel : Widget.Model
}
initialModel : AppModel
initialModel =
{ widgetModel = Widget.initialModel
}
init : ( AppModel, Cmd Msg )
init =
( initialModel, Cmd.none )
-- MESSAGES
type Msg
= WidgetMsg Widget.Msg
-- VIEW
view : AppModel -> Html Msg
view model =
Html.div []
[ Html.map WidgetMsg (Widget.view model.widgetModel)
]
-- UPDATE
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
subscriptions : AppModel -> Sub Msg
subscriptions model =
Sub.none
-- APP
main : Program Never AppModel Msg
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
I think this is the same idea behind https://stackoverflow.com/a/44275318/61624 but it has more description.
I'm new to elm (0.17) and I try to understand how it works. In this case, I try to develop a kind of project estimation.
This is what I did:
import Html exposing (..)
import Html.App as Html
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- model
type alias Host = {
name : String,
cost : Int
}
type alias Model =
{ email : String
, hosting : List Host
, period : List Int
, interventionDays : List Int
, total : Int
}
init : (Model, Cmd Msg)
init =
(Model "init#email.fr" [{name="AAA", cost=15}, {name="BBB", cost=56}, {name="CCC", cost=172}] [1..12] [1..31] 0, Cmd.none)
type Msg = Submit | Reset
calculate : Int
calculate = 42 -- to test
update : Msg -> Model -> (Model, Cmd Msg)
update action model =
case action of
Submit ->
(model, calculate)
Reset ->
(model, Cmd.none)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- view
hostOption host =
option [ value (toString host.cost) ] [ text host.name ]
durationOption duration =
option [value (toString duration) ] [ text (toString duration)]
view : Model -> Html Msg
view model =
Html.form []
[ h2 [] [ text "Estimate your project"]
, input [ placeholder model.email ] []
, select []
(List.map hostOption model.hosting)
, select []
(List.map durationOption model.period)
, select []
(List.map durationOption model.interventionDays)
, Html.span [][text (toString model.total)]
, button [onClick Submit] [text "Submit"]
, button [onClick Reset] [text "Reset"]
]
I think I have understood some ideas behind elm but I need help because elm-make command returns:
The 1st and 2nd branches of this `case` produce different types of values.
40| case action of
41| Submit ->
42| (model, calculate)
43| Reset ->
44|> (model, Cmd.none)
The 1st branch has this type:
( a, Int )
But the 2nd is:
( a, Cmd a )
Hint: All branches in a `case` must have the same type. So no matter which one
we take, we always get back the same type of value.
Detected errors in 1 module.
I understand the problem but I do not know how to fix it. Do I have to define my calculate function to work with model data ?
Thanks
I'm going to guess that you want to update the the total field of your model with calculate.
The first tuple item that the update function returns is the updated model. As things stand, both of your actions return the existing model without changing it. So you could try this:
case action of
Submit ->
({ model | total = calculate }, Cmd.none)
Reset ->
init
See here for the syntax for updating records.
Note that I also changed the Reset branch to return init, the initial model and command.
The compiler error is telling you that the update method, in some cases will return a (Model, Cmd) tuple, and in another cases will return a (Model, Int) tuple.
The update function as you have it, should return the modified model and also a Cmd to execute an action, in other words, a (Model, Cmd) tuple.
If you return (model, calculate) it will return a (Model, Int) tuple, since calculate is an Int. That is what is breaking the compiling.
So to fix it, first you need to decide what to do with each of the Msg. I assume by the name of them that the Calculate Msg will update the total and the Reset Msg will set the model to the default state.
For that you could do:
case action of
Submit ->
({ model | total = calculate }, Cmd.none)
Reset ->
init
In this case, both branches will return a tuple of type (Model, Cmd).
Note that the Reset branch will return init, which is already of type (Model, Cmd).
Check the official guide for more examples: http://guide.elm-lang.org/index.html