Overview¶
reflex
provides the Functional Reactive Programming (FRP) implementation.
This is the base for
reflex-dom
but is independent of the DOM / web interface design code, and can be used in many other applications.See Quick Ref
reflex-dom-core
and reflex-dom
provides a APIs for constructing DOM widgets, do websocket / XHR requests, etc.
Most of the functionality is part of the
reflex-dom-core
package.See Quick Ref
Reflex Basics¶
The reflex
package provides the foundation for the FRP architecture.
It consists of many type class definitions and their implementations, and the most important type class in this package is Reflex
.
The three main types to understand in Reflex
are Behavior, Event, and Dynamic.
Behavior
A container for a value that can change over time. ‘Behavior’s can be sampled at will, but it is not possible to be notified when they change
Behavior t a
abstracts the idea of a valuea
at all points in time. It must be defined for all points in time and at any point you can look at the behavior and sample its value. If you need to represent something that does not have a value at all points in time, you should probably useBehavior t (Maybe a)
.Event
Event t a
abstracts the idea of something that occurs or is updated at discrete points in time. An example might be button clicks which would beEvent t ()
, or key presses which might beEvent t Char
. Events are push oriented, i.e. they tell you when the value changes.Dynamic
Dynamic t a
is an abstraction that has a value at all points in time AND can notify you when its value is updated. They are essentially a tuple of an Event and a Behavior boxed up in a way that keeps everything consistent. They can be viewed as a step function over time, with the value changing at every occurrence.We use
Dynamic
inreflex-dom
in a lot of places where you might expect to useBehavior
in various other FRP settings because the DOM API is fundamentally push-based: you pretty much have to explicitly tell things to update, the browser isn’t asking our program which DOM tree should be displayed, so we have to know when the values change.
The t
type parameter indicates which timeline is in use.
Timelines are fully-independent FRP contexts, and the type of the timeline determines the FRP engine to be used. This is passed to every FRP-enabled datatypes
and it ensures that wires don’t get crossed if a single
program uses Reflex in multiple different contexts.
In reactive programming you have various sources of events which have to be utilised for providing responses. For example when user clicks a button, this event can have various different reponses depending upon the context or more specifically the state of the application.
The response to an event in most cases will do some changes like modify DOM, communicate with server or change the internal state of application.
In Reflex this response can be expressed or implemented by
- Firing another
Event
. - Modification of a
Dynamic
Value.
Note that there are no explicit callbacks or function calls in response to the incoming events. Instead there is generation of new Events and modification of Dynamic values. These Event and Dynamic values are then propagated to widgets which provide the appropriate response to the event.
Since this propagation of Event
/Dynamic
values can be cyclic, it can be thought
as an Event propagation graph.
For more details see Event
Architecture of a Reflex-DOM Application¶
A typical Reflex-DOM application consists of widgets, and some glue code to connect the widgets together.
Widget can be thought as a DOM Structure which has the capability to modify its contents in response to events or based on some dynamic values. It can also contain structures like input fields which can generate events. Moreover user interaction events like mouse clicks can also be captured from the widgets.
Additionally there are some pieces of code (equivalent to a controller) which does not have a Dom view, but can process input events, maintain a state and generate output events or dynamic values.
These controllers can encapsulate the logic behind handling of incoming events, they can transform (using Functor) or filter (using Applicative) these events and dynamic values as per the need. This way user has the power to create custom event flows which can be either restricted/local to some widgets or span the entire app.
Reflex does not enforce a strict separation between these two, and user has the complete flexibility to choose a suitable design.
Sometimes it is a good practice to partition the code in these sub-categories, like implementing the main business logic in a pure function or a state machine, and the view in a separate module.
But many times it is better to have independent self-contained widgets, thereby reducing the complexity of propagating trivial events from view to the controller.
Also see the reddit thread how to structure a reflex application.
DOM Creation¶
The HTML DOM is constructed as a tree of “Objects” in which both the “sequence” of objects in the tree and their “heirarchy” has to be specified.
In reflex-dom
, DOM creation works in a Monad DomBuilder
. Since it is monadic, the sequence of function calls directly correspond to the sequence of DOM elements.
To create heirarchy a lot of basic widgets take an addition argument of type (m a) which will be nested inside it.
For example:
let myText = do -- Specifies sequence
el "h1" (text "Header") -- Nesting
text "Content"
el "div" myText -- Nesting
View-Controller Architecture¶
Separate APIs to manage events and to render view
-- button_and_textvisibility.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
-- This code demonstrates use of an event to create dynamic values
-- Simple flow of an event from one widget to another.
main = mainWidget $ do
-- View Widget to Generate Events
-- button widget is defined in library, it creates a simple button
evClick <- button "Click Me!"
-- Controller
-- Handle events and create a 'Dynamic t Bool' value
-- This toggles the visibility when the button is pressed
isVisible <- foldDyn (\_ b -> not b) False evClick
-- View
-- This is a simple widget that takes a 'Dynamic t Bool' as input
textWithDynamicVisibility isVisible
return ()
-- This widget takes the input value of visibility
-- and creates a view based on that
textWithDynamicVisibility isVisible = do
let dynAttr = ffor isVisible
(\case
True -> ("style" =: "")
False -> ("style" =: "display: none;"))
elDynAttr "div" dynAttr $
text "Click the button again to make me disappear!"
Widgets Interacting Together¶
By using the recursive-do notation we can connect the widgets together. This is a simple example of creating a cicular Event-Dynamic propagation.:
-- button_and_textvisibility_2.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecursiveDo #-} -- This is important!
-- This code demonstrates use of an event to create dynamic values
-- Circular flow of Event/Dynamic using Recursive-do syntax
main = mainWidget $ do
rec
-- Controller
-- Handle events and create a 'Dynamic t Bool' value
-- This toggles the visibility when the button is pressed
isVisible <- foldDyn (\_ b -> not b) False evClick
-- View
-- This widget creates the button and its click event,
-- The click event is propagated to the controller
evClick <- textWithDynamicVisibility isVisible
return ()
-- This widget takes the input value of visibility
-- and creates a view based on that
textWithDynamicVisibility isVisible = do
-- View Widget to Generate Events
-- button widget is defined in library, it creates a simple button
evClick <- button "Click Me!"
let dynAttr = ffor isVisible
(\case
True -> ("style" =: "")
False -> ("style" =: "display: none;"))
elDynAttr "div" dynAttr $
text "Click the button again to make me disappear!"
return evClick
As you can see this helps to completely separate the View widget and controller code.
But the real power of recursive-do notation can be utilised in creating more complex Integrated widgets as desribed in the next section.
Integrated Widget Architecture¶
In Reflex it is possible to combine the view and controller part of the code to create integrated widgets which can be plugged in easily in your app.
Example of a widget which is self-contained. This widget creates a simple text field, which can be edited by clicking on it. Source:
editInPlace
:: MonadWidget t m
=> Behavior t Bool
-- ^ Whether or not click-to-edit is enabled
-> Dynamic t String
-- ^ The definitive value of the thing being edited
-> m (Event t String)
-- ^ Event that fires when the text is edited
Quoting mightybyte
This defines the entire interface to this widget. What makes this example particularly interesting is that the widget has to maintain some internal state in order to implement its functionality. Namely, it has to keep track of the Viewing/Editing state. Reflex allows widgets to handle this kind of state internally without needing to add it to some top-level application-wide state object. This hugely improves composability and ultimately allows you to build GUI apps just like you would any other Haskell app–main is your overarching top-level function and then you split out whatever widgets it makes sense to split out. Your guide for splitting things will probably be that you want to find pieces that are loosely connected to everything else in terms of inputs and ouputs and make them their own function.
Overview of ghcjs
and jsaddle
Packages¶
ghcjs
Is the compiler, likeghc
.
ghcjs-dom
Is the library which provides the interface APIs to work with DOM and Web APIs, either on a browser (by compiling with
ghcjs
) or natively using webkitgtk (when compiled withghc
)Applications should use the
ghcjs-dom
package and theGHCJS.DOM.*
modules it contains; to get the best mix of portability and performance (rather than using thejsaddle-dom
,ghcjs-dom-jsaddle
andghcjs-dom-jsffi
directly).
Note
The below package descriptions are provided for information only. For using reflex-dom in applications ghcjs-dom should be sufficient.
ghcjs-base
Is the base library for
ghcjs
for JavaScript interaction and marshallingThis package should be included in cabal only if using
ghcjs
by adding thisif impl(ghcjs) build-depends: ghcjs-base
jsaddle
JavaScript interface that works with
ghcjs
orghc
.It provides a set of APIs to do arbitrary JS execution in a type-safe manner.
If compiled with
ghc
on native platforms like WebKitGtk, WKWebView on iOS / macOS or Android using JNI.It uses a JavaScript command interpreter for each of the different targets.
If compiled with
ghc
usingjsaddle-warp
and running on browser.The JS commands are encoded in the executable running on native platform, and sent to the browser for execution using a websocket connection.
If compiled with
ghcjs
, it uses some JSFFI calls to execute the functions indirectly.Note: this has poor performance compared to calling the DOM APIs directly through
ghcjs-dom-ffi
as the DOM API calls are wrapped in an execution script.See README for more details.
ghcjs-base
and jsaddle
form the base for these packages
ghcjs-dom-ffi
This package implements the entire DOM/Web API interface as direct JSFFI calls.
On browser this is the most optimal way to execute DOM related actions.
ghcjs-dom-jsaddle
and jsaddle-dom
This provides the DOM/Web API interface usingjsaddle