on master help sign in
Update README.md
commitc73a728c1ff72aa4bb3251656b78ad98f7c95add authorHamish Mackenzie <Hamish.K.Mackenzie@googlemail.com> dateSun 2 Oct 2016, 17:39 (10 years ago) parentbb2aeea
1 file changed+198−2
README.md+198−2
README.md+198−2
@@ -1,5 +1,201 @@
11JSaddle
22=======
33
4−A EDSL (embedded domain specific language) interface for JavaScript.
5−Works with GHCJS or webkitgtk3-javascriptcore.
4+JSaddle is an interface for JavaScript that works with GHCJS or GHC. It is used by [ghcjs-dom](https://github.com/ghcjs/ghcjs-dom)
5+when compiled with GHC and is compatible with [ghcjs-dom](https://github.com/ghcjs/ghcjs-dom) when compiled with GHCJS.
6+
7+You can use JSaddle directly as follows:
8+
9+``` Haskell
10+module Main ( main ) where
11+
12+import Control.Monad.IO.Class (MonadIO(..))
13+import Control.Concurrent.MVar (takeMVar, putMVar, newEmptyMVar)
14+import Control.Lens ((^.))
15+import Language.Javascript.JSaddle
16+ (run, jsg, js, js1, jss, fun, valToNumber, syncPoint)
17+
18+main = run 3709 $ do
19+ doc <- jsg "document"
20+ doc ^. js "body" ^. jss "innerHTML" "<h1>Kia ora (Hi)</h1>"
21+
22+ -- Create a haskell function call back for the onclick event
23+ doc ^. jss "onclick" (fun $ \ _ _ [e] -> do
24+ x <- e ^. js "clientX" >>= valToNumber
25+ y <- e ^. js "clientY" >>= valToNumber
26+ newParagraph <- doc ^. js1 "createElement" "p"
27+ newParagraph ^. js1 "appendChild" (
28+ doc ^. js1 "createTextNode" ("Click " ++ show (x, y)))
29+ doc ^. js "body" ^. js1 "appendChild" newParagraph
30+ return ())
31+
32+ -- Make an exit button
33+ exitMVar <- liftIO newEmptyMVar
34+ exit <- doc ^. js1 "createElement" "span"
35+ exit ^. js1 "appendChild" (
36+ doc ^. js1 "createTextNode" "Click here to exit")
37+ doc ^. js "body" ^. js1 "appendChild" exit
38+ exit ^. jss "onclick" (fun $ \ _ _ _ -> liftIO $ putMVar exitMVar ())
39+
40+ -- Force all all the lazy evaluation to be executed
41+ syncPoint
42+
43+ -- In GHC compiled version the WebSocket connection will end when this
44+ -- thread ends. So we will wait until the user clicks exit.
45+ liftIO $ takeMVar exitMVar
46+ doc ^. js "body" ^. jss "innerHTML" "<h1>Ka kite ano (See you later)</h1>"
47+ return ()
48+```
49+
50+When compiled with GHC this code will run a Warp web server you can connect to at `http:\\localhost:3709\`.
51+
52+Here is the same program using ghcjs-dom to call JSaddle. As well as better type safety this version uses
53+JS FFI directly (rather than via JSaddle) when compiled with GHCJS:
54+
55+``` Haskell
56+module Main (
57+ main
58+) where
59+
60+import Control.Monad.IO.Class (MonadIO(..))
61+import Control.Concurrent.MVar (takeMVar, putMVar, newEmptyMVar)
62+
63+import GHCJS.DOM (run, syncPoint, currentDocument)
64+import GHCJS.DOM.Document (getBody, createElement, createTextNode)
65+import GHCJS.DOM.Element (setInnerHTML)
66+import GHCJS.DOM.Node (appendChild)
67+import GHCJS.DOM.EventM (on, mouseClientXY)
68+import qualified GHCJS.DOM.Document as D (click)
69+import qualified GHCJS.DOM.Element as E (click)
70+
71+main = run 3708 $ do
72+ Just doc <- currentDocument
73+ Just body <- getBody doc
74+ setInnerHTML body (Just "<h1>Hello World</h1>")
75+ on doc D.click $ do
76+ (x, y) <- mouseClientXY
77+ Just newParagraph <- createElement doc (Just "p")
78+ text <- createTextNode doc $ "Click " ++ show (x, y)
79+ appendChild newParagraph text
80+ appendChild body (Just newParagraph)
81+ return ()
82+
83+ -- Make an exit button
84+ exitMVar <- liftIO newEmptyMVar
85+ Just exit <- createElement doc (Just "span")
86+ text <- createTextNode doc "Click here to exit"
87+ appendChild exit text
88+ appendChild body (Just exit)
89+ on exit E.click $ liftIO $ putMVar exitMVar ()
90+
91+ -- Force all all the lazy evaluation to be executed
92+ syncPoint
93+
94+ -- In GHC compiled version the WebSocket connection will end when this
95+ -- thread ends. So we will wait until the user clicks exit.
96+ liftIO $ takeMVar exitMVar
97+ setInnerHTML body (Just "<h1>Ka kite ano (See you later)</h1>")
98+ return ()
99+```
100+
101+When compiled with GHC this code will run a Warp web server you can connect to at `http:\\localhost:3708\`.
102+
103+## How does it work on GHC
104+
105+As of version 0.5 it runs a small warp server that provides [index.html](https://github.com/ghcjs/jsaddle/blob/master/data/index.html) and
106+[jsaddle.js](https://github.com/ghcjs/jsaddle/blob/master/data/jsaddle.js). The [jsaddle.js](https://github.com/ghcjs/jsaddle/blob/master/data/jsaddle.js) code connects to the server using WebSockets and processes JSON requests from the server and sends the result back over the same WebSocket.
107+
108+### Why use WebSockets?
109+Older versions relied on WebKitGTK and JavaScriptCore, but with WekKit2 the interface used was only available to WebKit Extensions
110+so most of the benefits of this interface were lost (it was no longer in process with the Gtk code and RPC would have been needed).
111+The general advice for people migrating from WebKit1 to WebKit2 seems to be to use JavaScript. There is an interface in WebKit2
112+to send JavaScript code from the host application, but it does not seem to provide any advantages over the WebSockets approach.
113+
114+### JSVal
115+
116+When compiling with GHC a `JSVal` is represented by a integer key for a JavaScript `Map` in `jsaddle.js` code.
117+The first five keys `[0..4]` are
118+`null`, `undefined`, `true`, `false` and `window`. This means the Haskell code can quickly create a JSVal with one of
119+these values. Both the Haskell code and `jsaddle.js` can allocate new `JSVal` keys. The Haskell code allocates negative
120+keys and `jsaddle.js` allocates positive keys to avoid clashing.
121+
122+The haskell code will not know the value of the to go with the key, but it will include it in commands to the server
123+that are sort of "hey call this function and and put the result in the map with this key"). This allows for the
124+lazy execution of the JSaddle code.
125+
126+A finalizer is added on the Haskell code and an asynchronous `FreeJSVal` command is automatically sent to `jsaddle.js`
127+when the JSVal is no longer reachable. The `jsaddle.js` code then deletes the key from the Map.
128+
129+### JSStrings
130+
131+These are haskell `Text` type and so reside on the native side of the WebSocket. If you want to make avoid transferring large string values over the WebSocket use `toJSVal` to conevt it to a `JSVal`.
132+
133+### Lazy Execution
134+
135+To improve performance commands are sent to `jsaddle.js` in batches that contain any number of asynchronous commands followed by one synchronous one. To get the best performance you should avoid synchronous commands, these are:
136+
137+| Synchronous Command | What will trigger it |
138+| ------------------- | ------------------------------------------------------------ |
139+| **ValueToString** | converting a `JSVal` to a `JSString` (`Text` when using GHC) |
140+| **ValueToBool** | converting a `JSVal` to a `Bool` |
141+| **ValueToNumber** | converting a `JSVal` to a `Double` |
142+| **ValueToJSON** | converting a `JSVal` to a `JSON` |
143+| **DeRefVal** | converting a `JSVal` to a `Value` |
144+| **IsNull** | testing to see if a `JSVal` is `null` |
145+| **IsUndefined** | testing to see if a `JSVal` is `undefined` |
146+| **InstanceOf** | testing to see if a `JSVal` is an `instanceOf` a given type |
147+| **StrictEqual** | testing too `JSVal` values for equality (`===`) |
148+| **PropertyNames** | getting the list of properties in an JS object |
149+
150+So basically if you don't look inside a `JSVal` to find out something the state of the JavaScript context you will not produce any
151+synchronous commands and your code will run fast. As soon as you look at what is in a `JSVal` we have to wait for `jsaddle.js` to
152+send the results back and your code will block.
153+
154+In some cases you may wish to make sure all the JSaddle commands are executed. You can use the `syncPoint` and `syncAfter` functions to
155+force all the pending asynchronous commands to be executed.
156+
157+
158+## How does it work on GHCJS
159+
160+It uses a handful of JS FFI calls to execute JavaScript functions indirectly. This indirection will be small compared to the overhead of the WebSockets approach (used when JSaddle is compiled with GHC), but it will be significant compared to hand crafted JS FFI calls.
161+
162+For the best performance you may want to write both JS FFI and JSaddle wrappers
163+For your JavaScript code. This is the approach taken by recent versions of `ghcjs-dom`.
164+
165+For instance here is `getElementById` from the `ghcjs-dom-jsffi` (used by `ghcjs-dom` when compiled with GHCJS)
166+
167+``` Haskell
168+foreign import javascript unsafe "$1[\"getElementById\"]($2)"
169+ js_getElementById :: Document -> JSString -> IO (Nullable Element)
170+
171+getElementById ::
172+ (MonadIO m, IsDocument self, ToJSString elementId) =>
173+ self -> elementId -> m (Maybe Element)
174+getElementById self elementId
175+ = liftIO
176+ (nullableToMaybe <$>
177+ (js_getElementById (toDocument self) (toJSString elementId)))
178+```
179+
180+Here is `getElementById` from `jsaddle-dom` (used by `ghcjs-dom` when compiled with GHC)
181+
182+``` Haskell
183+getElementById ::
184+ (MonadDOM m, IsDocument self, ToJSString elementId) =>
185+ self -> elementId -> m (Maybe Element)
186+getElementById self elementId
187+ = liftDOM
188+ (((toDocument self) ^. jsf "getElementById" [toJSVal elementId])
189+ >>= fromJSVal)
190+```
191+
192+## Exceptions
193+
194+JSaddle does not support exceptions well. When compiled with GHCJS an exception will result in termination of the thread
195+at the point the exception is thrown.
196+
197+When using GHC the Haskell executions will probably continue for a while before the exception is received at all by the Haskell code
198+(because the lazy execution of the JS code). The exception will be thrown when the the next synchronous command is executed.
199+When it reaches the synchronous command a haskell type JSException will be thrown (this may not be the thread that initiated the
200+command that caused the exception). You can use `syncPoint` and `syncAfter` to force the exception to be thrown.
201+There are `JSM` versions of `catch` and `bracket` that also include a `syncPoint` call.