This library wraps elm/html
to provide a global context available while building the view.
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 everywhere 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;
- user ID - this only changes on login/logout.
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.
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 Html
and anyimport Html.X
with:import Html.WithContext as Html import Html.WithContext.X
-
don't expose
Html
orAttribute
in theimport
, but instead define your type aliases:type Html msg = Html.Html Context msg type Attribute msg = Html.Attribute Context msg
-
pass the context to
Html.toHtml
; -
everything should work as before, but now you can use
withContext
andwithContextAttribute
to access your context.
A nice way to do localization is to completely avoid exposing text
from Html
, 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 -> Html { a | language : Language } msg
text { en, it, fr } =
Html.withContext
(\{ language } ->
case language of
En ->
Html.text en
It ->
Html.text it
Fr ->
Html.text fr
)
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 technique 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 -> String
fontColor theme =
case theme of
Light ->
"#333"
Dark ->
"#ddd"
someViewFunction =
div
[ Html.withContextAttribute <| \{theme} -> Attribute.style "color" <| fontColor theme ]
(text "Hello")
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
.
Html msg
becomesHtml context msg
andAttribute msg
becomesAttribute context msg
,- added the
toHtml
function, - you have access to the
withContext
andwithContextAttribute
functions.
You should also have a look at the original README.