How can I render a specific purescript-halogen component into the <head> tag?
The following example written for Halogen 1.0.0 renders the stylesheet and the paragraph into the HTML body:
module Main where
import Prelude
import Control.Monad.Eff (Eff)
import Data.Maybe (Maybe(Nothing))
import CSS as C
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.CSS as HS
import Halogen.Query.HalogenM as HQ
import Halogen.VDom.Driver as HV
styles :: forall p i. HH.HTML p i
styles = HS.stylesheet $
C.select C.body $ C.margin C.nil C.nil C.nil C.nil
content :: forall p i. HH.HTML p i
content = HH.p_ [ HH.text "Test" ]
main :: Eff (HA.HalogenEffects ()) Unit
main = HA.runHalogenAff $ HA.awaitBody >>= HV.runUI ui unit
where
ui = H.component { initialState : const unit
, render : const render
, eval : const $ HQ.halt "no query"
, receiver : const Nothing
}
render = HH.div_ [ styles, content ]
The DOM is generated as follows:
<html>
<head>
<title>Test</title>
<script async="" type="text/javascript" src="main.js"></script>
</head>
<body>
<div>
<style type="text/css">
body { margin: 0 0 0 0 }
</style>
<p>
Test
</p>
</div>
</body>
</html>
This example works but according to the specification the style element is only allowed "where metadata content is expected", i.e. the <head> element. So I want to render the stylesheet there. How do I accomplish this?
Notice the awaitBody
function - as well as waiting for it to load, this selects the body
element, which is not really what you want in this case. If you want to write to the head
then you're going to need to select that and pass it to runUI
for a component that renders a stylesheet.
You'll also need to run two separate components, one for the head
, one for the body
:
module Main where
import Prelude
import Control.Monad.Aff.Console (CONSOLE)
import Control.Monad.Eff (Eff)
import Data.Const (Const)
import Data.Foldable (traverse_)
import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.VDom.Driver (runUI)
bodyComponent :: forall m. H.Component HH.HTML (Const Void) Unit Void m
bodyComponent =
H.component
{ initialState: const unit
, render: const $ HH.div_ [ HH.text "A component" ]
, eval: absurd <<< unwrap
, receiver: const Nothing
}
styleComponent :: forall m. H.Component HH.HTML (Const Void) Unit Void m
styleComponent =
H.component
{ initialState: const unit
, render: const $ HH.style_ [ HH.text "body { background: #222; color: #eee }"]
, eval: absurd <<< unwrap
, receiver: const Nothing
}
main :: Eff (HA.HalogenEffects (console :: CONSOLE)) Unit
main = HA.runHalogenAff do
HA.awaitLoad
traverse_ (runUI styleComponent unit) =<< HA.selectElement "head"
traverse_ (runUI bodyComponent unit) =<< HA.selectElement "body"
If they need to communicate then you could use the subscribe
and driver
functions from the HalogenIO
record that runUI
produces to set up channels between them.
I had concerns about doing this, as you're supposed to pass through an element that is empty as the target for where a container will render... but when using the Halogen-provided VDom
driver it appears to behave at least, in that the <head>
contents won't be replaced by the style component (it is appended to the end). This is basically unspecified behaviour though, so I'm not sure it would necessarily hold true with other drivers.