Core concepts
Components
Learn how to make a static page with Deku.
In the Hello world example, we saw how text ("Hello world")
got transformed into something your eyeballs oggled or screenreader scraggled in the DOM. In Deku, as in most other frameworks from which Deku's ideas are liberally borrowed, we call these Components.
A simple component
Let's start by making a simple comonent. It will result in a few different DOM elements being rendered, and we’ll build upon it throughout this page. Here's the code.
VITE_START=ASimpleComponent pnpm example
module Examples.ASimpleComponent where
import Deku.Toplevel (runInBody)
import Effect (Effect)
import Prelude
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)
import Deku.Control (text_)
import Deku.Core (Nut)
import Deku.DOM as D
import Deku.Toplevel (runInBody)
main :: Effect Unit
main = void $ runInBody mySimpleComponent
where
-- `Nut` is the type of Deku components
mySimpleComponent :: Nut
mySimpleComponent =
D.div_
[ D.span__ "I exist"
, D.ul_ $ map D.li__ [ "A", "B", "C" ]
, D.div_
[ D.h3__ "foo"
, D.i__ "bar"
, text_ " "
, D.b__ "baz"
]
]
I exist
- A
- B
- C
foo
bar baz
Before we proceed, it's important to establish a bit of Deku terminology that will come in handy as you shred through this documentation.
- An element is a DOM element in the MDN Documentation on Elements sense of the term. We will learn how to hook into the raw DOM in the Accessing the DOM section.
- A component is a PureScript term with type
Nut
, named affectionately after Deku nuts.Nut
is the type produced byD.div_
,D.span_
and the other DOM-building instructions above. But the concept of a component is broader than a one-to-one relationship with DOM elements - it can also represent the absence of DOM elements usingmempty
(which we’ll learn about later) or multiple DOM elements usinguseDyn
(which we’ll also learn about later).
What's in D?
Compared to the imports from Hello world, there's one additional import here worth noting.
import Deku.DOM as D
The Deku.DOM
module is the kitchen sink into which the entire DOM is thrown. You can find your favorites like D.li_
, D.h3_
, and D.blockquote_
. You can even find exotic ones like the oft-overlooked D.colgroup_
and the pugnacious D.noscript_
. Basically, if it's in the DOM, it's in Deku, and if you can't find what you’re looking for, you’ll learn to add it in the Custom elements section.Arrays all the way down
Let's revisit the previous example, but we’ll expand it array-by-array to show how the Deku DOM is organized. The example starts with a single enclosing div
created by the instruction D.div_
.
main :: Effect Unit
main = runInBody
( D.div_
[ ...
]
)
The div
contains three elements, a span
, a ul
, and another div
.
[ D.span_ [ ... ]
, D.ul_ $ map D.li__ [ ... ]
, D.div_
[ ...
]
]
You can have as many nested elements as you want. Deku makes no effort to check that your DOM's sensible. While a span
in a button
in a div
makes sense, a div
in a button
in a span
does not. But we won't stop you from doing it!
Malformed DOM
Arrays all the way down means arrays all the way down, including elements that can't have children. For example, <br />
can never take any children, and yet in Deku it is written D.br_ []
. This is done because there are rare cases where you want to emit a malformed DOM, like when teaching someone how not to write code. In general, though, make sure to leave arrays empty for elements like <br />
that don't take any elements.
Lastly, we saw that the ul
is created from mapping over strings in order to create Deku li
components.
D.ul_ $ map D.li__ [ "A", "B", "C" ]
Deku is designed to allow you to use garden-variety functional programming patterns, like mapping over arrays, to build your DOM. In fact, that's how this documentation is built: all of the text you’re reading is stored in structures that Deku marshalls into the DOM.
Adding text
Text in deku is created via the text
function. Additionally, for elements that only contain text, you can use a double-underscore. For example, D.i__ "Pow!"
Pow!
Adding attributes
What fun would HTML be without any attributes? Un-fun, like the example above. Let's fix that and make our DOM nice and stylish! We'll use the same example as before, but spiced up with attributes.
VITE_START=AddingAttributes pnpm example
module Examples.AddingAttributes where
import Deku.Toplevel (runInBody)
import Effect (Effect)
import Prelude
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)
import Deku.DOM.Attributes as DA
import Deku.Control (text_)
import Deku.DOM as D
import Deku.Toplevel (runInBody)
main :: Effect Unit
main = void $ runInBody
( D.div_
[ D.span
[ DA.style_ "color:teal;" ]
[ text_ "I exist" ]
, D.ul_ $ map D.li__ [ "A", "B", "C" ]
, D.div_
[ D.h3
[ DA.id_ "my-id"
, DA.style_ "background-color:silver;"
]
[ text_ "foo" ]
, D.i [ DA.klass_ "text-2xl" ] [ text_ "bar" ]
, text_ " "
, D.b__ "baz"
]
]
)
I exist
- A
- B
- C
foo
bar baz
Removing the underscore
To add attributes to a Deku DOM element, remove the underscore(s) from the element. Let's start with the span
from the example above.
D.span__ "I exist"
When we added the style attribute to make the color teal, we dropped the underscores. Furthermore, we added a text
to typeset the text.D.span
[ DA.style_ "color:teal;" ]
[ text_ "I exist" ]
Expand the text
D.span
above, make sure to also add an array with text_
followed by your text inside of it. This is because D.span__ "foo"
is shorthand for D.span_ [ text_ "foo" ]
.Setting attributes
Individual attributes like style
or href
are set with the DA.style_
or DA.href_
functions.
DA.style_ "color:teal;"
To feed an attribute to Deku, it needs to be in an array. Several attributes can be set by including them all in an array.
[ DA.id_ "my-id"
, DA.style_ "background-color:silver;"
]
¿Sì? Yes Yes!
PureScript has a delightful type-safe CSS library called purescript-css
that you can use to author CSS, which is easier to debug than string-based styles.
import Deku.CSS as CSS
import CSS (color, red, display, block)
myStyleString = CSS.render do
color red
display block
Adding event handlers
The web would be quite uneventful without events. Be they clicks or scrolls or hovers, Deku is your one-stop-shop for event planning!
Using an effect
The most straightforward event is that which triggers an effectful action, like an alert or an audio snippet.
VITE_START=UsingAnEffect pnpm example
module Examples.UsingAnEffect where
import Prelude
import Deku.Toplevel (runInBody)
import Deku.Control (text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Web.HTML (window)
import Web.HTML.Window (alert)
import Deku.Toplevel (runInBody)
main :: Effect Unit
main = void $ runInBody do
D.span
[ DL.click_ \_ ->
window >>= alert "Thanks for clicking!"
, DA.klass_ "cursor-pointer"
]
[ text_ "Click me!" ]
Click me!
Do be do be do
Functional programmers are notoriously lazy. In fact, there are entire languages, like Haskell, that are lazy. Part of our laziness is a disdain for parentheses, which not only requries two additional keystrokes, but forces us to press Shift at the same time. The gaul!
As a result, we often use do
to separate blocks that would otherwise be in parentheses. do
also pulls double duty (or do
ty if you’re into puns) and is used to write sequential computations. But when there's only one computation, like in the example above, it acts like parentheses. If you think it's unfair that one linguistic construct can do
multiple things at the same time, try learning how many things the word です means in Japanese. It makes do
look tame by comparison.
Using the DOM event
To use the original event emitted by the DOM, you can use the following syntax.
VITE_START=UsingTheOriginalEvent pnpm example
module Examples.UsingTheOriginalEvent where
import Prelude
import Deku.Toplevel (runInBody)
import Data.Newtype (unwrap)
import Deku.Control (text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Web.Event.Event (type_)
import Web.HTML (window)
import Web.HTML.Window (alert)
import Web.PointerEvent.PointerEvent (toEvent)
import Deku.Toplevel (runInBody)
main :: Effect Unit
main = void $ runInBody do
D.span
[ DL.click_ \e -> do
window >>= alert (unwrap (type_ $ toEvent e))
, DA.klass_ "cursor-pointer"
]
[ text_ "Click me!" ]
Click me!
Combinators, enums, and a third thing - oh my!
Often times, you won't want to use the full power of a listener but rather a shorthand version. For example, you may not care about the incoming event, or you may only want to grab a Number
from said event. In these cases, Deku boasts a small and mighty library of listener cominators.
Combinator | Meaning |
---|---|
DL.runOn | Use this listener with a simple effect |
DL.numberOn | Grab a number from this listener, and if there's no number, do nothing. |
DL.checkedOn | Grab a boolean from this listener, and if there's no boolean, do nothing. |
In addition, there are also some attributes that act more like enums. This is especially true for the type
attribute of the input
element.
Attribute | Meaning |
---|---|
DA.xtypeRange | Make an |
DA.xtypeButton | Make an |
The table above goes on for all valid input types.
I know this section heading promised a third thing, but there is no third thing. It feels like there should be, though, right? Like in this monologue.
As an example, consider the following slider. If you open your Developer Console and move the slider below, you’ll see it light up with numbers!
VITE_START=ShorthandListeners pnpm example
module Examples.ShorthandListeners where
import Prelude
import Deku.Toplevel (runInBody)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import Effect.Class.Console (logShow)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)
main :: Effect Unit
main = void $ runInBody
(D.input [ DA.xtypeRange, DL.numberOn_ DL.input logShow ] [])
While these events are fun, the true power of Deku lies in its ability to use events to change aspects of the DOM. The next section explores how that is done.