Thanks, for showing an interest. I will try to answer questions as best as I can.
Could you describe what the right structure is in your case? Are you doing child to parent communication and if yes, how?
I found that deeply nested child to parent communication just complicates things and it becomes harder to refactor. Therefore, I would not use more than two tier of nesting. Top Tier TEA components act as proxies and all the logic resides in the Bottom Tier. This is how the folder structure looks like:
- Type1
// Bottom Tier
- Messages.elm
- Model.elm
- Update.elm
- View.elm
// Helpers
- Page.elm
- API.elm
- Remote.elm
- Decoder.elm
- Encoder.elm
...
- Type2
...
// Top Tier
Messages.elm
Model.elm
Update.elm
View.elm
// Helpers
Encoder.elm
Decoder.elm
...
// Main
Main.elm
-
A Type can be domain centered such as User, Blog, Article or smaller ones such as Date, Decimal, Avatar, or view centered such as Header, Footer, Dropdown, or more functional such as Sort, Filter, Route.
-
New Types are introduced when current Types start to share the same code, e.g. whether that would be a model, view or update.
-
Not all Types has all three TEA components. In many cases, they start very innocently, with just one Model or a View module, but with a time they mature. E.g.Header type may have only a View but later on, we may add a dropdown, and for that, it needs Model to contain a dropdown, Messages and an Update. Another example is Date type, which initially is just a Model, but if some Views in Types starts to share the same code involving Date, then we create a View module in the Date.
-
Helpers splits Update, Model and a View in respective tiers into smaller modules. E.g Model is split into Page and API. Page contains models from other types such as dropdown, pagination etc. and functions to update it. API contains HTTP results and commands to generate them. An update is split into Decoder, Encoder, the same can be said about Views.
-
Most of the function signatures in Model and helpers are in this format: Model -> a
, and 99% they are used to update or transform a Model in respective Tier.
-
Update module functions in both tiers has this signature: BottomMsg -> TopModel -> (TopModel, Cmd Msg)
. In other words, every Update function in Bottom tier updates a Top tier Model based on the Bottom Tier msg. By allowing every Update module to access global data, we increase the extensibility of that module and avoid refactoring function signature when the module grows. This would be very bad in a language with mutable data, but in Elm this is not a case.
-
Initially Views in the Bottom tier has this signature: BottomModel -> View msg
. But as they mature they end up with this TopModel -> View Msg
signature.
-
Messages module contains messages for the respective tier. And in most cases, Bottom tier module contains only one, two or three messages. This is achieved,
- By passing a whole Bottom tier Model into a message, instead of fields for that Model. This has a big advantage of reducing the size of the update function, especially if we are updating TopModel which data is hidden by union types. The disadvantage is that if we are not careful, our Views can start to contain complicated logic, which is a big no-no. That’s why the logic for updating the Model resides in each Model respectively. In most cases, they are just Lenses. Views use them to create a message with updated Model.
- By having lots of small types. This again, allows us to maintain and extend the application more easily. One disadvantage of this is that Top tier modules grow fast and becomes big. But because they act as proxies, and doesn’t contain any logic, that doesn’t matter.
I am curious to know if your types in elm are more or less the same structure as your json or do you use a lot of custom types?
If I could guess, I would say that
- 70% of all the types in the application are custom made such as Route, Dropdown, Pagination, Query etc.
- 15% corresponds to JSON such as User, Order etc.
- Because I am a big fan of Union types, 15% of types created for JSON are union types, E.g State, Compliance Level, for the User. And these types are the biggest pain to decode and to write modules for them. Especially if you want to make them opaque.
Are you using any tools to generate the json decoders? From the backend, in the editor or with QuickType?
No. Due to fact that I use a lot of Custom opaque types, these tools are not very useful. So I write them manually. The good thing is that, due to the architecture of the app, those Decoders are very composable.
Is there any reason stopping you from porting to 0.19, except free time?
Not particularly. Although, I have lots of svg strings embedded using innerHtml
, so converting them in Svg will be a pain in the a**. In addition, I had doubts about migrating due to extensive usage of Highcharts.js, but libraries such as elm-visualization should help me.
What is the stack used, and what you used to develop the views, elm-css, elm-ui or plain css?
For views haven’t used any library, due to high customization requirements. As for CSS I have tried both elm-css and elm-ui. Both are exceptional libraries, unfortunately, any CSS library written in Elm suffers from the same problem - slow compilation. It just takes too long to compile and see the changes. Event 2s delay impacts productivity. Well at least in 0.18 version. Therefore, I use SASS. Due to the fact that SASS mixins are highly composable, they can correspond to Elm views. So if you have a Header view which contains a Button view in Elm then you have Header mixin which contains Button mixin in the SASS.