Running an Application
Over the course of this guide we've seen the standard way to run a Halogen application several times. In this chapter, we'll learn what is actually going on when we run a Halogen application and how to control a running app from the outside.
Using runUI and awaitBody
PureScript applications use the main function in their Main module as their entrypoint. Here's a standard main function for Halogen apps:
module Main where
import Prelude
import Effect (Effect)
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
main :: Effect Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
runUI component unit body
-- Assuming you have defined a root component for your application
component :: forall query input output m. H.Component query input output m
component = ...
The most important function used in main is the runUI function. Provide runUI with your root component, the root component's input value, and a reference to a DOM element, and it will provide your application to the Halogen virtual DOM. The virtual DOM will then render your application at that element and maintain it there for as long as your app is running.
runUI
:: forall query input output
. Component query input output Aff
-> input
-> DOM.HTMLElement
-> Aff (HalogenIO query output Aff)
As you can see, the runUI function requires that your Halogen application can ultimately be run in the Aff monad. In this guide we used constraints like MonadEffect and MonadAff, which Aff satisfies, so we're in the clear.
If you chose to use another monad for your application then you'll need to hoist it to run in
Affbefore you provide your application torunUI. The Real World Halogen uses a customAppMmonad that serves as a good example of how to do this.
In addition to runUI we used two other helper functions. First, we used awaitBody to wait for the page to load and then acquire a reference to the <body> tag as the root HTML element for the application to control. Second, we used runHalogenAff to launch asynchronous effects (our Aff code containing awaitBody and runUI) from within Effect. This is necessary because awaitBody, runUI, and our applications run in the Aff monad, but PureScript main functions must be in Effect.
The main function we've used here is the standard way to run a Halogen application that is the only thing running on the page. Sometimes, though, you may use Halogen to take over just one part of the page, or you may be running multiple Halogen apps. In these cases, you'll probably reach for a pair of different helper functions:
awaitLoadblocks until the document has loaded so that you can safely retrieve references to HTML elements on the pageselectElementcan be used to target a particular element on the page to embed the app within
Using HalogenIO
When you run your Halogen application with runUI you receive a record of functions with the type HalogenIO. These functions can be used to control your root component from outside the application. Conceptually, they're like a makeshift parent component for your application.
type HalogenIO query output m =
{ query :: forall a. query a -> m (Maybe a)
, messages :: Event output
, dispose :: m Unit
}
- The
queryfunction is like theH.queryfunction which underpinstellandrequest. This allows you to send queries to the root component of your application from outside the application. - The
messagesevent can be used to subscribe to a stream of output messages from the component -- it's like the handler we provided to theslotfunction, except rather than evaluate an action here we can perform some effect instead. - The
disposefunction can be used to halt and clean up the Halogen application. This will kill any forked threads, close all subscriptions, and so on.
You can't use tell and request at the root of your application, but you can use the mkTell and mkRequest functions (as seen in the example below) for a similar effect.
A common pattern in Halogen applications is to use a Route component as the root of the application, and use the query function from HalogenIO to trigger route changes in the application when the URL changes. You can see a full example of doing this in the Real World Halogen Main.purs file.
Full Example: Controlling a Button With HalogenIO
You can paste this example into Try PureScript to explore using HalogenIO to control the root component of an application.
module Example.Driver.IO.Main where
import Prelude
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Console (log)
import Halogen (liftEffect)
import Halogen as H
import Halogen.HTML as HH
import Halogen.Aff as HA
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
import Halogen.Subscription as HS
import Halogen.VDom.Driver (runUI)
main :: Effect Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
io <- runUI component unit body
_ <- liftEffect $ HS.subscribe io.messages \(Toggled newState) -> do
liftEffect $ log $ "Button was internally toggled to: " <> show newState
pure Nothing
state0 <- io.query $ H.mkRequest IsOn
liftEffect $ log $ "The button state is currently: " <> show state0
void $ io.query $ H.mkTell (SetState true)
state1 <- io.query $ H.mkRequest IsOn
liftEffect $ log $ "The button state is now: " <> show state1
-- Child component implementation
type Slot = H.Slot Query Message
data Query a
= IsOn (Boolean -> a)
| SetState Boolean a
data Message = Toggled Boolean
data Action = Toggle
type State = { enabled :: Boolean }
component :: forall i m. H.Component Query i Message m
component =
H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval
{ handleAction = handleAction
, handleQuery = handleQuery
}
}
initialState :: forall i. i -> State
initialState _ = { enabled: false }
render :: forall m. State -> H.ComponentHTML Action () m
render state =
let
label = if state.enabled then "On" else "Off"
in
HH.button
[ HP.title label
, HE.onClick \_ -> Toggle
]
[ HH.text label ]
handleAction :: forall m. Action -> H.HalogenM State Action () Message m Unit
handleAction = case _ of
Toggle -> do
newState <- H.modify \st -> st { enabled = not st.enabled }
H.raise (Toggled newState.enabled)
handleQuery :: forall m a. Query a -> H.HalogenM State Action () Message m (Maybe a)
handleQuery = case _ of
IsOn k -> do
enabled <- H.gets _.enabled
pure (Just (k enabled))
SetState enabled a -> do
H.modify_ (_ { enabled = enabled })
pure (Just a)