This library wraps mdgriffith/elm-ui
to provide a global context available while building the view. If you're not familiar with elm-ui
, you should try it and only come back to this library when you have a problem to solve.
A context is a global, constant or mostly constant object. It can be used to store those things that you will need almost everywhere in your view
but don't change often, or at all.
Examples of things you could want to put in the context:
- theme (dark/light/custom) - this is needed almost everwhere for colors, and styles, and changes very rarely;
- language - this is needed for every single label for localization, and changes rarely or never;
- timezone - this is needed to display local times for the user, and mostly doesn't change;
- responsive class (phone/tablet/desktop) - this doesn't usually change (unless the user dramatically resizes the window);
- user permissions (to disable buttons or inputs) - this changes very rarely and is needed in a lot of places.
Example of things that you do not want in the context:
- time - this changes constantly;
- window size - on desktop, this can change a lot while resizing;
- user ID - this should be part of your regular model.
A good test for inclusion is to think of this: does it make sense to completely redraw the user interface when the value changes? In particular, changing anything in the context will force the recalculation of all the lazy
nodes.
- Define a
Context
type (it will usually be a type alias); - replace any
import Element
and anyimport Element.X as X
with:import Element.WithContext as Element import Element.WithContext.X as X
- don't expose
Element
orAttribute
in theimport
, but instead define your type aliases:type Element msg = Element.Element Context msg type Attribute msg = Element.Attribute Context msg type Attr decorative msg = Element.Attr Context decorative msg
- pass the context to
Element.layout
; - everything should work as before, but now you can use
with
andwithAttribute
to access your context.
A nice way to do localization is to completely avoid exposing text
from Element.WithContext
, and instead defining your custom one like this:
type Language
= En
| It
| Fr
type alias L10N a =
{ en : a
, it : a
, fr : a
}
text : L10N String -> Element { a | language : Language } msg
text { en, it, fr } =
Element.with
(\{ language } ->
case language of
En ->
en
It ->
it
Fr ->
fr
)
Element.text
So that you can use it like this: text { en = "Hello", it = "Ciao", fr = "Bonjour" }
(you should also probably move all the localized strings into a Localization
package).
This has the advantage of keeping a nice API while making it (almost) impossible to have untranslated labels.
Notice how text
simply requires a context that includes a language
field, so is very generic.
This tecnique can be adapted for image sources, title texts, and anything that needs localization.
Strings with placeholders can be represented as L10N (a -> b -> String)
and used by defining an apply : L10N (a -> b) -> a -> L10N b
. Beware: different languages can have very different rules on plurals, genders, special cases, ...
If you have a field theme : Theme
in your context then you can replace any color constants in your code with Theme -> Color
functions, and use them like this:
type Theme
= Light
| Dark
fontColor : Theme -> Color
fontColor theme =
case theme of
Light ->
rgb 0.2 0.2 0.2
Dark ->
rgb 0.8 0.8 0.8
someViewFunction =
el
[ Element.withAttribute
(\{theme} -> fontColor theme)
Font.color
]
(text "Hello")
(\{theme} -> fontColor theme
can be replaced by .theme >> fontColor
, depending on taste).
This also has the advantage that you can "force" a particular theme in places that need it, like the theme picker, by just doing fontColor Light
.
Element msg
becomesElement context msg
andAttribute msg
becomesAttribute context msg
,layout
requires acontext
parameter,- you have access to the
with
andwithAttribute
functions.
You should also have a look at the original README.