The extensible records approach seems fine because when using the tab views you don’t have to think about what data each tab needs, you just pass the model.
With regards to Messages.elm
that’s where you’d want to be careful because it leads to the anti-pattern as well. The way you avoid creating Messages.elm
is by abstracting the message creation in your views.
For e.g.
-- Main.elm
FirstTab.view model IncrementPar
-- Tabs.FirstTab.elm
view
: { a | parameters : Dict String Int, language : Int, device : Element.DeviceClass }
-> (String -> msg)
-> Element.Element msg
view { parameters, language, device } onIncrement =
let
getPar key =
Maybe.withDefault 0 <| Dict.get key parameters
in
Element.column [ Element.centerX, Element.spacing 10 ]
[ Element.row []
[ Element.text <| "Parameter 1: " ++ (String.fromInt <| getPar "par1")
, Input.button [] { onPress = Just <| onIncrement "par1", label = Element.text "+" }
]
, Element.row
[]
[ Element.text <| "Parameter 2: " ++ (String.fromInt <| getPar "par2")
, Input.button [] { onPress = Just <| onIncrement "par2", label = Element.text "+" }
]
, showDeviceClass device
, showLanguage language
]
Notice the use of msg
instead of Msg
. If you use msg
instead of Msg
for all your tabs then the Msg
type can stay in Main.elm
.
The way to think about it is to ask yourself:
- What data does my tab need? The answer to this is what you put in the extensible record.
- What messages can the tab generate? The answer to this is the “callbacks” you pass,
onIncrement
in the example above.
Another example:
-- Main.elm
SecondTab.view model ChangeLanguage IncrementPar
-- Tabs.SecondTab.elm
view
: { a | parameters : Dict String Int, language : Int, device : Element.DeviceClass }
-> msg
-> (String -> msg)
-> Element.Element msg
view { parameters, language, device } onChangeLanguage onIncrement =
let
getPar key =
Maybe.withDefault 0 <| Dict.get key parameters
in
Element.column [ Element.centerX, Element.spacing 10 ]
[ Element.row
[]
[ Element.text <| "Change Language: " ++ String.fromInt language
, Input.button [] { onPress = Just onChangeLanguage, label = Element.text "change" }
]
, Element.row
[]
[ Element.text <| "Parameter 3: " ++ (String.fromInt <| getPar "par3")
, Input.button [] { onPress = Just <| onIncrement "par3", label = Element.text "+" }
]
, showDeviceClass device
, showLanguage language
]
If you don’t want to think about the callbacks needed for each specific tab then you can use extensible records again. So for the two examples above you’d do the following:
-- Main.elm
type Msg
= ChangeTab Int
| ChangeText String
| ChangePassword String
| ChangeLanguage
| IncrementPar String
callbacks =
{ onChangeLanguage = ChangeLanguage
, onIncrement = IncrementPar
}
FirstTab.view model callbacks
SecondTab.view model callbacks
-- Tabs.FirstTab.elm
view
: { a | parameters : Dict String Int, language : Int, device : Element.DeviceClass }
-> { a | onIncrement : String -> msg }
-> Element.Element msg
view { parameters, language, device } { onIncrement } = ...
-- Tabs.SecondTab.elm
view
: { a | parameters : Dict String Int, language : Int, device : Element.DeviceClass }
-> { a | onChangeLanguage : msg, onIncrement : String -> msg }
-> Element.Element msg
view { parameters, language, device } { onChangeLanguage, onIncrement } = ...