From 8635f0e8c2ff24c8659806c0a70af164081da630 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 7 Apr 2017 15:41:43 +0900 Subject: [PATCH 01/15] Put description on top --- code-snippets/nested_dynamics.hs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/code-snippets/nested_dynamics.hs b/code-snippets/nested_dynamics.hs index 01bbbca..217332d 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) @@ -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) From 23a24c95fbeb1e0806932349c38429a90dff39d3 Mon Sep 17 00:00:00 2001 From: Divam Date: Thu, 15 Jun 2017 15:25:53 +0900 Subject: [PATCH 02/15] wip: add installation / setup stuff --- doc/installation.rst | 154 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 3 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index c141f14..831fb2c 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -1,10 +1,158 @@ - Installation ------------- +============ -.. todo:: copy from reflex-platform, it has to provide all the possible ways +.. todo:: copy from reflex-platform, it has to provide all the possible ways user might need to install including stack, nix, nixos, ... +The essential components required for developing reflex based application are + +1. GHC or GHCJS + +2. Reflex library + +OS and System requirements +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +Using nix +--------- + +Nix is the preferred method for setting up the development environment for reflex. + + +Using Reflex-platform +~~~~~~~~~~~~~~~ + Please refer to `reflex-platform `_ +The `try-reflex` script can create a development environment with ghcjs. You can use this to have a quick starting setup to compile code-snippets and smaller projects. (Quick means requiring less user input, the first time can take considerable time to complete) + +For a non-trivial project it is recommended to use cabal. + +Using cabal with nix and reflex-platform +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +0. If you dont have a project with cabal file then use `cabal init` to create one. + +1. Use the `workon` script from reflex-platform to create a development environment (nix shell) according to the dependencies specified in cabal file + +``` +$ ~/reflex-platform/work-on ghcjs ./your-project + +# or just "cabal configure" if working on ghc + $ cabal configure --ghcjs + $ cabal build +``` + +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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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 + +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/workon ghcjs ./. +``` + +Nix can also be used directly (instead of the `workon` script) as shown below + + +Creating multiple sub-projects (Server + Client) +------------------------------------------------ + +If you writing both the server and client in haskell, it is a good practice to put the code used for communication (like message types) and common data types in a shared package. Then use the shared package in both client and server by adding it to their dependent packages. + +The structure of a typical project looks like this + +``` +. +├── reflex-platform +├── client +│ ├── client.cabal +│ ├── Setup.hs +│ └── src +│ └── Main.hs +├── common +│ ├── common.cabal +│ ├── Setup.hs +│ └── src +│ └── DataTypes.hs +└── server + ├── server.cabal + ├── Setup.hs + └── src + └── Main.hs + + +# client.cabal +name: client +version: 0.1.0.0 +build-type: Simple +cabal-version: >=1.10 + +executable client + main-is: Main.hs + build-depends: base >=4.9 && <4.10 + , common + , reflex-dom + , reflex + hs-source-dirs: src + default-language: Haskell2010 + +# common.cabal +name: common +version: 0.1.0.0 +build-type: Simple +cabal-version: >=1.10 + +library + exposed-modules: DataTypes + build-depends: base >=4.9 && <4.10 + , aeson + hs-source-dirs: src + default-language: Haskell2010 + +# server.cabal +name: server +version: 0.1.0.0 +build-type: Simple +cabal-version: >=1.10 + +executable server + main-is: Main.hs + build-depends: base >=4.9 && <4.10 + , common + , wai + , warp + hs-source-dirs: src + default-language: Haskell2010 +``` + +To specify the local dependency of `common` we can use the nix + +There are various ways of doing this, depending upon the level of your understading of nix. + +For a simple setup see +https://github.com/srhb/reflex-servant-scaffold + + +Local Haddock documentation +------------------------------------ + +In a nix shell created using `try-reflex` or `workon` you can use this command to get the path to haddock documentation. +``` +# Or use ghcjs-pkg +ghc-pkg field haddock-html +``` From 78289e78a91175dcec51eda59c4d041e65390b88 Mon Sep 17 00:00:00 2001 From: Divam Date: Thu, 15 Jun 2017 15:34:52 +0900 Subject: [PATCH 03/15] Fix code formatting --- doc/installation.rst | 162 +++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 84 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index 831fb2c..223b0a8 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -32,17 +32,16 @@ For a non-trivial project it is recommended to use cabal. Using cabal with nix and reflex-platform ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -0. If you dont have a project with cabal file then use `cabal init` to create one. +If you dont have a project with cabal file then use `cabal init` to create one. -1. Use the `workon` script from reflex-platform to create a development environment (nix shell) according to the dependencies specified in cabal file +1. Use the `workon` script from reflex-platform to create a development environment (nix shell) according to the dependencies specified in cabal file. +:: -``` -$ ~/reflex-platform/work-on ghcjs ./your-project + $ ~/reflex-platform/work-on ghcjs ./your-project -# or just "cabal configure" if working on ghc - $ cabal configure --ghcjs - $ cabal build -``` + # or just "cabal configure" if working on ghc + $ cabal configure --ghcjs + $ cabal build 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. @@ -57,13 +56,12 @@ Since the build environment is dependent on the reflex-platform, it is important The simplest way to do this is to create a submodule in your project -Assuming you are using git for versioning -``` -git submodule add https://github.com/reflex-frp/reflex-platform +Assuming you are using git for versioning:: -# Then use the workon script to get the nix-shell -./reflex-platform/workon ghcjs ./. -``` + git submodule add https://github.com/reflex-frp/reflex-platform + + # Then use the workon script to get the nix-shell + ./reflex-platform/workon ghcjs ./. Nix can also be used directly (instead of the `workon` script) as shown below @@ -73,71 +71,69 @@ Creating multiple sub-projects (Server + Client) If you writing both the server and client in haskell, it is a good practice to put the code used for communication (like message types) and common data types in a shared package. Then use the shared package in both client and server by adding it to their dependent packages. -The structure of a typical project looks like this - -``` -. -├── reflex-platform -├── client -│ ├── client.cabal -│ ├── Setup.hs -│ └── src -│ └── Main.hs -├── common -│ ├── common.cabal -│ ├── Setup.hs -│ └── src -│ └── DataTypes.hs -└── server - ├── server.cabal - ├── Setup.hs - └── src - └── Main.hs - - -# client.cabal -name: client -version: 0.1.0.0 -build-type: Simple -cabal-version: >=1.10 - -executable client - main-is: Main.hs - build-depends: base >=4.9 && <4.10 - , common - , reflex-dom - , reflex - hs-source-dirs: src - default-language: Haskell2010 - -# common.cabal -name: common -version: 0.1.0.0 -build-type: Simple -cabal-version: >=1.10 - -library - exposed-modules: DataTypes - build-depends: base >=4.9 && <4.10 - , aeson - hs-source-dirs: src - default-language: Haskell2010 - -# server.cabal -name: server -version: 0.1.0.0 -build-type: Simple -cabal-version: >=1.10 - -executable server - main-is: Main.hs - build-depends: base >=4.9 && <4.10 - , common - , wai - , warp - hs-source-dirs: src - default-language: Haskell2010 -``` +The structure of a typical project looks like this:: + + . + ├── reflex-platform + ├── client + │ ├── client.cabal + │ ├── Setup.hs + │ └── src + │ └── Main.hs + ├── common + │ ├── common.cabal + │ ├── Setup.hs + │ └── src + │ └── DataTypes.hs + └── server + ├── server.cabal + ├── Setup.hs + └── src + └── Main.hs + + + # client.cabal + name: client + version: 0.1.0.0 + build-type: Simple + cabal-version: >=1.10 + + executable client + main-is: Main.hs + build-depends: base >=4.9 && <4.10 + , common + , reflex-dom + , reflex + hs-source-dirs: src + default-language: Haskell2010 + + # common.cabal + name: common + version: 0.1.0.0 + build-type: Simple + cabal-version: >=1.10 + + library + exposed-modules: DataTypes + build-depends: base >=4.9 && <4.10 + , aeson + hs-source-dirs: src + default-language: Haskell2010 + + # server.cabal + name: server + version: 0.1.0.0 + build-type: Simple + cabal-version: >=1.10 + + executable server + main-is: Main.hs + build-depends: base >=4.9 && <4.10 + , common + , wai + , warp + hs-source-dirs: src + default-language: Haskell2010 To specify the local dependency of `common` we can use the nix @@ -150,9 +146,7 @@ https://github.com/srhb/reflex-servant-scaffold Local Haddock documentation ------------------------------------ -In a nix shell created using `try-reflex` or `workon` you can use this command to get the path to haddock documentation. +In a nix shell created using `try-reflex` or `workon` you can use this command to get the path to haddock documentation.:: -``` -# Or use ghcjs-pkg -ghc-pkg field haddock-html -``` + # Or use ghcjs-pkg + ghc-pkg field haddock-html From ea2c1c1d95f1690ccded1b65acbf16b794271d35 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 4 Apr 2018 16:17:43 +0900 Subject: [PATCH 04/15] Major re-write to the documentation. --- code-snippets/StaticBuilder.hs | 63 +++ code-snippets/nested_dynamics.hs | 10 +- doc/advanced_topics.rst | 5 +- doc/app_devel_docs.rst | 337 ++++++++++++++ doc/conf.py | 2 +- doc/guide_to_ajax.rst | 55 --- doc/guide_to_dom_creation.rst | 233 ---------- doc/guide_to_event_management.rst | 337 -------------- doc/index.rst | 35 +- doc/installation.rst | 239 +++++----- doc/non_dom_reflex.rst | 44 ++ doc/{architecture.rst => overview.rst} | 184 ++++++-- doc/reflex-dom.rst | 13 - doc/reflex_docs.rst | 611 +++++++++++++++++++++++++ doc/reflex_dom_docs.rst | 309 +++++++++++++ doc/resources.rst | 191 ++++++++ 16 files changed, 1860 insertions(+), 808 deletions(-) create mode 100644 code-snippets/StaticBuilder.hs create mode 100644 doc/app_devel_docs.rst delete mode 100644 doc/guide_to_ajax.rst delete mode 100644 doc/guide_to_dom_creation.rst delete mode 100644 doc/guide_to_event_management.rst create mode 100644 doc/non_dom_reflex.rst rename doc/{architecture.rst => overview.rst} (55%) delete mode 100644 doc/reflex-dom.rst create mode 100644 doc/reflex_docs.rst create mode 100644 doc/reflex_dom_docs.rst create mode 100644 doc/resources.rst 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 217332d..86695a5 100644 --- a/code-snippets/nested_dynamics.hs +++ b/code-snippets/nested_dynamics.hs @@ -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 diff --git a/doc/advanced_topics.rst b/doc/advanced_topics.rst index 4df1d42..1a492c0 100644 --- a/doc/advanced_topics.rst +++ b/doc/advanced_topics.rst @@ -43,6 +43,10 @@ You need to serve ``index.html``, ``rts.js``, ``lib.js``, ``out.js`` and Simplest way is to copy these files to the *static* directory of your backend project. This can be automated using simple shell script. +https://github.com/ghcjs/ghcjs/wiki/Deployment + +-dedupe flag https://www.reddit.com/r/haskell/comments/54knub/ghcjs_dedupe/ + Client Side Routing ~~~~~~~~~~~~~~~~~~~ @@ -79,4 +83,3 @@ 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..595459e --- /dev/null +++ b/doc/app_devel_docs.rst @@ -0,0 +1,337 @@ +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 cycle 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. + +* Introduce ``delay``. + + For example in this kind of loop in event propagation there is a need of ``delay``:: + + rec + let someEv = updated d + ev = someFun <$> someEv + + d <- holdDyn 0 ev + +* 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 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. + + +* 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 (_raw_element 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 + +If you access a DOM element via FFI which has just been created, then it might result in an error. This is because the ``getPostBuild`` is fired before the DOM has been put in the Window. To solve this add ``delay``:: + + evPB <- delay 0.2 =<< getPostBuild + elAttr ("id" =: "some-uniq-id") $ text "example" + performEvent_ (someFFI <$ evPB) + +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 required 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