Reflex Dom¶
See Quick Ref
Basic Widgets¶
Static DOM¶
Here is a simple example of using some of the static-dom widgets:
-- simple_dom.hs
{-# LANGUAGE OverloadedStrings #-}
import Reflex.Dom
-- Code to showcase Reflex.Dom's APIs to create simple static DOM
main = mainWidget $ do
simple
simple :: (DomBuilder t m) => m ()
simple = do
el "div" $
-- Specify attributes in a (Map Text Text)
elAttr "span" ("style" =: "color:blue") $
text "Text inside span"
-- Use CSS style center-align and red-text
-- using these specialised APIs
divClass "center-align" $
elClass "span" "red-text" $
text "Div with class center-align and red text"
el "dl" $ do
dtdd "dt dd tags" $
text "Here goes the description"
dtdd "Reflex" $ do
text "Haskell + awesome FRP!"
el "br" $ blank -- Add line break, blank == return ()
-- A simple URL link
elAttr "a" ("href" =: "http://reflexfrp.org") (text "Reflex-FRP")
Dynamic DOM¶
To create interactive widgets you need to do changes in DOM in response to
Event
s or Dynamic
values.
The simplest way to create a dynamic DOM is to use library APIs which take Dynamic values as input. The following section covers these APIs. Using these APIs you can create bigger widgets which can have multiple Dynamic values as input.:
-- Show simple text
dynText $ someDynTextValue -- :: Dynamic t Text
display $ someDynValueWithShowInstance
-- The value of input element can be modified from an external Event t text
txtInpEl <- inputElement $ def
& inputElementConfig_setValue .~ changeValueEv
Also you can create dynamic widgets by using static widgets, ie the widget
which don’t take dynamic values as inputs (eg. button :: Text -> m (Event t a)
).
This can be done simply by mapping the Dynamic values over these widgets and using dyn
.:
-- Use the library API button which accepts static Text
-- and modify its value by using a (Dynamic t Text)
dyn (button <$> (value txtInpEl))
The library provides a number of standard widgets which accept Dynamic
values as input
elDynAttr elDynClass
Change the attributes of a DOM element via Dynamic values.
tableDynAttr
A widget to display a table with static columns and dynamic rows.:
tabDisplay
A widget to construct a tabbed view that shows only one of its child widgets at a time. Creates a header bar containing a<ul>
with one<li>
per child; clicking a<li>
displays the corresponding child and hides all others.
DOM Input elements¶
To create input form elements and use them to create Event
and Dynamic
values use the widgets provided by Reflex.Dom.Widget.Input
The various input elements usually contain these two values:
*_input :: Event t a
*_value :: Dynamic t a
The _input
event will only fires when user modifies contents of the input field.
But if you are modifying the value of the input field using reflex Event
and you want to capture even these changes, then use updated value
.
Tip
When using the *_input
Events you might have to use debounce
. See Debounce, Delay, BatchOccurence
DOM Events¶
domEvent
API can be used to createEvent
on DOM elements:(e,_) <- el' "span" $ text "Click Here" clickEv :: Event t () clickEv <- domEvent Click eFor a complete list of events accepted by
domEvent
seeEventName
in Reflex.Dom.Builder.Class.Events
Dynamic widgets based on Events¶
Create a widget which updates whenever Event
occurs.
If you have a widget which depends on some event (like server response), but you need to display something else instead of a blank.
-- responseEv :: Event t SomeData
-- displaySomeData :: SomeData -> m ()
-- widgetHold :: m a -> Event t (m a) -> m (Dynamic t a)
widgetHold (text "Loading...") (displaySomeData <$> responseEv)
Every time the widgetHold
event fires, it removes the old DOM fragment and builds a new one in-place
Miscellaneous¶
Resize Detector¶
-- Reflex.Dom.Widget.Resize
resizeDetector :: (...) => m a -> m (Event t (), a)
This is useful to respond to changes in size of a widget.
Host / URL / Location¶
Reflex.Dom.Location
contains utility functions for obtaining the host, URL, protocol, etc.
Client side routes¶
obelisk-route¶
Obelisk is packaged with a set of routing libraries
obelisk-route
,obelisk-route-frontend
andobelisk-route-backend
. These libraries provide the following features
- Type safety in routes design.
- Derive encoding/decoding of routes from a single definition.
- Share the routes between frontend and backend.
- Compile time checking of routes to static files.
For example usage of
obelisk-route
please see source code of reflex-frp.org or reflex-examples.
Apart from this the Reflex.Dom.Contrib.Router provides APIs to manipulate and track the URL.
Also checkout https://github.com/3noch/reflex-dom-nested-routing
SVG¶
To embed an SVG element use elDynAttrNS'
along with SVG namespace:
elSvgns = elDynAttrNS' (Just "http://www.w3.org/2000/svg")
Using canvas element with reflex is generally not a good idea, as it is based on an imperative style of coding (vs the declarative style of svg).
Also checkout https://github.com/qfpl/reflex-dom-svg
XHR/ websocket¶
For usage on XHR / AJAX requests please see the haddock documentation of module Reflex.Dom.Xhr
, it contains example usage of the APIs.
Websocket¶
Use webSocket
API from the Reflex.Dom.WebSocket
module.:
webSocket
:: Text -- url, like "ws://localhost:3000/myWebSocketHandler"
-- use wss for SSL connections
-> WebSocketConfig t a -> m (WebSocket t)
data WebSocketConfig t a
= WebSocketConfig {_webSocketConfig_send :: Event t [a],
_webSocketConfig_close :: Event t (Word, Text),
_webSocketConfig_reconnect :: Bool}
type WebSocket t =
RawWebSocket t ByteString
data RawWebSocket t a
= RawWebSocket {_webSocket_recv :: Event t a,
_webSocket_open :: Event t (),
_webSocket_error :: Event t (),
_webSocket_close :: Event t (Bool, Text)}
To send data over WebSocket pass an event to _webSocketConfig_send
of type
Event t [a]
where a
is either Text
or ByteString
.
The return value from WebSocket is available from _webSocket_recv :: Event t ByteString
Here _webSocketConfig_close
is an Event
which can close the WebSocket connection
from client side. And _webSocket_close
is the response from server when the
connection closes.
Manually closing a websocket that is configured to reconnect will cause it to reconnect.
If you want to be able to close it permanently you need to set _webSocketConfig_reconnect = False
.
See reflex-examples for an echo example.
Integration with Backend¶
One of the big strength of reflex-dom
is that a common code can be shared between backend and frontend.
Quoting mightybyte again. See hsnippet.com source code here
I used a very similar architecture with Reflex with HSnippet, and it’s delightful to work with. Server communication was done over websockets with the wire format being a serialized version of these data types. Adding a new client/server or server/client message couldn’t be more simple.
The simplest form of integration with backend is to define the message data in the common
package, along with its serialisation functions (eg deriving instance
of ToJSON
and FromJSON
).
servant-reflex¶
https://github.com/imalsogreg/servant-reflex
servant-reflex lets you share your servant APIs with the frontend. See the readme for more details.
reflex-websocket-interface¶
Going a few steps further in this integration is the library reflex-websocket-interface
It provides a reflex side API like this:
getResponse :: (_) => Event t request -> m (Event t response)
This takes care of encoding and decoding of the messages (using
aeson
), do all the routing of Event behind the scenes, and provide the response at the point where request was initiated.This architecture of handling the request and its response at the same place in widget code is essential for self-contained widgets. It also helps greatly simplify the coding, especially when there are more than one instance of a widget, and they all use single websocket to communicate.
Internally this uses Requester.
It ensures the server has code to handle all the request types.
It further ensures that the type of response for a request is consistent between frontend and backend.
Performance¶
Prerendering / Server side rendering¶
The renderStatic
API can be used to render the DOM parts of the application to plain HTML.
This way the server can serve the generated HTML, so that the page opens instantly for the user.:
renderStatic :: StaticWidget x a -> IO (a, ByteString)
To create widget which support static rendering, the prerender
API will be required internally to separate the static code from the Immediate DomBuilder one.
prerender :: forall js m a. Prerender js m =>
m a -> (PrerenderClientConstraint js m => m a) -> m a
Here the first widget supports Static rendering, and the second one has the actual JSM functionality.
See reflex-examples for example usage.
lazy¶
Reflex.Dom.Widget.Lazy
contains widgets for creating long lists. These are scrollable element and only renders child row elements near the current scroll position.