diff --git a/code-snippets/StaticBuilder.hs b/code-snippets/StaticBuilder.hs new file mode 100644 index 0000000..11ec09a --- /dev/null +++ b/code-snippets/StaticBuilder.hs @@ -0,0 +1,63 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE CPP #-} + +import Reflex.Dom +import Data.Text as T +import Data.Text.Encoding as T +import Data.ByteString.Char8 as BS8 + +main = do +#if defined (ghcjs_HOST_OS) + -- Use the widget normally as the main entry point + mainWidget topWidget + return () +#else + (_,bs) <- renderStatic $ topWidget + -- Use the generated bytestring as the body of page + -- And include the all.js in the end + BS8.putStrLn bs +#endif + +-- Widget supporting Static Rendering +topWidget :: ((MonadHold t m, + PostBuild t m, + Prerender js m, + DomBuilder t m, + TriggerEvent t m, + PerformEvent t m)) + => m () +topWidget = do + el "h1" $ text "Some heading" + + -- Use constDyn for widget which need Dynamic values + elDynAttr "div" (constDyn ("id" =: "mydiv")) $ text "hello" + + -- The Events will only fire in the Immediate DomBuilder + ev <- button "Click to test" + + divClass "static-text" $ widgetHold (text "Initial text") + (text "Changed text" <$ ev) + + -- The MonadHold widgets have to be put inside prerender + c <- prerender (return $ constDyn 0) (Reflex.Dom.count ev) + display c + + message <- getWebSocketMessage ("some message sent to websocket" <$ ev) + + divClass "message" $ widgetHold (text "Waiting For message") + (text <$> message) + + return () + +getWebSocketMessage :: + (_) + => Event t Text + -> m (Event t Text) +getWebSocketMessage messageEv = prerender (return never) $ do + let sendEv = (\m -> [T.encodeUtf8 m]) <$> messageEv + ws <- webSocket "ws://echo.websocket.org" $ WebSocketConfig sendEv never False + return (T.decodeUtf8 <$> _webSocket_recv ws) diff --git a/code-snippets/nested_dynamics.hs b/code-snippets/nested_dynamics.hs index 01bbbca..86695a5 100644 --- a/code-snippets/nested_dynamics.hs +++ b/code-snippets/nested_dynamics.hs @@ -1,3 +1,7 @@ +-- A slightly contrived example just to demonstrate use of nested dynamics +-- This example also has a nested state machine, +-- By using foldDynM, we could use foldDyn inside of it. +-- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} @@ -5,10 +9,6 @@ import Reflex.Dom import Data.Text as T import Data.Monoid --- A slightly contrived example just to demonstrate use of nested dynamics --- This example also has a nested state machine, --- By using foldDynM, we could use foldDyn inside of it. --- -- A ScoreCard can either display some info/updates or the current score data ScoreCard t = Info (Dynamic t Text) @@ -33,11 +33,11 @@ main = mainWidget $ do -- gameEv :: (Reflex t) => Event t GameEvent gameEv = leftmost [newGame, move1, move2, move3, move4] - newGame = const NewGame <$> newGameEv - move1 = const (DoTurn "Move 1" 1) <$> m1 - move2 = const (DoTurn "Move 2" 20) <$> m2 - move3 = const (DoTurn "Move 3" 10) <$> m3 - move4 = const (DoTurn "Move 4" 5) <$> m4 + newGame = NewGame <$ newGameEv + move1 = (DoTurn "Move 1" 1) <$ m1 + move2 = (DoTurn "Move 2" 20) <$ m2 + move3 = (DoTurn "Move 3" 10) <$ m3 + move4 = (DoTurn "Move 4" 5) <$ m4 -- Capture the score, in a Dynamic independent of ScoreCard -- This will maintain its value irrespective of the state of ScoreCard @@ -47,9 +47,9 @@ main = mainWidget $ do foldDyn handleGameEvent 0 gameEv - let + let initCard = Score scoreDyn - + eventHandler _ (Info _) = return (Score scoreDyn) eventHandler _ (Score _) = do let handleGameEvent (NewGame) _ = "New Game!" @@ -62,7 +62,7 @@ main = mainWidget $ do -- So this will be reset whenever you toggle the display of score card textDyn <- foldDyn handleGameEvent "" gameEv return (Info textDyn) - + -- external state machine using foldDynM -- Here the (ScoreCard t) itself contains a Dynamic value -- scoreCardDyn :: Dynamic t (ScoreCard t) diff --git a/doc/advanced_topics.rst b/doc/advanced_topics.rst deleted file mode 100644 index 4df1d42..0000000 --- a/doc/advanced_topics.rst +++ /dev/null @@ -1,82 +0,0 @@ -Advanced Topics ---------------- - -Backend integration -~~~~~~~~~~~~~~~~~~~ - -Since in most cases you will be writing your backend also in Haskell. It is -important to be able to share code between backend and frontend. - -#. Share server-client messages and their serialisation/deserialisation code. - -#. Share routes - - .. todo:: This will be backend specific. How to do this?? - - -Assuming you have two projects (either simple cabal projects or with stack, nix), -in order to share code between the two; you have these options: - -#. Create a common source directory and share the link of this directory in both - projects, and add this common directory to ``hs-source-dirs`` of cabal file. - -#. Create a separate project (ie with separate cabal file) and add dependency of - this project to both frontend and backend cabal files. - - This is a better way to handle common code. But you need to provide the - dependency of local package either through stack.yaml or in your nix config. - - .. todo:: More details on this - - Is it better to provide a scaffolding for this - - Any other thing to mention here regarding - Integration with Yesod, Servant, Snap, etc. - -Deploying -~~~~~~~~~ - -You need to serve ``index.html``, ``rts.js``, ``lib.js``, ``out.js`` and -``runmain.js`` from the cabal generated folder -``dist/build//.jsexe/`` - -Simplest way is to copy these files to the *static* directory of your backend -project. This can be automated using simple shell script. - -Client Side Routing -~~~~~~~~~~~~~~~~~~~ - -``reflex-dom-contrib`` has a ``route`` API to provide routing capabilities. - -* Change route via Event (like click) -* Get route changes from browser Forward/Back button clicks. -* JS forward/backward calls - -https://github.com/reflex-frp/reflex-dom-contrib/blob/master/src/Reflex/Dom/Contrib/Router.hs - -.. Here is a post which shows how client side routing can be used. Though it would - be easier if an example with the route API is provided -.. https://ublubu.tumblr.com/post/144208331227/client-side-routing-in-reflex-dom-notes-1 - servant-router - -Designing library/ reusable web snippets -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -Libraries - diagrams-reflex, reflex-gloss, etc. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -FFI -~~~ - -.. todo:: What is the recommended way to do FFI in reflex app? The GHCJS wiki has some useful [info](https://github.com/ghcjs/ghcjs/blob/master/doc/foreign-function-interface.md) and may be a good place to start. - -Use JQuery, BootStrap, etc? -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -Design of project for both ghc and ghcjs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Create both desktop app and web app from same project - diff --git a/doc/app_devel_docs.rst b/doc/app_devel_docs.rst new file mode 100644 index 0000000..35913f3 --- /dev/null +++ b/doc/app_devel_docs.rst @@ -0,0 +1,321 @@ +Application Development with Reflex-DOM +======================================= + +.. + Dev Environment + --------------- + + * reflex-plaform + + * GHCi, + + * hoogle + See :ref:`haddock_and_hoogle` + + * + +Debugging +--------- + +Functionality +~~~~~~~~~~~~~ + +In addition to the normal ``Debug.Trace`` APIs, the following can be used for debugging. + +The output of these APIs will be in the browser console when compiled with ``ghcjs``. +For ``jsaddle-warp`` and ``webkit`` based apps the output will be on the terminal.:: + + + traceEvent :: (Reflex t, Show a) => String -> Event t a -> Event t a + traceEventWith :: Reflex t => (a -> String) -> Event t a -> Event t a + +Moreover the `reflex-dom-contrib `_ package contains a bunch of utility functions. +One can just copy-paste these functions, ie use them without dependency on the package.:: + + -- Reflex.Dom.Contrib.Utils + -- pops up a javascript alert dialog box + alertEvent :: (_) => (a -> String) -> Event t a -> m () + + -- pops up a javascript confirmation dialog box + confirmEvent :: (_) => (a -> String) -> Event t a -> m (Event t a) + + -- | Prints a string when an event fires. This differs slightly from + -- traceEvent because it will print even if the event is otherwise unused. + putDebugLnE :: MonadWidget t m => Event t a -> (a -> String) -> m () + +.. _hang_stack_overflow: + +Hang / Stack Overflow +~~~~~~~~~~~~~~~~~~~~~ + +In general its possible to create a loop by mistake with this kind of code in a "pure" haskell.:: + + let + f v = ... (f v) + +But thanks to ``MonadFix`` (``RecursiveDo``) this is a very common problem, even in a "monadic" code. + +Basically for doing anything useful one has to introduce a feedback in the event propagation graph. +And often this can lead to either a loop or a deadlock. + +To fix this + +* Breaking down a big ``rec`` block into nested ``rec`` blocks or a series of ``rec`` blocks. + Moving the code in a separate functions can also help simplify the ``rec`` block. + + Also see: using :ref:`new_trigger_event` to break down a big ``rec`` block. + +* Avoid using ``switchPromptlyDyn`` / ``tagPromptlyDyn``, instead use ``switch . current`` / ``tag . current`` + + Many times what one really need is the previous value of a ``Dynamic`` to create a cyclic event propagation. + +* Use ``widgetHold`` against ``dyn`` + + Separating an initial value from an update event means that the function using them doesn't have to call ``sample`` on a ``Dynamic``, + which can be unsafe when you don't know whether the MonadFix knot has been tied. + + Using ``widgetHold`` ensures that the user doesn't accidentally give an untied Dynamic. + +For more details checkout the articles on :ref:`monad_fix` + + +Compilation Errors +~~~~~~~~~~~~~~~~~~ + +These are a few common compile time errors which can occur while using the +widgets + +* If you define a widget but don't use it any where :: + + -- 't' is not used anywhere + let t = textInput $ def + + Compile error + + • Couldn't match type ‘DomBuilderSpace m0’ with ‘GhcjsDomSpace’ + arising from a use of ‘textInput’ + The type variable ‘m0’ is ambiguous + • In the expression: textInput $ def + In an equation for ‘t’: t = textInput $ def + + + Solution: Simply comment this code or use it. + + +* In a ``rec`` block if use a "pure" API in a "monadic" context, then you can get weird type errors:: + + -- This will lead to type-checker assume the monad to be Dynamic + ev <- switchPromptlyDyn dynEv + + The biggest problem with such errors is that the line numbers are not correct, so it can take a while to figure out the source of error + + One possible solution is to explicitly specify the type of functions and expression in the ``let`` and ``do`` block inside of ``rec``:: + + -- This is required to specify the types + -- {-# LANGUAGE ScopedTypeVariables #-} + + -- This can be useful to specify types partially, just to help figure out source of error + -- {-# LANGUAGE PartialTypeSignatures #-} + + -- Specify an explicit forall + myWidget :: forall t m k . (MonadWidget t m, Ord k) + => Map k Text -> m () + myWidget mapInput = do + .. + + rec + let + eTabClicks :: Event t k = leftmost tabClicksList + + d :: Dynamic t k <- do + someCodeThatIsSupposedToReturnDynamicK + +.. _ffi: + +Web APIs and FFI +---------------- + +* For working with DOM and using Web APIs the ``ghcjs-dom`` package should suffice. + + It provides APIs like ``getElementById``, ``getBoundingRect`` to work with DOM, and many other Web APIs related to geolocation, media management, web audio, etc. + + To use the DOM related APIs for ``reflex-dom`` created elements, extract the `raw` element from the `reflex element` :: + + import qualified GHCJS.DOM.Types as DOM + import qualified GHCJS.DOM.DOMRectReadOnly as DOM + import qualified GHCJS.DOM.Element as DOM + + (e,_) <- el' "div" $ text "Hello" + + let getCoords e = DOM.liftJSM $ do + rect <- DOM.getBoundingClientRect (_element_raw e) + y <- DOM.getY rect + h <- DOM.getHeight rect + return (y,h) + + performEvent (getCoords e <$ ev) + +* But when using external .js files, one has to do arbitrary JS code execution. + + For doing this ``jsaddle`` package is preferred as it provides a type-safe way to execute the JS code. + + See `documentation `_ of ``Language.Javascript.JSaddle.Object`` for examples + + See :ref:`dom_ui_libs` for example usage. + +* It is also possible to do arbitrary JS code block execution using ``eval`` API from ``Language.Javascript.JSaddle.Evaluate``. :: + + eval :: (ToJSString script) => script -> JSM JSVal + + liftJSM $ eval "console.log('Hello World')" + +* JSFFI functions + + This will only work with ``ghcjs``:: + + import GHCJS.Types (JSVal) + + foreign import javascript unsafe + "try { $r = $1 / $2; } catch (e) { $r = "error"; }" + divide :: Double -> Double -> JSVal + + See https://github.com/ghcjs/ghcjs/blob/master/doc/foreign-function-interface.md + +Capturing DOM events with FFI +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Many of the Web APIs work on a `callback` mechanism, where a user supplied function will be called. Many of these APIs in JS code start with `on` prefix. + +Example JS code for creating an AudioNode to handle audio data, +`Source `_ :: + + // Give the node a function to process audio events + scriptNode.onaudioprocess = function(audioProcessingEvent) { + // The input buffer is the song we loaded earlier + var inputBuffer = audioProcessingEvent.inputBuffer; + .. + } + +Similar callback can be created by using the ``on`` API from ``GHCJS.DOM.EventM`` :: + + -- here audioProcess is the equivalent "tag" for JS onaudioprocess + + myNode :: ScriptProcessorNode + + liftJSM $ on myNode audioProcess myAudioProcessHandler + + myAudioProcessHandler :: EventM ScriptProcessorNode AudioProcessingEvent () + myAudioProcessHandler = do + -- aEv :: AudioProcessingEvent + aEv <- ask + buf <- getInputBuffer aEv + .. + +Exception Handling +~~~~~~~~~~~~~~~~~~ + +.. Add proper exception handling + + jsFn <- eval "(function (cb) { try{cb();alert(1)}catch(e){console.warn(e);alert(2)} })" + + (funJsv) <- function $ \ _ _ args -> do + io $ print 13333 + call jsFn jsFn [funJsv] + + + +Integrating CSS and embed in HTML +------------------------------------ + +``reflex-dom`` has the following entry points for embedding CSS and a head widget:: + + mainWidget :: (forall x. Widget x ()) -> IO () + + mainWidgetWithHead :: (forall x. Widget x ()) -> (forall x. Widget x ()) -> IO () + + -- Share data between head and body widgets + mainWidgetWithHead' :: (a -> Widget () b, b -> Widget () a) -> IO () + + -- import Data.FileEmbed -- from file-embed package + -- This requires TemplateHaskell + -- customCss :: ByteString + -- customCss = $(embedFile "src/custom.css") + mainWidgetWithCss :: ByteString -> (forall x. Widget x ()) -> IO () + + mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> IO () + +``reflex-dom-core`` provides equivalent functions in ``Reflex.Dom.Main`` for use with ``jsaddle-warp`` + + + +Deploying +--------- + +Nix based server +~~~~~~~~~~~~~~~~ + +If your server has ``nix`` installed then the steps to deploy are quite simple. + +If you are using :ref:`reflex_project_skeleton` or following `project-development.md `_ +follow the instructions and create the ``nix-build`` outputs of your backend and frontend projects. + +* Frontend + + For ``ghcjs`` based projects the ``frontend-result`` will contain the *.js files which you can simply copy to the desired location on server. + + For information on the use of closure compiler to reduce the size of ``all.js`` see https://github.com/ghcjs/ghcjs/wiki/Deployment + +* Backend + + For ``backend-result`` once you have the build products ready, copy them to server using:: + + # or nix copy, if using nix 2.0 + $ nix-copy-closure --to someuser@server.org backend-result + + You will have to configure the server's nix configuration and add `someuser` to trusted users:: + + For NixOS add this to ``/etc/nixos/configuration.nix``:: + + nix.trustedUsers = [ "someuser" ]; + + For non NixOS, add this to ``/etc/nix/nix.conf``:: + + trusted-users = someuser + + On the server then use the same nix-path + +Miscellaneous +------------- + +Rendering image from ``ByteString`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have the encoded image data as ``ByteString`` then you can render the image in browser using the `img` tag in combination with `createObjectURL`. + +This API will create a URL which can be specified in the ``img`` tag's ``src`` attribute:: + + foreign import javascript unsafe "window['URL']['createObjectURL']($1)" createObjectURL_ :: Blob.Blob -> IO JS.JSVal + + createObjectURL :: ByteString -> IO Text + createObjectURL bs = do + let opt :: Maybe JS.BlobPropertyBag + opt = Nothing + -- bsToArrayBuffer :: MonadJSM m => ByteString -> m ArrayBuffer + ba <- bsToArrayBuffer bs + b <- Blob.newBlob [ba] opt + url <- createObjectURL_ b + return $ T.pack $ JS.fromJSString $ JS.pFromJSVal url + +Android / iOS Apps +------------------ + +On a mobile device the speed of a ``ghcjs`` based browser app can be extremely bad. But the good news is that with little effort the ``reflex-dom`` apps can be compiled to run as a native mobile app. The performance of these apps can be considerably faster (of the order of 10x) as the haskell runtime runs on the actual processor. + +See the README of :ref:`reflex_project_skeleton` or `project-development.md `_ +for instructions of creating an android or iOS app from your frontend project. + +Also see: https://github.com/gonimo/gonimo + +.. note:: Cross-compiling currently doesn't support Template Haskell, so replace all the ``makeLenses``, etc code with generated splices + +.. todo:: Expand this section diff --git a/doc/conf.py b/doc/conf.py index 1ab0e5f..9ed19ca 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,7 +49,7 @@ # General information about the project. project = 'Reflex' -copyright = '2017, Divam' +copyright = '2018, Divam' author = 'Divam' # The version info for the project you're documenting, acts as replacement for diff --git a/doc/guide_to_ajax.rst b/doc/guide_to_ajax.rst deleted file mode 100644 index ebe27be..0000000 --- a/doc/guide_to_ajax.rst +++ /dev/null @@ -1,55 +0,0 @@ -.. _guide_to_ajax: - -A Guide to AJAX ---------------- - -.. Not sure whether this section as a whole should be categorised as AJAX - -XHR -~~~ - - -WebSockets -~~~~~~~~~~ - -Use ``webSocket`` API from the ``Reflex.Dom.WebSocket`` module.:: - - webSocket :: Text -> 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. - - -.. - I used a very similar architecture with Reflex with HSnippet, and it's - delightful to work wth. 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. - - https://github.com/mightybyte/hsnippet/blob/master/shared/src/HSnippet/Shared/WsApi.hs - diff --git a/doc/guide_to_dom_creation.rst b/doc/guide_to_dom_creation.rst deleted file mode 100644 index 58b7eff..0000000 --- a/doc/guide_to_dom_creation.rst +++ /dev/null @@ -1,233 +0,0 @@ -.. _guide_to_dom_creation: - -A Guide to DOM Creation -======================= - -The ``reflex-dom`` package provides a lot of helpful APIs to construct DOM widgets, do AJAX or any other arbitrary IO. - -See `Quick Ref `_ - -.. todo:: Add links to latest haddock - -DOM creation works in ``MonadWidget``. Since it is monadic, the sequence of widget APIs directly correspond to the sequence of DOM elements. - -.. todo:: Discuss Reflex-Dom entry point functions - - briefly explain these clases here? - - MonadWidget, WidgetHost, Widget -.. - -- Reflex-Dom entry point. Takes a monadic widget-building action of lengthy - -- type and turns it into an IO action. - [I] mainWidget :: - Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO () - [I] mainWidgetWithHead :: - Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> - Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO () - [I] mainWidgetWithCss :: - ByteString -> - Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO () - - -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 :: (MonadWidget 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!" - -- Should we have a 'textbr' API with line break at the end? - 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. - -Also you can create dynamic widgets by using static widgets, ie the widget -which don't take dynamic values as inputs (like Text -> m (Event t a)). -This can be done simply by mapping the Dynamic values over these widgets (with -mapDyn fmap??) and using ``dyn``.:: - - - txtInpEl <- textInput $ def {_textInputConfig_initialValue = "Button Text"} - - -- Use the library API button which accepts static Text - -- and modify its value by using a (Dynamic t Text) - dyn (button <$> (value txtInpEl)) - -Library Widgets with Dynamic input -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -Change the attributes of a DOM element via Dynamic values. Use - -``dynText elDynAttr elDynClass display`` - -``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
    with one
  • per child; clicking - a
  • 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`` - -See input_widgets.hs for usage of these widgets - -.. todo:: Add a link to page with demo of widgets - or may be Haddock documentation? - - -Dynamic widgets based on Events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Create a widget which updates whenever ``Event`` occurs. - -If you have a widget which depends on some event (like AJAX response), but you -need to display something else instead of a blank. :: - - -- ajaxResponseEv :: Event t SomeData - -- displaySomeData :: SomeData -> m () - - -- widgetHold :: m a -> Event t (m a) -> m (Dynamic t a) - widgetHold (text "Loading...") (displaySomeData <$> ajaxResponseEv) - - -Dynamic widgets on Dynamic Collections -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you have a collection of Dynamic values, then it is straighforward to use -them to create a dynamic DOM. But if your collection is itself a Dynamic then -use these APIs:: - - simpleList :: Dynamic t [v] -> (Dynamic t v -> m a) -> m (Dynamic t [a]) - list :: Dynamic t (Map k v) -> (Dynamic t v -> m a) -> m (Dynamic t (Map k a)) - - - -- * Widgets on Collections - listWithKey - listWithKey' - listWithKeyShallowDiff - listViewWithKey - - listHoldWithKey - - partitionMapBySetLT?? - -.. What is Workflow?? - -SVG ---- - -Use ``elDynAttrNS'`` along with SVG namespace:: - - elSvgns = elDynAttrNS' (Just "http://www.w3.org/2000/svg") - - -Troubleshooting type-class errors ---------------------------------- - -There are a few common compile time errors which can occue while using the -widgets - -#. If you define a widget but don't use it any where :: - - -- 't' is not used anywhere - let t = textInput $ def - - Compile error - - • Couldn't match type ‘DomBuilderSpace m0’ with ‘GhcjsDomSpace’ - arising from a use of ‘textInput’ - The type variable ‘m0’ is ambiguous - • In the expression: textInput $ def - In an equation for ‘t’: t = textInput $ def - - - Solution: Simply comment this code or use it. - -.. http://stackoverflow.com/questions/41367144/haskell-how-to-fix-the-type-variable-ambigous-compiler-error - - -.. - https://www.reddit.com/r/reflexfrp/comments/3h3s72/rendering_dynamic_html_table/ - - I finally figured out how to render a dynamic table. Here's a sample code: - h1_ $ text "Fetch table" - clickEvent <- button "Fetch records" - - let req = xhrRequest "GET" "/users/list" def - asyncReq <- performRequestAsync (tag (constant req) clickEvent) - - resp <- holdDyn (Just []) $ fmap decodeXhrResponse asyncReq - h1_ $ text "The table" - x2 <- mapDyn fromJust resp - renderUserTable x2 - - renderUserTable xsd = do - xsTabled <- mapDyn makeTable xsd - dyn xsTabled - - makeTable xs = do - el "table" $ do - el "tr" $ do - el "th" $ text "User Name" - el "th" $ text "Age" - el "th" $ text "Department" - el "th" $ text "On Hold Status" - forM xs $ \u -> do - el "tr" $ do - el "td" $ text (show (userName u)) - el "td" $ text (show (userAge u)) - el "td" $ text (show (userDept u)) - el "td" $ text (userStatus u) - - As you can see i used the function dyn to create a dynamic html table. Unfortunately i could not figure out how to use other functions like - tableDynAttr, listWithKey etc. - Complete lack of documentation makes it hard for me to comprehend how those functions work. - It would be great if someone posted simple examples of how to use some of the functions from Reflex.Dom.Widget modules. - diff --git a/doc/guide_to_event_management.rst b/doc/guide_to_event_management.rst deleted file mode 100644 index d4e5dae..0000000 --- a/doc/guide_to_event_management.rst +++ /dev/null @@ -1,337 +0,0 @@ -.. _guide_to_event_management: - -A Guide to Event Management -=========================== - -The reflex package provides many APIs to create the control logic of reflex app -which is independent of the DOM. - -See `Quick Ref `_ - -In order to leverage the full power of reflex, one has to effectively use -ability to create Event propagation graphs. This guide gives an overview of -various useful techniques. - -Overview --------- - -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 change in DOM, AJAX request or -change the internal state of application. - -In Reflex this response can be expressed or implemented by - -1. Firing another ``Event``. -2. Modification of a ``Dynamic`` Value. - -Note that there is 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. - -The following sections covers details of constructing this graph. - -Event ------ - -Creation -~~~~~~~~ - -The following are the primary sources of events - -#. DOM - - #. Input fields like button, text-box, etc. - - See the type of input fields (``TextInput RangeInput`` etc) - to see what events are available. - - #. User interaction events like mouse click, mouse over, etc. - - ``domEvent`` API can be used to create ``Event`` on DOM elements:: - - (e,_) <- el' "span" $ text "Click Here" - - clickEv :: Event t () - clickEv <- domEvent Click e - - For a complete list of events accepted by ``domEvent`` see ``EventName`` - - .. todo:: Add a link to haddock - -#. Response from AJAX request or WebSocket connection - see :ref:`guide_to_ajax` - -#. ``Dynamic`` values - By calling ``updated`` on a ``Dynamic`` value one can obtain the event - when its value changes. - -Manipulation -~~~~~~~~~~~~ - -Using these primary ``Event``\s you can create secondary / derived events by - -#. Manipulated the value using fmap:: - - -- inputValueEv :: Event t Int - - doubledInputValueEv = ffor inputValue (* 2) - -#. Filter the value:: - - -- inputValueEv :: Event t Int - - -- This Event will fire only if input value is even - evenOnlyEv = ffilter even inputValueEv - - Use ``fmapMaybe fforMaybe`` for similar filtering - -#. Tagging value of ``Dynamic`` or ``Behavior``. - - Use these APIs, see - `Quick Ref `_ - :: - - tagPromptlyDyn, tag, attachDyn, attachDynWith, attachPromptlyDynWithMaybe - -.. todo:: Explain the sampling of Dynamic: Promptly vs delayed? - - May be an example which showcase the correct and wrong usage - -Behavior --------- - -The sink of a behavior is ``sample``, so it is only useful when a widget is created -dynamically by sampling some behavior, but remain static after its creation. - - -Dynamic -------- - -Creation -~~~~~~~~ - -The following are the primary sources of ``Dynamic`` values - -#. DOM - - #. Input fields like text-box, range input etc. - - See the type of input fields (``TextInput RangeInput`` etc) - -.. Any other places where we can get Dynamic?? - -Event to Dynamic -~~~~~~~~~~~~~~~~ - -Create a ``Dynamic`` which changes value when ``Event`` occurs:: - - holdDyn :: a -> Event t a -> m (Dynamic t a) - foldDyn :: (a -> b -> b) -> b -> Event t a -> m (Dynamic t b) - -These can be utilised to maintain a state in application. -For more see :ref:`maintain_state` - -Manipulation -~~~~~~~~~~~~ - -Using these primary ``Dynamic`` values you can create secondary / derived values by - -#. ``fmap`` - -#. ``zipDyn zipDynWith`` - - Zipping is useful when multiple ``Dynamic`` values have a common point of influence - in the application. - - For example if I have two variable parameters like color and font of text. - Then I can construct the dynamic attributes from these parameters by simply - zipping them together.:: - - -- textFont :: Dynamic t Text - -- textColor :: Dynamic t Text - - getAttr (f,c) = ("style" =: ("font-family: " <> f "; color: " <> c)) - - elDynAttr "div" (getAttr <$> (zipDyn textFont textColor)) $ text "Text" - -Simple Event Propagation Graph --------------------------------- - -.. Its probably better to just give some example here? - -Simple -~~~~~~ - -Simply pass the Event/Dynamic to input of function - -In monadic code create simple event propagation tree - -Recursive Do -~~~~~~~~~~~~ - -In Monadic code - create a cyclic graph of event propagation - -Problems in cyclic dependency - -#. Deadlock - Runtime deadlock due to block on an MVar operation - This can occur if a widget depends on an Event which is created - in a ``let`` clause after the widget creation. - To fix this simply move the ``let`` clause before the widget creation - -#. Loop - Output of holdDyn feeds back can cause this?? - - -.. _maintain_state: - -Maintaining State via fold --------------------------- - -In order to store a state/data for your app (ie create a state machine) simply -use ``foldDyn`` - -:: - - -- State can be any arbitrary haskell data - -- stateDynVal :: Dynamic t MyState - - -- ev can a collection of all events on which the state depends - -- For example all input events - -- ev :: Event t Inputs - - -- This is a pure API which can process the input events and current state - -- to generate a new state. - -- eventHandler :: (Inputs -> MyState -> MyState) - - -- foldDyn :: (a -> b -> b) -> b -> Event t a -> Dynamic t b - stateDynVal <- foldDyn eventHandler initState ev - -Even nested state machines can be designed if your have a state with nested ``Dynamic`` value -by using ``foldDynM`` - -see nested_dynamic.hs - -Use ``foldDynMaybe``, ``foldDynMaybeM`` in cases where you want to filter input -events, such that they don't modify the state of application. - -For example in a shopping cart if the user has not selected any items, the "add -to cart" button should do nothing. This kind of behavior can be implemented by -returning ``Nothing`` from the eventHandler. - - -Using Collections in Event propagation graph --------------------------------------------- - -In order to model complex flows of events or dynamically changing data -collection, we need to use higher order containers like lists (``[]``) or Maps -(``Data.Map``) - -.. todo:: This section is relevant with appropriate examples - - So add examples here - -Use of Dynamic t [], Dynamic t (Map k v), etc - -User data model design : separate guide? - -Fanning -~~~~~~~ - -Split or distribute the event - -.. todo:: How to effectively use fan? EventSelector? - -Merging/Switching -~~~~~~~~~~~~~~~~~ - -``Dynamic`` values can be merged simply by ``zipDyn``, ``mconcat``, etc. - -``Events`` - - Given some events you can choose either to keep them all by using ``align`` - align - If two events can possibly happen together (because of a common driver - perhaps), then use this to capture them in a single event. - - or select just one from the list using ``leftmost`` - - or use one of these to merge - mergewith, mergeList - returns a NonEmpty list - - -Higher order FRP ----------------- - -Nested Values and flattening -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When you model real world ``Dynamic`` values many times you end up with nested -structures. - -For example, if the value of items in a shopping cart depends on the shipping -method chosen, then you can end up with a value ``total' :: Dynamic t [Dynamic t Int]``:: - - selectedItems :: Dynamic t [Item] - isExpeditedShipping :: Dynamic t Bool - - total' = Dynamic t [Dynamic t Int] - total' = ffor selectedItems - (map getItemPrice) - - getItemPrice :: Item -> Dynamic t Int - getItemPrice itm = ffor isExpeditedShipping - (\case - True -> (itemPrice itm) + (shippingCharges itm) - False -> itemPrice itm) - -In such cases in order to get a total value ``Dynamic t Int``, you need to use -flattening APIs. In case of ``Dynamic`` it is simply ``join`` from -``Control.Monad`` (since ``Dynamic`` has an instance of ``Monad``):: - - total'' :: Dynamic t (Dynamic t Int) - total'' = foldr1 (\a b -> (+) <$> a <*> b) <$> total' - - total :: Dynamic t Int - total = join total'' - -See `QuickRef `_ -for details on other flattening APIs. - - - -.. Push/Pull APIs? - -.. Note from Divam - The ``Reflex`` typeclass provides functions which I think - are not important discussing here? - Similarly MonadSample, MonadHold are not relevant in introduction - They are relevant in QuickRef which lists the API and their constraints - - - -.. just some other content, may be relevant here - - https://www.reddit.com/r/reflexfrp/comments/3bocn9/how_to_extract_the_current_value_from_a_text_box/ - - Event is probably as you understand it, discrete events. Behavior's are values which change over time (but you don't know when they changed) - and a Dynamic is Event + Behavior, values which change over time, and you're notified when they change, too. - The problem with your example, is that omg is not an Event, Behavior or Dynamic but just a String (so it will never change). - What you might want to do is tag the event with the value from the text box like this: - omg <- mapDyn (\t -> "myUrl/" ++ t ++ "/me") value questionBox - dyn <- mkAsyncDyn "default" $ tag (current omg) insertEvent - This way omg is a Dynamic, so it can change over time. Then we tag the event with the value of the behavior current omg. - (Note that if we used directly tagDyn omg insertEvent the event would fire both when omg changed as well as when the button was clicked, which is not what we want) - mkAsyncDyn :: MonadWidget t m => T.Text -> Event t String -> m (Dynamic t (Maybe T.Text)) - mkAsyncDyn defaultValue event = do - ev <- performRequestAsync $ fmap (\url -> xhrRequest "GET" url def) event - holdDyn (Just defaultValue) $ fmap _xhrResponse_body ev - So the takeaway here is that for values to update they need to be reactive type (Event, Behavior, Dynamic), sample is almost never what you want to do. - - - https://www.reddit.com/r/reflexfrp/comments/4nyteu/joindyn_and_eboth/ - http://anderspapitto.com/posts/2016-11-09-efficient-updates-of-sum-types-in-reflex.html - diff --git a/doc/index.rst b/doc/index.rst index 3ffbe17..13d2e56 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,7 +6,9 @@ Welcome to Reflex-FRP documentation! ================================== -This is a work-in progress, so many sections are incomplete or empty... +If you are new to reflex, then check out some :ref:`tutorials` + +This documentation is a work-in-progress, so some of the sections are incomplete or empty. Please feel free to contribute to this Documentation by opening a pull-request `here `_ @@ -17,26 +19,21 @@ Contents: :maxdepth: 3 installation - architecture - guide_to_dom_creation - guide_to_event_management - guide_to_ajax - debugging - advanced_topics - -.. todo:: Add Haddock documentation + overview + reflex_docs + reflex_dom_docs + app_devel_docs + non_dom_reflex + resources - Reflex API reference - Reflex-dom API reference +.. + Indices and tables + ================== - Can we preview the API from haddock here directly ? + * :ref:`genindex` + * :ref:`modindex` + * :ref:`search` -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - +.. todo:: Add Haddock documentation diff --git a/doc/installation.rst b/doc/installation.rst index c141f14..a0734b3 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -1,10 +1,212 @@ - Installation ------------- +============ + + +Overview +-------- + +The essential components required for developing ``reflex`` based application are + +#. GHC or GHCJS + + If you are building a web application with ``reflex-dom`` then you need ``ghcjs`` to create JavaScript output. + With ``ghc`` you can use the ``reflex-dom`` to create a ``webkit`` based desktop or mobile app. + +#. Reflex library + + The current supported ``reflex`` and ``reflex-dom`` packages (version 0.5 and 0.4 respectively) are available only through Github, as they are not yet published on Hackage. + +To quickly get started with developing full-stack web apps using reflex, +`Obelisk `_ +is the recommended method. + +For a more advanced usage, ``reflex-platform`` is the recommended method. + +.. _obelisk: + +Obelisk +------- + + `Obelisk `_ is a command line tool and a set of libraries to make it easy to get started with full-stack web development with ``reflex``. + It includes features like + + * Automatic installation of latest ``reflex``, ``ghc``, ``ghcjs`` and haskell dependencies/libraries using ``nix``. + + * Create a skeleton project with + + * frontend using ``reflex-dom`` + * backend using ``snap``, with pre-rendering support. + + * Development workflow related commands like + + * ``ob run`` to automatically rebuild your application on a file write. + It also serves the frontend using ``jsaddle-warp``, to help in faster development. + * ``ob repl`` to provide a ``ghci`` shell. + * ``ob deploy`` to help in deployment to EC2, and create optimised/minified ``js``. + * Create android app ``.apk`` file, and ``iOS`` app with ``nix`` commands. + + * Routing library ``obelisk-route`` to create type safe routes + + see :ref:`obelisk_route` + +.. _reflex_platform + +``reflex-platform`` +------------------- + + `reflex-platform `_ is a collection of ``nix`` expressions and scripts to provide ``ghc``, ``ghcjs`` and a curated set of packages for use with ``reflex-dom``. + + This includes a specially modified ``text`` package which internally uses the JS string. + The performance of this ``text`` package is significantly better on the browser. + +.. note:: + GHCJS uses a lot of memory during compilation. 16GB of memory is recommended, with 8GB being pretty close to bare minimum. + +.. _reflex_project_skeleton: + +``reflex-project-skeleton`` +--------------------------- + + `reflex-project-skeleton `_ is a bare repository which uses ``reflex-platform`` to provide a nice development enviroment, with both the power of ``nix`` (for binary cache of dependencies) and ``cabal new-*`` commands (for incremental builds). + + For a project with both a ``backend`` and ``frontend`` components, this is the recommended setup. + + See README and ``reflex-project-skeleton/reflex-platform/project/default.nix`` for usage details. + + This also supports cross-compiling the ``frontend`` part to android and iOS platforms! + + The following contains information of creating a project-skeleton from scratch and also more details about its working. + + https://github.com/reflex-frp/reflex-platform/blob/develop/docs/project-development.md + +Minimal dev-env using ``reflex-platform`` +----------------------------------------- + + Please refer to `reflex-platform' README `_ + + The ``try-reflex`` script can create a development environment with ``ghc`` and ``ghcjs``. You can use this to have a quick starting setup to compile code-snippets and smaller projects. + + When using this for the first time, setup can take considerable time to download all the dependencies from the binary cache. + + But for a non-trivial project it is recommended to use ``cabal``. + + +Using cabal with ``reflex-platform`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + If you dont have a project with cabal file then use ``cabal init`` to create one. + + Then use the ``workon`` script from reflex-platform to create a development environment (nix-shell) according to the dependencies specified in cabal file. + :: + + $ ~/reflex-platform/scripts/work-on ghcjs ./your-project + + # or just "cabal configure" if working on ghc + $ cabal configure --ghcjs + $ cabal build + + .. note:: The ``cabal update`` and ``cabal install`` commands should not be used, as the task of fetching and installing dependecies is done by ``nix``. + + This will use your package's cabal file to determine dependencies. If you have a ``default.nix``, it will use that instead. Note that your project's path must include at least one slash (``/``) so that work-on can detect that it is a path, rather than a package name. + + This will give you the exact environment needed to work with the given package and platform, rather than the general-purpose environment provided by the Reflex Platform. + + You can replace ghcjs with ghc to hack on the native GHC version of the package (including with GHCi if you want). You can also use a package name instead of a path, which will drop you into the standard build environment of that package; this works even if you don't yet have the source for that package. + + +Add reflex-platform to project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + .. note + The ``reflex-project-skeleton`` does this, and has many additional benefits + + Since the build environment is dependent on the reflex-platform, it is important to keep this dependency as a part of the project. Moreover the version of libraries will change with time in the reflex-platform so it is important to keep a reference to the reflex-platform' "version" which has been used to build the project. + + The simplest way to do this is to create a submodule in your project, and use the ``workon`` script from it to create a shell with proper build dependencies. + + Assuming you are using git for versioning:: + + git submodule add https://github.com/reflex-frp/reflex-platform + + # Then use the workon script to get the nix-shell + ./reflex-platform/scripts/work-on ghcjs ./. + + A better way is to use the ``nix`` commands, see :ref:`reflex_project_skeleton` or `project-development.md `_ + + +.. _haddock_and_hoogle: + +Local Haddock and Hoogle +------------------------ + + Local hoogle server can be run from the shell created for development environment by :: + + $ hoogle server --local + + To obtain a shell; if you are using + + * ``reflex-project-skeleton`` or ``obelisk`` then do:: + + $ nix-shell -A shells.ghc + + * ``reflex-platform``: Create a shell from either ``try-reflex`` or ``workon``:: + + From this shell the path of local haddock documentation can also be obtained using:: + + # or use ghcjs-pkg + $ ghc-pkg field haddock-html + + +GHCi / ghcid with ``jsaddle-warp`` +---------------------------------- + +* ``reflex-project-skeleton``: + + For a simple ghci repl do:: + + $ ./cabal new-repl frontend + + or create a shell using nix-build:: + + $ nix-shell -A shells.ghc + $ cabal new-repl frontend + + See the README of the project for more details + + For ``ghcid`` you might have to run the ghcid from the frontend directory so that it detects the ``src`` folder correctly :: + + $ cd frontend; ghcid -c "cd ..; ./cabal new-repl frontend" + +* ``reflex-platform``: + + Create a shell from either ``try-reflex`` or ``workon`` + and use the regular ``cabal repl`` or ``ghcid`` commands from your project root. + +With ``jsaddle-warp`` package you can run your app in browser without using ``ghcjs``. +You need to modify the ``main`` like the code below. Then you can run it via ``ghci`` or ``ghcid``, and open your application from browser via http://127.0.0.1:3911/:: + + module Main where + + import Reflex.Dom.Core + import Language.Javascript.JSaddle.Warp + + main = run 3911 $ mainWidget $ text "hello" + +This should works fine on Chrome/Chromium, but might not work with firefox. + +IDE tools support +----------------- + +Instructions for setting emacs/spacemacs are here : https://github.com/reflex-frp/reflex-platform/pull/237 + +Contributing to Reflex +---------------------- -.. todo:: copy from reflex-platform, it has to provide all the possible ways - user might need to install including stack, nix, nixos, ... +To contribute to ``reflex`` or ``reflex-dom`` packages, it is best to use ``reflex-platform``. +The ``hack-on`` script will checkout the source of the package in your local ``reflex-platform`` directory as a git submodule, and use it to provide the development environment.:: -Please refer to `reflex-platform `_ + $ ./scripts/hack-on reflex -- or reflex-dom +You can then patch the source code, test your changes and send a PR from the git submodule. +.. todo:: Add ways to use reflex without nix / reflex-plarform diff --git a/doc/non_dom_reflex.rst b/doc/non_dom_reflex.rst new file mode 100644 index 0000000..17652e5 --- /dev/null +++ b/doc/non_dom_reflex.rst @@ -0,0 +1,44 @@ + +Non-DOM related usage of ``reflex`` +=================================== + +The ``reflex`` FRP architecture (and package) can be used to create non-DOM based UI application and even some non-UI stuff like server. + +``reflex-host`` +--------------- + +Source : https://github.com/bennofs/reflex-host + +This provides a set of higher-level abstractions on top of the ``reflex`` FRP primitives. + +Using this library, you don't need to build your own event loop. You can just start registering external events and performing actions in response to FRP events. + +* https://github.com/dalaing/reflex-host-examples + + This has a set of examples using this package + +* https://github.com/dalaing/reflex-basic-host + + Contains an even simplified API interface + +UI +-- + +* https://github.com/reflex-frp/reflex-sdl2 + + Experimental SDL 2 based reflex app using sdl2 haskell bindings. + +* https://github.com/deech/fltkhs-reflex-host + + An experimental code for `FLTK GUI toolkit` based applications using reflex. + +* https://github.com/lspitzner/bricki-reflex + + Experimental ``brick`` based terminal UI. + + http://hexagoxel.de/postsforpublish/posts/2017-10-30-brick-plus-reflex.html + +Other +----- + +* https://github.com/dalaing/reflex-server-websocket diff --git a/doc/architecture.rst b/doc/overview.rst similarity index 55% rename from doc/architecture.rst rename to doc/overview.rst index 841fcd2..97e254a 100644 --- a/doc/architecture.rst +++ b/doc/overview.rst @@ -1,40 +1,30 @@ +Overview +======== -Architecture of a Reflex-DOM Application ----------------------------------------- +``reflex`` -A typical Reflex-DOM application consists of widgets, and some glue code to *connect* the widgets together. + provides the Functional Reactive Programming (FRP) implementation. -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. + 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. -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. + See `Quick Ref `_ -These controller 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-dom-core`` and ``reflex-dom`` -Reflex does not enforce a strict separation between these two, and user has the -complete flexibility to chose a suitable design. + provides a APIs for constructing DOM widgets, do websocket / XHR requests, etc. -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. + Most of the functionality is part of the ``reflex-dom-core`` package. -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. + See `Quick Ref `_ + +.. _reflex_basics: +Reflex Basics +------------- -Overview of Reflex Basics -~~~~~~~~~~~~~~~~~~~~~~~~~ -The reflex package provides the foundation for the FRP architecture through the -type class definitions, and the most important type class in this package is ``Reflex``. +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. @@ -64,13 +54,85 @@ The three main types to understand in ``Reflex`` are Behavior, Event, and Dynami viewed as a step function over time, with the value changing at every occurrence. + We use ``Dynamic`` in ``reflex-dom`` in a lot of places where you might expect to use ``Behavior`` 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 + +1. Firing another ``Event``. +2. 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 :ref:`reflex_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 :: @@ -110,7 +172,7 @@ Separate APIs to manage events and to render view :: 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.:: @@ -161,12 +223,13 @@ 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 :: +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 @@ -190,18 +253,61 @@ Quoting `mightybyte `_ 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. -Single Page App vs Other designs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Reflex is suitable primarily for single-page apps. +Overview of ``ghcjs`` and ``jsaddle`` Packages +---------------------------------------------- + + +``ghcjs`` + + Is the compiler, like ``ghc``. + +``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 with ``ghc``) + + Applications should use the ``ghcjs-dom`` package and the ``GHCJS.DOM.*`` modules it contains; to get the best mix of portability and performance (rather than using the ``jsaddle-dom``, ``ghcjs-dom-jsaddle`` and ``ghcjs-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 marshalling + + This package should be included in cabal only if using ``ghcjs`` by adding this :: + + if impl(ghcjs) + build-depends: ghcjs-base + +``jsaddle`` + + JavaScript interface that works with ``ghcjs`` or ``ghc``. + + 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`` using ``jsaddle-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. -.. todo:: Add ways to build non-single-page apps. + 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 :ref:`guide_to_event_management` for more info on how to construct the event graph using the APIs. + See `README `_ for more details. -.. See :ref:`guide_to_dom_creation` for more info on how to create DOM using APIs from Reflex-DOM. +``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 using ``jsaddle`` diff --git a/doc/reflex-dom.rst b/doc/reflex-dom.rst deleted file mode 100644 index 7e7694e..0000000 --- a/doc/reflex-dom.rst +++ /dev/null @@ -1,13 +0,0 @@ -Reflex Dom -========== - -Type Classes ------------- - -.. A reference for what the type class is for - -DomBuilder - -DOM Display related APIs ------------------------- - diff --git a/doc/reflex_docs.rst b/doc/reflex_docs.rst new file mode 100644 index 0000000..348d533 --- /dev/null +++ b/doc/reflex_docs.rst @@ -0,0 +1,617 @@ +Reflex +====== + +The ``reflex`` library provides the foundation Classes and their implementation APIs to do Functional Reactive Programming. +This is independent of the DOM creation code, and can be used to implement FRP architecture in non-web related apps also. + +The `Quick Ref `_ provides a really nice overview of its APIs. + + +FRP Basics +---------- + +In order to leverage the full power of reflex, one has to effectively use the +ability to create an Event propagation graphs, and use it to model the business logic. +This guide gives an overview of basics and various useful techniques. + +Also see :ref:`reflex_basics` + +.. _reflex_event: + +``Event`` +~~~~~~~~~ + +Creation +^^^^^^^^ + +.. _new_trigger_event: + +newTriggerEvent +*************** + + Is used to inject value in the ``reflex`` event-propagation-graph from outside using IO action:: + + newTriggerEvent :: TriggerEvent t m + => m (Event t a -- Event triggered by fun + , a -> IO ()) -- fun + + ``newTriggerEvent`` can also be used to break a big ``rec`` block.:: + + rec + ev1 <- widget1 evN + + .. + .. + + evN <- widgetN evN_1 + + In this the ``widgetN`` and many other widgets in-between can be pulled outside the ``rec`` block:: + + (evN, evNIOAction) <- newTriggerEvent + + ev1 <- widget1 evN + + .. + .. + + evN' <- widgetN evN_1 + + performEvent $ ((\v -> liftIO $ evNIOAction v) <$> evN') + +From ``Dynamic`` +**************** + + By calling ``updated`` on a ``Dynamic`` value one can obtain the event when its value changes.:: + + updated :: (Reflex t) => Dynamic t a -> Event t a + +Repeating Events +**************** + + Using APIs from ``Reflex.Time`` one can create repeating events.:: + + tickLossy :: (_) + => NominalDiffTime -- in seconds + -> UTCTime + -> m (Event t TickInfo) + + ``tickLossy`` will create an ``Event`` every ``n`` seconds. Though it is not guaranteed to always fire an ``Event`` after the elapsed time, especially if the value ``n`` is very small. + + There are many more APIs in this module to generate repeating events based on more complex algorithms. + +From DOM widgets +**************** + + When doing DOM based programming using ``reflex-dom-core``, a number of widgets provide ``Event`` in response to the external events. + + * Input fields like button, text-box, drop down, etc. + + See :ref:`dom_input_elements` + + * User interaction events like mouse click, mouse over, etc. + + See :ref:`dom_events` + + * Response from XHR / AJAX / websocket requests + + See :ref:`xhr_websocket` + + * Arbitrary ``on`` events from the browser + + See :ref:`ffi` + +Manipulation +^^^^^^^^^^^^ + +Using these primary ``Event``\s you can create secondary / derived events by + +#. Manipulating the value using ``Functor`` / ``fmap``:: + + -- inputValueEv :: Event t Int + + doubledInputValueEv = ffor inputValue (* 2) + +#. Filtering the value:: + + -- inputValueEv :: Event t Int + + -- This Event will fire only if input value is even + evenOnlyEv = ffilter even inputValueEv + + Use ``fmapMaybe fforMaybe`` for similar filtering + +#. Multiple events can be combined using + + Merges the value `a` :: + + <> :: Semigroup a => Event a -> Event a -> Event a + + + This fires the `a` event only when `b` is not firing at the same time:: + + difference :: Event a -> Event b -> Event a + + Combine two separate events:: + + align :: Event a -> Event b -> Event (These a b) + alignWith :: (These a b -> c) -> Event a -> Event b -> Event c + + Combine a list of events:: + + mergeWith :: (a -> a -> a) -> [Event a] -> Event a + mergeList :: [Event a] -> Event (NonEmpty a) + + Drop all except the `leftmost` event:: + + leftmost :: [Event a] -> Event a + + Other APIs:: + + mergeMap :: Ord k => Map k (Event a) -> Event (Map k a) + merge :: GCompare k => DMap (WrapArg Event k) -> Event (DMap k) + +#. Tagging value of ``Dynamic`` or ``Behavior``. + + Using these APIs, see + `Quick Ref `_ + :: + + gate :: Behavior Bool -> Event a -> Event a + tag :: Behavior a -> Event b -> Event a + tagPromptlyDyn :: Dynamic a -> Event b -> Event a + attach :: Behavior a -> Event b -> Event (a, b) + attachPromptlyDyn :: Dynamic a -> Event b -> Event (a, b) + attachWith :: (a -> b -> c) -> Behavior a -> Event b -> Event c + attachPromptlyDynWith :: (a -> b -> c) -> Dynamic a -> Event b -> Event c + attachWithMaybe :: (a -> b -> Maybe c) -> Behavior a -> Event b -> Event c + attachPromptlyDynWithMaybe :: (a -> b -> Maybe c) -> Dynamic a -> Event b -> Event c + <@> :: Behavior (a -> b) -> Event a -> Event b + <@ :: Behavior a -> Event b -> Event a + + The below will create an event which will fire whenever the Dynamic changes and give the *old* value of the Dynamic. + :: + tag (current dyn) $ updated dyn + + +``Behavior`` +~~~~~~~~~~~~ + +``Behavior`` value can be tagged with an ``Event`` using ``tag`` or ``attach``, or it can be sampled in a widget, when it is first created using ``sample``. + +``Dynamic`` +~~~~~~~~~~~ + +Creation +^^^^^^^^ + + Create a ``Dynamic`` which changes value when ``Event`` occurs:: + + holdDyn :: (MonadHold t m) => a -> Event t a -> m (Dynamic t a) + + There are also a number of input APIs in ``reflex-dom-core`` which provide ``Dynamic`` values in the context of DOM. See :ref:`dom_input_elements` + +Manipulation +^^^^^^^^^^^^ + + Using some primary ``Dynamic`` values you can create secondary / derived values by + + * ``fmap`` - Simply use ``Functor`` instance when only one ``Dynamic`` value is being manipulated. + + * Combine multiple ``Dynamic`` values using:: + + zipDyn :: Reflex t => Dynamic t a -> Dynamic t b -> Dynamic t (a, b) + + zipDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Dynamic t b -> Dynamic t c + + Zipping is useful when multiple ``Dynamic`` values have a common point of influence + in the application. + + For example if you have two variable parameters like color and font of text. + Then you can construct the dynamic attributes from these parameters by simply + zipping them together.:: + + -- textFont :: Dynamic t Text + -- textColor :: Dynamic t Text + + getAttr (f,c) = ("style" =: ("font-family: " <> f <> "; color: " <> c)) + + elDynAttr "div" (getAttr <$> (zipDyn textFont textColor)) $ text "Text" + + * Using ``Applicative``:: + + -- dInt1, dInt2, dInt3 :: Dynamic t Int + let + eInt :: Dynamic t (Int, Int, Int) + eInt = (,,) <$> dInt1 <*> dInt2 <*> dInt3 + + Much more complicated things can be done using ``traverse``/ ``sequenceA``:: + + -- mDyn :: Map k (Dynamic t Int) + let + dMap :: Dynamic t (Map k Int) + dMap = sequenceA mDyn + + + .. note:: ``zipDynWith`` is more efficient than ``f <$> d1 <*> d2`` + +``Reflex`` +~~~~~~~~~~ + +The ``Reflex`` class provides the basic functionality for FRP. It provides the basic functions to efficiently handle the ``Event``, ``Behavior`` and ``Dynamic`` values. +All the `pure` APIs like ``tagDyn``, ``zipDyn``, etc are created using the functionality provided through ``Reflex`` class. + +The other two most important features required for FRP are maintaining some state, and doing modifications based on events. This is provided from the two classes ``MonadHold`` and ``Adjustable``. + +Also see `QuickRef `_ + +``MonadHold`` +~~~~~~~~~~~~~ + +This is required to create any stateful computations with Reflex. +It designates monads that can create new ``Behavior`` s based on ``Event`` s.:: + + hold :: a -> Event t a -> m (Behavior t a) + + +``Adjustable`` +~~~~~~~~~~~~~~ + +A Monad that supports adjustment over time. After an action has been run, if the given events fire, it will adjust itself so that its net effect is as though it had originally been run with the new value.:: + + runWithReplace :: m a -> Event t (m b) -> m (a, Event t b) + + +Event Propagation Graph +----------------------- + +.. Its probably better to just give some example here? + +Simple Tree +~~~~~~~~~~~ + +Simply pass the ``Event``/``Dynamic`` values to input of functions. This will create kind of an event propagation flow from top to bottom. But no feedback-loops can be created, for that use ``RecursiveDo``. + +RecursiveDo +~~~~~~~~~~~ + +Is used to create a cyclic event propagation graph. Because the underlying mechanism of graph creation is monadic (using ``MonadHold``, etc). To create feedback-loops we need to use ``MonadFix``. + +The actual usage is quite simple:: + + -- Required extension for rec style blocks + -- {-# LANGUAGE RecursiveDo #-} + + rec + let + ev1 = f2 <$> ev2 + d1 <- widgetHold (w1Init) (w1 <$> ev1) + ev2 <- viewD1Widget d1 + +in this example the ``ev1`` is used to create a ``Dynamic`` value ``d1``, which is then shown to the user using ``viewD1Widget``. +This widget can in turn modify the value using the ``Event`` ``ev2``. + +But there are some pitfalls too, especially if you use 'Promptly' APIs like ``tagPromptlyDyn``, ``switchPromptlyDyn``, ``attachPromptlyDyn``, etc. +All these APIs take a ``Dynamic`` value as input, and if used incorrectly they can cause problems like hang, stack overflow, etc. + +In most cases you would want to use their corresponding APIs like ``tag``, ``switch``, ``attach``, etc (which all work on the ``Behavior`` values), along with ``current :: Dynamic t a -> Behavior t a``. + +see debugging :ref:`hang_stack_overflow` + +For more details checkout the articles on :ref:`monad_fix` + +.. _maintain_state: + +Maintaining State via fold +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to store a state/data for your app (ie create a state machine) simply +use ``foldDyn`` + +:: + + -- State can be any arbitrary haskell data + stateDynVal :: Dynamic t MyState + + -- ev can a collection of all events on which the state depends + -- For example all input events + ev :: Event t Inputs + + -- This is a pure API which can process the input events and current state + -- to generate a new state. + eventHandler :: (Inputs -> MyState -> MyState) + + -- foldDyn :: (a -> b -> b) -> b -> Event t a -> Dynamic t b + stateDynVal <- foldDyn eventHandler initState ev + +Even nested state machines can be designed if your have a state with nested ``Dynamic`` value by using ``foldDynM`` + +.. See ` DisplayGameUpdates/Main.hs `_ + +Use ``foldDynMaybe``, ``foldDynMaybeM`` in cases where you want to filter input +events, such that they don't modify the state of application. + +For example in a shopping cart if the user has not selected any items, the "add +to cart" button should do nothing. This kind of behavior can be implemented by +returning ``Nothing`` from the eventHandler. + + +``getPostBuild`` +~~~~~~~~~~~~~~~~ +:: + + getPostBuild :: PostBuild t m => m (Event t ()) + +This ``Event`` will fire once at the start of an action / DOM widget is created. Also each time that part of the DOM gets re-created (like if it is created from scratch via ``widgetHold``). This can be used to do communication with server or do some FFI. + +Note that the ``Event`` fires when the build action completes, but the fragment may not yet be in the browser DOM. So you might have to add some delay to this before accessing the DOM via some FFI. + +Doing IO via ``performEvent`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example:: + + doneEv <- performEvent (ffor triggerEv $ \val -> liftIO $ do + putStrLn "Doing some action" + someIOAction val) + + widgetHold (text "Waiting for action to complete") + (showResultOfAction <$> doneEv) + +.. todo:: Does the doneEv always occur in the frame after triggerEv? + +.. _debounce: + +Debounce, Delay, BatchOccurence +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Reflex.Time`` provides a set of useful APIs which come handy when you need to do real life event handling.:: + + debounce :: (_) => NominalDiffTime -> Event t a -> m (Event t a) + + -- Wait for user to stop typing for 0.5 sec, and then send a search request to server + + searchTextEv <- debounce 0.5 (_textInput_input someTextInput) + +When doing FFI calls ``delay`` may be required:: + + delay :: (_) => NominalDiffTime -> Event t a -> m (Event t a) + + + performEvent (abort <$ stopAndRestartEv) + delayedEv <- delay 0.2 stopAndRestartEv + performEvent (start <$ delayedEv) + +When handling a set of events from external sources many times the sequence of events is not deterministic, +or perhaps we want a ``debounce`` kind of functionality but dont want to miss any ``Event``. +In such cases we need to use ``batchOccurrences`` to properly model the logic. :: + + batchOccurrences :: (_) => NominalDiffTime -> Event t a -> m (Event t (Seq a)) + + + +Higher order FRP +---------------- + +Nested Values and flattening +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you model real world ``Dynamic`` values many times you end up with nested +structures. + +For example, if the value of items in a shopping cart depends on the shipping +method chosen, then you can end up with a value ``total' :: Dynamic t [Dynamic t Int]``:: + + selectedItems :: Dynamic t [Item] + isExpeditedShipping :: Dynamic t Bool + + total' = Dynamic t [Dynamic t Int] + total' = ffor selectedItems + (map getItemPrice) + + getItemPrice :: Item -> Dynamic t Int + getItemPrice itm = ffor isExpeditedShipping + (\case + True -> (itemPrice itm) + (shippingCharges itm) + False -> itemPrice itm) + +In such cases in order to get a total value ``Dynamic t Int``, you need to use +flattening APIs. In case of ``Dynamic`` it is simply ``join`` from +``Control.Monad`` (since ``Dynamic`` has an instance of ``Monad``):: + + total'' :: Dynamic t (Dynamic t Int) + total'' = foldr1 (\a b -> (+) <$> a <*> b) <$> total' + + total :: Dynamic t Int + total = join total'' + +See `QuickRef `_ +for details on other flattening APIs. + +Dynamic widgets on Dynamic Collections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to model complex flows of events or dynamically changing data +collection, we need to use higher order containers like lists (``[]``) or Maps +(``Data.Map``). + +To effectively work with such ``Dynamic`` collections, ``Reflex.Collection`` provides a bunch of APIs. + +See Quickref for a summary of these APIs +https://github.com/reflex-frp/reflex/blob/develop/Quickref.md#collection-management-functions + +.. + A tutorial on this is in pipeline by dalaing + + +``Reflex.Network`` +~~~~~~~~~~~~~~~~~~ + +Provides these APIs. +If you look closely they are the equivalent of ``dyn`` and ``widgetHold``, but work in non-DOM applications.:: + + networkView :: (Reflex t, NotReady t m, Adjustable t m, PostBuild t m) + => Dynamic t (m a) -> m (Event t a) + + networkHold :: (Reflex t, Adjustable t m, MonadHold t m) + => m a -> Event t (m a) -> m (Dynamic t a) + + +``EventWriter`` and ``DynamicWriter`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``EventWriter`` allows you to send events "upwards" in your widget hierarchy, much like Elm's update propagation.:: + + -- Main APIs + runEventWriterT :: (Reflex t, Monad m, Semigroup w) => EventWriterT t w m a -> m (a, Event t w) + tellEvent :: EventWriter t w m => Event t w -> m () + + -- Example usage + body :: MonadWidget t m => m () + body = do + rec + (_, ev) <- runEventWriterT ewbs + dy <- foldDyn (:) ["bar"] ev + simpleList dy dynText + return () + + ewbs :: MonadWidget t m => EventWriterT t Text m () + ewbs = do + evClick <- button "Click Me" + tellEvent ("foo" <$ evClick) + return () + +.. + A tutorial on this is in pipeline by dalaing + +.. _requester: + +``Requester`` +~~~~~~~~~~~~~ + +``Requester`` lets you make requests and receive responses anywhere within your widgets, and automatically collect/distribute them as necessary. + +The primary API which will be used to initiate a request and get a response is:: + + requesting :: Event t (Request m a) -> m (Event t (Response m a)) + +This requires defining two type constructors ``Request m`` and ``Response m``. + +The API to actually collect all the requests and provide response to each request is:: + + runRequesterT :: (Reflex t, Monad m) + => RequesterT t request response m a + -> Event t (RequesterData response) + -> m (a, Event t (RequesterData request)) + +As you can see all the requests are bundled up in the ``RequesterData request``, and the responses are also provided in a similar event of type ``RequesterData response``. + +The ``RequesterData`` is like a ``Map`` structure where the keys are some arbitrary values corresponding to the origin of request, and the values are the actual request data. + + +to provide a response one can use these APIs:: + + traverseRequesterData :: forall m request response. Applicative m + => (forall a. request a -> m (response a)) + -> RequesterData request + -> m (RequesterData response) + +can be used to provide response to all the request by specifying a `request handler`. + +But if you want access to each request separately and provide the responses in independent manner (in case you are doing XHR/ websocket requests for each request separately). + +Then you can convert this into a list of key value pairs (``DSum``), provide the response to each request by using the same key with ``singletonRequesterData`` to recreate the ``RequesterData``:: + + + requesterDataToList :: RequesterData f -> [DSum RequesterDataKey f] + + singletonRequesterData :: RequesterDataKey a -> f a -> RequesterData f + +``Workflow`` +~~~~~~~~~~~~ + + +``Reflex.Workflow`` provides a specialised API:: + + newtype Workflow t m a = Workflow { unWorkflow :: m (a, Event t (Workflow t m a))} + + workflow :: forall t m a. (Reflex t, Adjustable t m, MonadFix m, MonadHold t m) + => Workflow t m a -> m (Dynamic t a) + +The working of this API can be easily explained using a DOM based widget example:: + + -- A DOM based example of Workflow + page1, page2, page3 :: (MonadWidget t m) => Workflow t m Text + page1 = Workflow . el "div" $ do + el "div" $ text "This is page 1" + pg2 <- button "Switch to page 2" + return ("Page 1", page2 <$ pg2) + + page2 = Workflow . el "div" $ do + el "div" $ text "This is page 2" + pg3 <- button "Switch to page 3" + pg1 <- button "No wait, I want to go back to page 1" + return ("Page 2", leftmost [page3 <$ pg3, page1 <$ pg1]) + + page3 = Workflow . el "div" $ do + el "div" $ text "You have arrived on page 3" + pg1 <- button "Start over" + return ("Page 3", page1 <$ pg1) + + main = mainWidget $ do + r <- workflow page1 + el "div" $ do + text "Current page is: " + dynText r + +Performance +----------- + +``UniqDynamic`` +~~~~~~~~~~~~~~~ + +``UniqDynamic`` is useful to eliminate redundant update events from a Dynamic.:: + + uniqDynamic :: Reflex t => Dynamic t a -> UniqDynamic t a + + fromUniqDynamic :: (Reflex t, Eq a) => UniqDynamic t a -> Dynamic t a + +Internally, ``UniqDynamic`` uses pointer equality as a heuristic to avoid unnecessary update propagation; this is much more efficient than performing full comparisons. +However, when the UniqDynamic is converted back into a regular Dynamic, a full comparison is performed. + +In order to maintain this constraint, the value inside a UniqDynamic is always evaluated to weak head normal form. + +Also see the documentation of ``Reflex.Dynamic.Uniq`` + +Patch and Incremental +~~~~~~~~~~~~~~~~~~~~~ + +An ``Incremental`` is a more general form of a ``Dynamic``. +Instead of always fully replacing the value, only parts of it can be patched. +This is only needed for performance critical code via ``mergeIncremental`` to make small changes to large values. + +``Reflex.Patch.*`` provides a number of data structures which have the ability to do incremental updates. + +Cheap / Fast variants of APIs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +Internals +--------- + + +Frames +~~~~~~ + +A frame is the atomic time unit + +* Frame begins with, say, a mouse click +* Mouse click event fires +* Events fmapped from that event fire +* All other events depending on those events fire +* Repeat until there are no more event firings +* Frame ends + +Spider Timeline +~~~~~~~~~~~~~~~ + + diff --git a/doc/reflex_dom_docs.rst b/doc/reflex_dom_docs.rst new file mode 100644 index 0000000..349b203 --- /dev/null +++ b/doc/reflex_dom_docs.rst @@ -0,0 +1,324 @@ +Reflex Dom +========== + +See `Quick Ref `_ + +.. Type Classes +.. ------------ + +.. A reference for what the type class is for + +.. DomBuilder +.. ~~~~~~~~~~ + +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 ``
      `` with one ``
    • `` per child; clicking + a ``
    • `` displays the corresponding child and hides all others. + +.. _dom_input_elements: + +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`` + +.. Add an example in reflex-examples? + +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 :ref:`debounce` + +.. _dom_events: + +DOM Events +~~~~~~~~~~ + + ``domEvent`` API can be used to create ``Event`` on DOM elements:: + + (e,_) <- el' "span" $ text "Click Here" + + clickEv :: Event t () + clickEv <- domEvent Click e + + For a complete list of events accepted by ``domEvent`` see ``EventName`` 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. + +.. Does this respond to viewport size changes? + +Host / URL / Location +~~~~~~~~~~~~~~~~~~~~~ + +``Reflex.Dom.Location`` contains utility functions for obtaining the host, URL, protocol, etc. + +Client side routes +~~~~~~~~~~~~~~~~~~ + +.. _obelisk_route: + +obelisk-route +^^^^^^^^^^^^^ + + `Obelisk `_ is packaged with a set of routing libraries ``obelisk-route``, ``obelisk-route-frontend`` and ``obelisk-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: + +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: + +`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 :ref:`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. diff --git a/doc/resources.rst b/doc/resources.rst new file mode 100644 index 0000000..eda1f24 --- /dev/null +++ b/doc/resources.rst @@ -0,0 +1,199 @@ +Resources +========= + +.. _tutorials: + +Tutorials +--------- + +* Queensland FP Lab: Functional Reactive Programming with `reflex` + + https://blog.qfpl.io/projects/reflex/ + + These are very well written tutorials for beginners. It also has a number of exercises. + + +* https://github.com/hansroland/reflex-dom-inbits/blob/master/tutorial.md + + This is a single page `long-form` introduction, which covers a lot of material for ``reflex-dom`` applications. + +Examples +-------- + +* https://github.com/gspia/reflex-examples + + A fork of the https://github.com/reflex-frp/reflex-examples, updated to use a recent reflex-platform together with an example on the new project setup (as of early 2018). + + Examples include Basic ToDo, Drag-and-Drop, file input and many more. + +* https://github.com/reflex-frp/reflex-dom-contrib + + A collection is useful APIs and DOM widgets. + +* https://github.com/gspia/7guis-reflex-nix + + Example of 7 types of GUI tasks from basic counter to a spreadsheet. + +.. + This contains some code for poissonLossy + https://github.com/imalsogreg/my-reflex-recipes - take snippets from this + +Applications +------------ + +Full-Stack Haskell Apps +~~~~~~~~~~~~~~~~~~~~~~~ + +* http://hsnippet.com/ + + A web application to try out reflex in browser. + + The code is somewhat out of date, so latest features in reflex may not be available. + + Code: https://github.com/mightybyte/hsnippet + +* http://hexplore.mightybyte.net/ + + An experimental interface to browse haskell packages (registered on hackage) + + Code: https://gitlab.com/mightybyte/hexplore/ + +* https://tenjinreader.com + + An application to read Japanese books. + Uses :ref:`reflex_project_skeleton`. + + It has a web + android version of the reflex app + + Code: https://github.com/blueimpact/tenjinreader + +* https://app.gonimo.com/ + + The free baby monitor for smartphone, tablet or PC. + + It has a web + android version of the reflex app + + Code: https://github.com/gonimo/gonimo + +Games +~~~~~ + +* https://mightybyte.github.io/reflex-2048/ + + Code: https://github.com/mightybyte/reflex-2048 + +* https://rvl.github.io/flatris/ + + Code: https://github.com/rvl/flatris + + A simple FE only game. + This also contains an example of auto-reloading development environment + +Other +~~~~~ + +* https://github.com/CBMM/cochleagram + + Tools for psychoacoustics. + + This captures WebAudio, and does the processing to create an audio spectogram. + +Reflex Libraries +---------------- + +.. _dom_ui_libs: + +DOM-UI Libraries +~~~~~~~~~~~~~~~~ + +* Semantic UI components + + https://github.com/reflex-frp/reflex-dom-semui + +* Bootstrap Material Design + + https://github.com/hexresearch/reflex-material-bootstrap + + See README for instructions on integrating external js and also for using closure-compiler. + +* Material Components + + https://github.com/alasconnect/reflex-material + +* https://github.com/TaktInc/reflex-dhtmlx + + A wrapper around `date-picker` widget from DHTMLX + +* https://github.com/gspia/reflex-dom-htmlea + + This library provides short-hand names for the most common HTML elements and attributes. + + A longer term aim is to provide self contained customisable components providing reasonable default settings with examples, allowing to build demos quickly. + For example, a table component gives a functionality in which it is possible to select columns, cells, rows and have other ready made functionality. + + Also see https://github.com/gspia/reflex-dom-themes and https://github.com/gspia/reflex-dom-htmlea-vs + +Other Libraries +~~~~~~~~~~~~~~~ + +* https://github.com/diagrams/diagrams-reflex + + Port of the ``diagrams`` library with svg output. + See the README for supported constructs. + + Examples + http://bergey.github.io/gooey/ + + https://github.com/bergey/gooey + +* https://github.com/qfpl/reflex-dom-svg + + This is a work-in-progress helper library for creating svg + +* https://github.com/qfpl/reflex-dom-canvas + + An experimental support for canvas element + +* https://github.com/reflex-frp/reflex-dom-ace + + This package provides a Reflex wrapper around the ACE editor. + + This is also intended to serve as an example of how to structure FFI packages that rely on external JS packages. + +* https://github.com/dfordivam/audiocapture + + Demo for capturing audio via WebAudio APIs + +Posts / Blogs +------------- + + +* https://github.com/mightybyte/real-world-reflex/blob/master/index.md + +* https://emmanueltouzery.github.io/reflex-presentation + + +.. _monad_fix: + +MonadFix / RecursiveDo +~~~~~~~~~~~~~~~~~~~~~~ + +* 24 Days of GHC Extensions: Recursive Do + + https://ocharles.org.uk/blog/posts/2014-12-09-recursive-do.html + +* Grokking Fix + + http://www.parsonsmatt.org/2016/10/26/grokking_fix.html + +* MonadFix is Time Travel + + https://elvishjerricco.github.io/2017/08/22/monadfix-is-time-travel.html + +* Haskell Wiki + + https://wiki.haskell.org/MonadFix + +* Typeclassopedia on MonadFix + + https://wiki.haskell.org/Typeclassopedia#MonadFix