3
votes

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?

1
Why render the stylesheet with Halogen if it is completely static? It is possible to render style attributes on specific elements if one needs that flexibility. But the question in general is still interesting... I'm thinking about <title>.stholzm
Rendering a static stylesheet in Purescript has the following advantages: You can use the full power of PureScript to assemble the stylesheet and do not need another CSS preprocessor like Sass or Less. Furthermore you can modularize the code so that the Halogen components are stored together with their styles. Using style attributes is not an option if you need media queries or pseudo-classes like :hover.Siegfried Weber

1 Answers

3
votes

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.