Advanced usage
Custom elements
How to bend Deku to do your bidding.
Sometimes, Deku's representation of DOM elements doesn't fit with how you need them to be used. In some cases, the DOM's components are not enough. When you need a bit more out of your DOM, Deku provides a backdoor to define whatever component you want while adding a modicum of type safety to the affair.
Defining custom elements
In this section, we’ll see how to take an element already in the DOM and give it a new alias through Deku.
The elementify function
In order to create a custom element, use the elementify
function. It's actually pretty safe all things considered, as the worst it can do is crash your app. Unless your app is controlling traffic lights or something, in which case it is definitely not safe.
As Deku has the whole DOM covered, there's not a single tag we can think of that isn't Deku-ified. So this method is only useful to create custom aliases for tags that have different polls or to add new tags if Deku is out of line with the DOM spec.
VITE_START=UnsafeCustomElement pnpm example
module Examples.UnsafeCustomElement where
import Prelude
import Deku.Toplevel (runInBody)
import Data.Maybe (Maybe(..))
import Deku.Control (elementify, text_)
import Deku.Core (Nut)
import Deku.DOM (Attribute, HTMLAnchorElement)
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Poll (Poll)
import Type.Proxy (Proxy)
import Deku.Toplevel (runInBody)
type HTMLMyNiftyAnchor (r :: Row Type) =
( __tag :: Proxy "HTMLMyNiftyAnchor"
| HTMLAnchorElement r
)
myNiftyAnchor
:: Array (Poll (Attribute (HTMLMyNiftyAnchor ()))) -> Array Nut -> Nut
myNiftyAnchor = elementify Nothing "a"
main :: Effect Unit
main = void $ runInBody do
myNiftyAnchor [] [ text_ "hi" ]
hi
Defining attributes
Things get more interesting when custom attributes are involved. By defining what attributes are settable for your custom element and how they should be marshaled into strings or listeners, you can create domain-specific logic for your DOM.
VITE_START=AddingCustomElements pnpm example
module Examples.AddingCustomElements where
import Prelude
import Deku.Toplevel (runInBody)
import Data.Maybe (Maybe(..))
import Deku.Attribute (Attribute)
import Deku.Control (elementify, text_)
import Deku.Core (Nut, attributeAtYourOwnRisk)
import Deku.DOM (HTMLAnchorElement)
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Poll (Poll)
import Type.Proxy (Proxy)
import Deku.Toplevel (runInBody)
type HTMLMyNiftyAnchor (r :: Row Type) =
( __tag :: Proxy "HTMLMyNiftyAnchor"
, href :: MyPages
, target :: MyTarget
| HTMLAnchorElement r
)
myNiftyAnchor
:: Array (Poll (Attribute (HTMLMyNiftyAnchor ()))) -> Array Nut -> Nut
myNiftyAnchor = elementify Nothing "a"
data MyPages = PureScript | MikeSolomonOrg
data MyTarget = Blank
myNiftyHref
:: forall r
. Poll MyPages
-> Poll (Attribute (href :: MyPages | r))
myNiftyHref = map
( attributeAtYourOwnRisk "class" <<< case _ of
PureScript -> "https://purescript.org"
MikeSolomonOrg -> "https://mikesolomon.org"
)
myNiftyTarget
:: forall r
. Poll MyTarget
-> Poll (Attribute (target :: MyTarget | r))
myNiftyTarget = map
( attributeAtYourOwnRisk "target" <<< const "_blank"
)
main :: Effect Unit
main = void $ runInBody do
myNiftyAnchor
[ myNiftyHref $ pure PureScript
, myNiftyTarget $ pure Blank
]
[ text_ "hi" ]
hi
An example using fake Discord messages
You can also use this method to work with components created by libraries like Stencil.js. In this section, we’ll bring stencil components from wc-discord-message
into Deku.
Importing our custom elements
Before using custom elements, make sure to import them using whatever instructions are given in the README.md of the library you’re using. In the case of wc-discord-message
, that'd be:
$ pnpm i wc-discord-message
And then:
import { applyPolyfills, defineCustomElements } from 'wc-discord-message/loader'
applyPolyfills().then(() => defineCustomElements(window))
You can also import the package via unpkg, which is what this documentation does. It's important to note that Deku can't help you with this. To initialize your components, you’ll still need to write some JavaScript or hire a consultant to.
Using our custom elements
We can define our Discord elements the same way we defined our custom anchor element above.
VITE_START=MockDiscord pnpm example
module Examples.MockDiscord where
import Prelude
import Deku.Toplevel (runInBody)
import Assets (beluMomURL, belugaURL)
import Data.Maybe (Maybe(..))
import Deku.Attribute (Attribute)
import Deku.Control (elementify, text_)
import Deku.Core (Nut, attributeAtYourOwnRisk)
import Deku.DOM (HTMLElement)
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Poll (Poll)
import Type.Proxy (Proxy)
import Deku.Toplevel (runInBody)
type HTMLDiscordMessage (r :: Row Type) =
( __tag :: Proxy "HTMLDiscordMessage"
, author :: String
, avatar :: String
| HTMLElement r
)
discordMessage
:: Array (Poll (Attribute (HTMLDiscordMessage ()))) -> Array Nut -> Nut
discordMessage = elementify Nothing "discord-message"
author
:: forall r
. Poll String
-> Poll (Attribute (author :: String | r))
author = map (attributeAtYourOwnRisk "author")
avatar
:: forall r
. Poll String
-> Poll (Attribute (author :: String | r))
avatar = map (attributeAtYourOwnRisk "avatar")
type HTMLDiscordMessages (r :: Row Type) =
( __tag :: Proxy "HTMLDiscordMessages"
| HTMLElement r
)
discordMessages
:: Array (Poll (Attribute (HTMLDiscordMessages ()))) -> Array Nut -> Nut
discordMessages = elementify Nothing "discord-messages"
main :: Effect Unit
main = void $ runInBody do
discordMessages []
[ discordMessage
[ author $ pure "beluga"
, avatar $ pure belugaURL
]
[ text_ "mom" ]
, discordMessage
[ author $ pure "belu-mom🌸"
, avatar $ pure beluMomURL
]
[ text_ "yes beluga" ]
, discordMessage
[ author $ pure "beluga"
, avatar $ pure belugaURL
]
[ text_ "whos my dad?" ]
, discordMessage
[ author $ pure "belu-mom🌸"
, avatar $ pure beluMomURL
]
[ text_ "it's complicated..." ]
]
mom yes beluga whos my dad? it's complicated...