Without being an expert, let alone a novice, I will answer some Halogen questions that I have encountered while working with the PureScript UI library Halogen. Questions, comments, and improvements welcome.
- How do I initialize a Halogen component?
- How do I asynchronously update the UI (e.g. Show/update a loading bar)?
- How do I query the rendered markup of my component?
To initialize a component, you'll need to use a "lifecycleComponent", which provides initializer and finalizer properties that can trigger your Initialize and Finalize Queries (or whatever you happen to call the queries).
data Query a
= Initialize a
| Finalize a
myComponent :: forall eff. H.Component HH.HTML Query Input Message (CAff eff)
myComponent = do
H.lifecycleComponent
{ initialState: const (initialState pp)
, render
, eval
, initializer: Just (H.action Initialize)
, finalizer: Just (H.action Finalize)
, receiver: const Nothing
}
where
eval :: Query ~> H.ComponentDSL State Query Message (CAff eff)
eval = case _ of
Initialize next -> do
pure next
Finalizer next -> do
pure next
Within the eval function, you need to create and subscribe to an "EventSource". See eventSource, eventSource', eventSource_, and eventSource_', which provide different ways of handling the event.
eventSource_
simply indicates whether or not a callback fired.
You will also need to create a request query to handle the event (remember that the query algebra has actions and requests).
import Halogen.Query.EventSource as HES
data Query a
= Initialize a
| Finalize a
| TheEventHappened (HES.SubscribeStatus -> a)
eval :: Query ~> H.ComponentDSL State Query Message (CAff eff)
eval = case _ of
MyQuery next -> do
-- eventSource_ handles events that don't need to
-- pass along data. Think of it as simply indicating
-- whether yes/no
subscribe $ eventSource_ (\eff -> do
void $ runAff Eff.logShow (const $ pure unit)
(onSomeEvent do
log "Some async event happened."
-- Trigger the callback that halogen passed
-- use
liftEff $ eff
))) (H.request TheEventHappened)
TheEventHappened reply -> do
-- Are we done listening to the event source?
-- Then return Done.
-- pure (reply H.Done)
-- Are we still listening to the event source?
-- Then return Listening
-- pure (reply H.Listening)
Often we need to know more than just whether a callback fired or not. For example, we may want to pass along the value of a
timer. In that case we can use eventSource
which lets us pass a value to the callback function, e.g.
import Halogen.Query.EventSource as HES
data Query a = QUpdateLoading String Int Int (HES.SubscribeStatus -> a)
-- | Every 500 milliseconds, trigger the `UpdateLoading` Request. Do this
-- | until the max time `max` has been reached.
updateLoading :: forall s m r t u e.
(MonadAff ( avar :: AVAR, console :: CONSOLE | e) m) =>
Int -> String -> HalogenM { stage :: Stage | s } Query u t r m Unit
updateLoading max message = do
subscribe $ H.eventSource (forEveryUntil 500 max) (\time -> do
Just $ H.request $ UpdateLoading message time max
)
-- | Continuously run a callback function ever `interval` milliseconds
-- | until the maximum time `remaining` is <= 0
runForTime :: forall e. Int -> Int -> Aff e Unit -> Aff e Unit
runForTime interval remaining callback
| remaining <= 0 = pure unit
| otherwise = later' interval do
callback
runForTime interval (remaining - interval) callback
-- | For every `interval` milliseconds, run callback function `callback`,
-- | until `max` milliseconds has elapsed (approximately).
forEveryUntil :: forall e. Int -> Int -> (Int -> Eff (avar :: AVAR, console :: CONSOLE | e) Unit) -> Eff (avar :: AVAR, console :: CONSOLE | e) Unit
forEveryUntil interval max callback = do
void $ runAff Effc.logShow (const $ pure unit) do
time <- liftAff $ makeVar' 0
(runForTime interval max do
modifyVar (\v -> v + interval) time
val <- peekVar time
liftEff $ callback val
Some more examples that were helpful to me...
- AceComponent's use of eventSource_
- implementation of eventSource and it's variations
- Halogen component with a timer that uses EventSource
- Browse purescript repos that use eventSource_
You don't really query the markup, but you can get a reference to an HTML element by marking it with a RefLabel. For instance, if you have a canvas element that you need to draw on, you can mark the canvas element as a reference using the ref property, and then get the reference in your query evaluation using getHTMLElementRef.
canvasRef :: H.RefLabel
canvasRef = H.RefLabel "menuCanvas"
render state =
HH.div [HP.class_ "inner"]
[ HH.canvas [ HP.ref canvasRef, HP.width 300, HP.height 300 ] ]
eval = case _ of
Initialize next -> do
H.getHTMLElementRef canvasRef >>= (\maybeCanvas -> ...)
This information is for halogen 4.
Halogen.Query.EventSource
has different functions now.