So far we've learned how to define a component, and to use Aff for effect handling. For this information to be of any use we're going to want to see something in the browser!
Halogen provides a driver for a PureScript implementation of a virtual DOM for efficient patching of the actual DOM. This functionality is provided by the Halogen.VDom.Driver module.
The most basic possible main function for a Halogen app will look something like this:
import Prelude
import Control.Monad.Eff (Eff)
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
import Button (myButton)
main :: Eff (HA.HalogenEffects ()) Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
runUI myButton unit bodyThis assumes our component is pure, has no meaningful input value, we don't care about any messages it might emit, and have no need to send queries into it.
The main function involved here is runUI. It takes a component and a reference to a HTML element to use as a container:
runUI
:: forall f eff i o
. Component HTML f i o (Aff (HalogenEffects eff))
-> i
-> DOM.HTMLElement
-> Aff (HalogenEffects eff) (HalogenIO f o (Aff (HalogenEffects eff)))The i argument is the input type for the component - since we're creating the root component here this will never change. We still need to provide a value though, as the component's initial state might be based on it. All the examples we've covered so far don't make use of this, so for those cases we'd be passing unit.
The element we pass in should already be present in the DOM, and should be empty. If either of these conditions are not met then strange things may occur - the behaviour is unspecified.
We expect the component's m type variable to be Aff (HalogenEffects eff) at this point, and this is also what runUI returns in. This is why in the previous chapter the recommendation was made to use Aff for components even if you only need Eff. If the component is pure, this type will work out since the m type should be a type variable and we can substitute Aff in. If we have something else in here, then the component will have to be hoisted into Aff.
The HalogenEffects type here is a synonym for the row of effects involved in actually running components:
type HalogenEffects eff =
( avar :: AVAR
, ref :: REF
, exception :: EXCEPTION
, dom :: DOM
| eff
)AVARandREFare both used in the internal component machinery.EXCEPTIONs are possible, but only if you try really hard. They should never occur from operations provided by Halogen itself.DOM... well, this one is probably self explanatory.
The last thing to look at here is the resulting HalogenIO value. It's a record that gives us some options for communicating with the component we just ran:
type HalogenIO f o m =
{ query :: f ~> m
, subscribe :: Consumer o m Unit -> m Unit
}Note that m is polymorphic in the synonym. It's populated with Aff (HalogenEffects eff) once again for our case.
queryallows us to send queries into the component, using its query algebra (f). This is useful for things like routing, or driving an app from an external source - WebSockets, for example.subscribeallows us to receive the messages the component emits by providing acoroutineConsumer.
If we go back to our basic button example from chapter 2, we can demonstrate both of the above with something like this:
import Prelude
import Control.Coroutine as CR
import Control.Monad.Aff.Console (CONSOLE, log)
import Control.Monad.Eff (Eff)
import Data.Maybe (Maybe(..))
import Halogen as H
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
import Button as B
main :: Eff (HA.HalogenEffects (console :: CONSOLE)) Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
io <- runUI B.myButton unit body
io.subscribe $ CR.consumer \(B.Toggled newState) -> do
log $ "Button was toggled to: " <> show newState
pure Nothing
io.query $ H.action $ B.Toggle
io.query $ H.action $ B.Toggle
io.query $ H.action $ B.ToggleHere we're setting up a consumer that will listen to the component forever (as it returns pure Nothing - see the docs for consumer for an explanation), and immediately toggling the button several times on start up. Checking the browser console should reveal the corresponding logged "Button was toggled" messages.
Aside from runUI we used a couple of other utility functions in our main, exported from Halogen.Aff:
runHalogenAffruns a Halogen-producedAffvalue, turning it into anEffso it can be used asmainfor a PureScript bundle. It's provided as a convenience - there is no special behaviour here that couldn't be implemented with functions provided byaff.awaitBodyfetches thebodyelement when the document loads. Since we're inAffwe can use this to avoid the need for callbacks. This is used when the entire page is going to be a Halogen app.
There are also two more functions provided for cases where we want to run our Halogen app as just part of the page, rather than embedding it in the body:
awaitLoaddoes what the name suggests - waits for the document to load.selectElementis a wrapper aroundquerySelector- using this afterawaitLoadallows targeting of a particular container element on the page, to embed our app within.
Now we know how to build simple components and run them, we can take a look at embedding child components within a parent.