packages feed

react-flux 1.1.1 → 1.2.0

raw patch · 12 files changed

+286/−230 lines, 12 files

Files

ChangeLog.md view
@@ -1,3 +1,14 @@+# 1.2.0++* (Breaking Change) Add timeout support to the AJAX functionality.  This has three changes.  First, the AJAX code moved+  into it's own module `React.Flux.Ajax` which is not re-exported by `React.Flux`.  Second, the+  `AjaxRequest` has a new entry `reqTimeout` to optionally specify a timeout value.  Finally, the+  `jsonAjax` function now takes a `RequestTimeout` as the first parameter.++* `reactRender` and `reactRenderToString` now error when executed if you call them when compiling with GHC+  instead of GHCJS.  These functions only work when using GHCJS to compile because for the actual rendering+  we rely on the React javascript code.  In addition, there are a few haddock updates about this change.+ # 1.1.1  * When compiling with GHC (not GHCJS), add fake instances for FromJSVal to allow more programs to compile@@ -15,7 +26,7 @@   (from the `Data.JSString` module in `ghcjs-base`).  If a value was just going to be passed straight into   react, I made it a JSString and if the value was intended to be manipulated in Haskell I used Text.   The main changes are:-      +       * `elemText` used to (confusingly) take a `String`.  Now there are three functions: `elemString`,         `elemText`, and `elemJSString` that take a `String`, `Text`, and `JSString` value respectively. 
example/README.md view
@@ -36,7 +36,7 @@  ~~~ cd example/todo-npm install react@0.14.6+npm install react@15.3.0 react-dom@15.3.0 node run-in-node.js ~~~ 
example/purecss-side-menu/index.html view
@@ -9,8 +9,8 @@     </head>     <body>         <div id="side-menu-app"/>-        <script src="https://fb.me/react-15.1.0.min.js"></script>-        <script src="https://fb.me/react-dom-15.1.0.min.js"></script>+        <script src="https://fb.me/react-15.3.0.min.js"></script>+        <script src="https://fb.me/react-dom-15.3.0.min.js"></script>         <script src="purecss-side-menu.js"></script>     </body> </html>
example/routing/route-example.html view
@@ -7,7 +7,8 @@     </head>     <body>       <section id="route-example"/>-      <script src="https://fb.me/react-0.13.3.js"></script>+      <script src="https://fb.me/react-15.3.0.js"></script>+      <script src="https://fb.me/react-dom-15.3.0.js"></script>       <script src="../../js-build/install-root/bin/route-example.jsexe/all.js"></script>     </body> </html>
example/todo/todo-dev.html view
@@ -7,8 +7,8 @@     </head>     <body>         <section id="todoapp"/>-        <script src="https://fb.me/react-0.15.1.js"></script>-        <script src=" https://fb.me/react-dom-0.15.1.js"></script>+        <script src="https://fb.me/react-15.3.0.js"></script>+        <script src=" https://fb.me/react-dom-15.3.0.js"></script>         <script src="../../js-build/install-root/bin/todo.jsexe/all.js"></script>     </body> </html>
example/todo/todo.html view
@@ -7,8 +7,8 @@     </head>     <body>         <section id="todoapp"/>-        <script src="https://fb.me/react-15.0.1.js"></script>-        <script src="https://fb.me/react-dom-15.0.1.js"></script>+        <script src="https://fb.me/react-15.3.0.js"></script>+        <script src="https://fb.me/react-dom-15.3.0.js"></script>         <script src="../../js-build/todo.min.js"></script>     </body> </html>
jsbits/ajax.js view
@@ -8,14 +8,25 @@  function hsreact$ajax(req, handler) {     var xhr = new XMLHttpRequest();+     xhr['open'](req['reqMethod'], req['reqURI']);+     var lst = req['reqHeaders'];     for (var i = 0; i < lst.length; i++) {         xhr['setRequestHeader'](lst[i][0], lst[i][1]);     }++    if (req['reqTimeout']) {+        xhr['timeout'] = req['reqTimeout'];+        xhr['ontimeout'] = function() {+            hsreact$ajaxCallback(xhr, {hs: handler, timedOut: true});+        }+    }+     xhr['onreadystatechange'] = function() {-        if (xhr['readyState'] === XMLHttpRequest['DONE']) {-            hsreact$ajaxCallback(xhr, {hs: handler});+        // a status of 0 with DONE occurs when the request is timed out+        if (xhr['readyState'] === XMLHttpRequest['DONE'] && xhr['status'] !== 0) {+            hsreact$ajaxCallback(xhr, {hs: handler, timedOut: false});         }     };     xhr['send'](req['reqBody']);
react-flux.cabal view
@@ -1,5 +1,5 @@ name:                react-flux-version:             1.1.1+version:             1.2.0 synopsis:            A binding to React based on the Flux application architecture for GHCJS category:            Web homepage:            https://bitbucket.org/wuzzeb/react-flux@@ -60,6 +60,7 @@                    React.Flux.PropertiesAndEvents                    React.Flux.Combinators                    React.Flux.Lifecycle+                   React.Flux.Ajax                    React.Flux.Internal                    React.Flux.Addons.React                    React.Flux.Addons.Bootstrap
src/React/Flux.hs view
@@ -131,6 +131,8 @@ ----------------------------------------------------------------------------------------------------  -- | Render your React application into the DOM.  Use this from your @main@ function, and only in the browser.+-- 'reactRender' only works when compiled with GHCJS (not GHC), because we rely on the React javascript code+-- to actually perform the rendering. reactRender :: Typeable props             => String -- ^ The ID of the HTML element to render the application into.                       -- (This string is passed to @document.getElementById@)@@ -150,13 +152,19 @@  #else -reactRender _ _ _ = return ()+reactRender _ _ _ = error "reactRender only works when compiled with GHCJS, because we rely on the javascript React code."  #endif  -- | Render your React application to a string using either @ReactDOMServer.renderToString@ if the first -- argument is false or @ReactDOMServer.renderToStaticMarkup@ if the first argument is true. -- Use this only on the server when running with node.+-- 'reactRenderToString' only works when compiled with GHCJS (not GHC), because we rely on the React javascript code+-- to actually perform the rendering.+--+-- If you are interested in isomorphic React, I suggest instead of using 'reactRenderToString' you use+-- 'exportViewToJavaScript' and then write a small top-level JavaScript view which can then integrate with+-- all the usual isomorphic React tools. reactRenderToString :: Typeable props                     => Bool -- ^ Render to static markup?  If true, this won't create extra DOM attributes                             -- that React uses internally.@@ -184,7 +192,7 @@  #else -reactRenderToString _ _ _ = return ""+reactRenderToString _ _ _ = error "reactRenderToString only works when compiled with GHCJS, because we rely on the javascript React code."  #endif 
+ src/React/Flux/Ajax.hs view
@@ -0,0 +1,238 @@+module React.Flux.Ajax (+    initAjax+  , RequestTimeout(..)+  , jsonAjax+  , AjaxRequest(..)+  , AjaxResponse(..)+  , ajax+  ) where++import Data.Aeson+import Data.Text (Text)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import React.Flux.Internal+import React.Flux.Store+import React.Flux.Views++#ifdef __GHCJS__+import Control.Arrow ((***))+import Control.DeepSeq (deepseq)+import GHCJS.Foreign.Callback+import GHCJS.Foreign (jsNull)+import GHCJS.Marshal (ToJSVal(..), toJSVal_aeson, FromJSVal(..))+import GHCJS.Types (JSVal)+import qualified Data.Text as T+import qualified Data.JSString.Text as JSS++import React.Flux.Export+#endif++-- | An optional timeout to use for @XMLHttpRequest.timeout@.+-- When a request times out, a status code of 504 is set in 'respStatus' and the+-- response handler executes.+data RequestTimeout = TimeoutMilliseconds Int | NoTimeout++instance ToJSVal RequestTimeout where+    toJSVal (TimeoutMilliseconds i) = toJSVal i+    toJSVal NoTimeout = pure jsNull++-- | The input to an AJAX request built using @XMLHttpRequest@.+data AjaxRequest = AjaxRequest+  { reqMethod :: JSString+  , reqURI :: JSString+  , reqTimeout :: RequestTimeout+  , reqHeaders :: [(JSString, JSString)]+  , reqBody :: JSVal+  } deriving (Typeable, Generic)++-- | The response after @XMLHttpRequest@ indicates that the @readyState@ is done.+data AjaxResponse = AjaxResponse+  { respStatus :: Int+  , respResponseText :: JSString+  , respResponseXHR :: JSVal -- ^ The raw @XMLHttpRequest@ object.  Use this if you want more status about the response,+                             --   such as response headers.+  } deriving (Typeable, Generic)++#ifdef __GHCJS__+-- | GHCJS panics if we export the function directly, so wrap it in a data struct+data HandlerWrapper = HandlerWrapper (AjaxResponse -> IO [SomeStoreAction])+    deriving Typeable++-- | This is broken out as a separate function because if the code is inlined (either manually or by+-- ghc), there is a runtime error.+useHandler :: AjaxResponse -> HandlerWrapper -> IO ()+useHandler r (HandlerWrapper h) = do+    actions <- h r+    actions `deepseq` mapM_ executeAction actions+{-# NOINLINE useHandler #-} -- if this is inlined, there is a bug in ghcjs+#endif++-- | If you are going to use 'ajax' or 'jsonAjax', you must call 'initAjax' once from your main+-- function.  The call should appear before the call to 'reactRender'.+initAjax :: IO ()+#ifdef __GHCJS__+initAjax = do+    return ()+    a <- asyncCallback2 $ \rawXhr h -> do+        resp <- if js_reqTimedOut h+                  then return $ AjaxResponse 504 "Timeout" rawXhr+                  else AjaxResponse <$> js_xhrStatus rawXhr <*> js_xhrResponseText rawXhr <*> pure rawXhr+        e <- js_getHandlerWrapper h+        js_release e+        parseExport e >>= useHandler resp++    js_setAjaxCallback a+#else+initAjax = return ()+#endif++-- | Use @XMLHttpRequest@ to send a request to the backend.  Once the response arrives+-- and the @readyState@ is done, the response will be passed to the given handler and the resulting+-- actions will be executed.  Note that 'ajax' returns immedietly and does not wait for the request+-- to finish.+ajax :: AjaxRequest -> (AjaxResponse -> IO [SomeStoreAction]) -> IO ()+#ifdef __GHCJS__+ajax req handler = do+    reqJson <- toJSVal req+    handlerE <- export $ HandlerWrapper handler+    js_ajax reqJson handlerE+#else+ajax _ _ = return ()+#endif++-- | Use @XMLHttpRequest@ to send a request with a JSON body, parse the response body as JSON, and+-- then dispatch some actions with the response.  This should be used from within the transform+-- function of your store.  For example,+--+-- >data Target = AlienShip | AlienPlanet+-- >  deriving (Show, Typeable, Generic, ToJSON, FromJSON)+-- >+-- >data Trajectory = Trajectory+-- >    { x :: Double, y :: Double, z :: Double, vx :: Double, vy :: Double, vz :: Double }+-- >  deriving (Show, Typeable, Generic, ToJSON, FromJSON)+-- >+-- >data UpdatePending = NoUpdatePending | UpdatePending Text | PreviousUpdateHadError Text+-- >+-- >data MyStore = MyStore { currentTrajectory :: Maybe Trajectory, launchUpdate :: UpdatePending }+-- >+-- >data MyStoreAction = LaunchTheMissiles Target+-- >                   | MissilesLaunched Trajectory+-- >                   | UnableToLaunchMissiles Text+-- >  deriving (Typeable, Generic, NFData)+-- >+-- >instance StoreData MyStore where+-- >    type StoreAction MyStore = MyStoreAction+-- >+-- >    transform (LaunchTheMissiles t) s = do+-- >        jsonAjax NoTimeout "PUT" "/launch-the-missiles" [] t $ \case+-- >            Left (_, msg) -> return [SomeStoreAction myStore $ UnableToLaunchMissiles msg]+-- >            Right traj -> return [SomeStoreAction myStore $ MissilesLaunched traj]+-- >        return s { launchUpdate = UpdatePending ("Requesting missle launch against " ++ T.pack (show t)) }+-- >+-- >    transform (MissilesLaunched traj) s =+-- >        return s { currentTrajectory = Just traj, launchUpdate = NoUpdatePending }+-- >+-- >    transform (UnableToLaunchMissiles err) s =+-- >        return s { launchUpdate = PreviousUpdateHadError err }+-- >+-- >myStore :: ReactStore MyStore+-- >myStore = mkStore $ MyStore Nothing NoUpdatePending+--+-- And then in your view, you can render this using something like:+--+-- >myView :: ReactView ()+-- >myView = defineControllerView "launch the missles" myStore $ \s () -> do+-- >    case launchUpdate s of+-- >        NoUpdatePending -> return ()+-- >        UpdatePending msg -> span_ $ faIcon_ "rocket" <> elemText msg+-- >        PreviousUpdateHadErroer err -> span_ $ faIcon_ "exclamation" <> elemText err+-- >    clbutton_ ["pure-button button-success"] ([SomeStoreAction myStore $ LaunchTheMissiles AlienShip]) $ do+-- >        faIcon_ "rocket"+-- >        "Launch the missles against the alien ship!"+-- >    p_ $ elemString $ "Current trajectory " ++ show (currentTrajectory s)+--+jsonAjax :: (ToJSON body, FromJSON response)+         => RequestTimeout+         -> Text -- ^ the method+         -> Text -- ^ the URI+         -> [(Text, Text)] -- ^ the headers.  In addition to these headers, 'jsonAjax' adds two headers:+                           -- @Content-Type: application/json@ and @Accept: application/json@.+         -> body -- ^ the body+         -> (Either (Int, Text) response -> IO [SomeStoreAction])+            -- ^ Once @XMLHttpRequest@ changes the @readyState@ to done this handler will be+            -- executed and the resulting actions dispatched to the stores.+            --+            --   * If the response status is @200@, the body will be parsed as JSON and a+            --     'Right' value will be passed to this handler.   If there is an+            --     error parsing the JSON response, a 'Left' value with @500@ and the error message+            --     from aeson is given to the handler. +            --+            --   * If the response status is anything besides @200@, a 'Left' value with a pair+            --     of the response status and response text is passed to the handler.+         -> IO ()+#ifdef __GHCJS__+jsonAjax timeout method uri headers body handler = do+    bodyRef <- toJSVal_aeson body >>= js_JSONstringify+    let extraHeaders = [("Content-Type", "application/json"), ("Accept", "application/json")]+    let req = AjaxRequest+              { reqMethod = JSS.textToJSString method+              , reqURI = JSS.textToJSString uri+              , reqHeaders = extraHeaders ++ map (JSS.textToJSString *** JSS.textToJSString) headers+              , reqTimeout = timeout+              , reqBody = bodyRef+              }+    ajax req $ \resp ->+        if respStatus resp == 200+            then do+                j <- js_JSONParse $ respResponseText resp+                mv <- fromJSVal j+                case mv of+                    Nothing -> handler $ Left (500, "Unable to convert response body")+                    Just v -> case fromJSON v of+                        Success v' -> handler $ Right v'+                        Error e -> handler $ Left (500, T.pack e)+            else handler $ Left (respStatus resp, JSS.textFromJSString $ respResponseText resp)+#else+jsonAjax _ _ _ _ _ = return ()+#endif++#ifdef __GHCJS__+foreign import javascript unsafe+    "hsreact$ajax($1, $2)"+    js_ajax :: JSVal -> Export HandlerWrapper -> IO ()++foreign import javascript unsafe+    "hsreact$setAjaxCallback($1)"+    js_setAjaxCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO ()++foreign import javascript unsafe+    "JSON['parse']($1)"+    js_JSONParse :: JSString -> IO JSVal++foreign import javascript unsafe+    "JSON['stringify']($1)"+    js_JSONstringify :: JSVal -> IO JSVal++foreign import javascript unsafe+    "$1.hs"+    js_getHandlerWrapper :: JSVal -> IO (Export HandlerWrapper)++foreign import javascript unsafe+    "$r = $1.timedOut"+    js_reqTimedOut :: JSVal -> Bool++foreign import javascript unsafe+    "$1['status']"+    js_xhrStatus :: JSVal -> IO Int++foreign import javascript unsafe+    "$1['responseText']"+    js_xhrResponseText :: JSVal -> IO JSString++foreign import javascript unsafe+    "h$release($1)"+    js_release :: Export HandlerWrapper -> IO ()++instance ToJSVal AjaxRequest+#endif
src/React/Flux/Combinators.hs view
@@ -9,36 +9,17 @@   , foreign_   , labeledInput_   , style-  -- * Ajax-  , initAjax-  , jsonAjax-  , AjaxRequest(..)-  , AjaxResponse(..)-  , ajax ) where -import Data.Aeson import Data.Monoid ((<>))-import Data.Text (Text)-import Data.Typeable (Typeable)-import GHC.Generics (Generic) import React.Flux.DOM import React.Flux.Internal import React.Flux.PropertiesAndEvents-import React.Flux.Store import React.Flux.Views  #ifdef __GHCJS__-import Control.Arrow ((***))-import Control.DeepSeq (deepseq)-import GHCJS.Foreign.Callback-import GHCJS.Marshal (ToJSVal(..), toJSVal_aeson, FromJSVal(..)) import GHCJS.Types (JSVal)-import qualified Data.Text as T-import qualified Data.JSString.Text as JSS -import React.Flux.Export- foreign import javascript unsafe     "$r = window[$1]"     js_lookupWindow :: JSString -> JSVal@@ -132,198 +113,3 @@ -- on elements. style :: [(JSString,JSString)] -> PropertyOrHandler handler style = nestedProperty "style" . map (\(n,a) -> (n &= a))-------------------------------------------------------------------------------------- Ajax------------------------------------------------------------------------------------- | The input to an AJAX request built using @XMLHttpRequest@.-data AjaxRequest = AjaxRequest-  { reqMethod :: JSString-  , reqURI :: JSString-  , reqHeaders :: [(JSString, JSString)]-  , reqBody :: JSVal-  } deriving (Typeable, Generic)---- | The response after @XMLHttpRequest@ indicates that the @readyState@ is done.-data AjaxResponse = AjaxResponse-  { respStatus :: Int-  , respResponseText :: JSString-  , respResponseXHR :: JSVal -- ^ The raw @XMLHttpRequest@ object.  Use this if you want more status about the response,-                             --   such as response headers.-  } deriving (Typeable, Generic)--#ifdef __GHCJS__--- | GHCJS panics if we export the function directly, so wrap it in a data struct-data HandlerWrapper = HandlerWrapper (AjaxResponse -> IO [SomeStoreAction])-    deriving Typeable---- | This is broken out as a separate function because if the code is inlined (either manually or by--- ghc), there is a runtime error.-useHandler :: AjaxResponse -> HandlerWrapper -> IO ()-useHandler r (HandlerWrapper h) = do-    actions <- h r-    actions `deepseq` mapM_ executeAction actions-{-# NOINLINE useHandler #-} -- if this is inlined, there is a bug in ghcjs-#endif---- | If you are going to use 'ajax' or 'jsonAjax', you must call 'initAjax' once from your main--- function.  The call should appear before the call to 'reactRender'.-initAjax :: IO ()-#ifdef __GHCJS__-initAjax = do-    return ()-    a <- asyncCallback2 $ \rawXhr h -> do-        resp <- AjaxResponse <$> js_xhrStatus rawXhr <*> js_xhrResponseText rawXhr <*> pure rawXhr-        e <- js_getHandlerWrapper h-        js_release e-        parseExport e >>= useHandler resp--    js_setAjaxCallback a-#else-initAjax = return ()-#endif---- | Use @XMLHttpRequest@ to send a request to the backend.  Once the response arrives--- and the @readyState@ is done, the response will be passed to the given handler and the resulting--- actions will be executed.  Note that 'ajax' returns immedietly and does not wait for the request--- to finish.-ajax :: AjaxRequest -> (AjaxResponse -> IO [SomeStoreAction]) -> IO ()-#ifdef __GHCJS__-ajax req handler = do-    reqJson <- toJSVal req-    handlerE <- export $ HandlerWrapper handler-    js_ajax reqJson handlerE-#else-ajax _ _ = return ()-#endif---- | Use @XMLHttpRequest@ to send a request with a JSON body, parse the response body as JSON, and--- then dispatch some actions with the response.  This should be used from within the transform--- function of your store.  For example,------ >data Target = AlienShip | AlienPlanet--- >  deriving (Show, Typeable, Generic, ToJSON, FromJSON)--- >--- >data Trajectory = Trajectory--- >    { x :: Double, y :: Double, z :: Double, vx :: Double, vy :: Double, vz :: Double }--- >  deriving (Show, Typeable, Generic, ToJSON, FromJSON)--- >--- >data UpdatePending = NoUpdatePending | UpdatePending Text | PreviousUpdateHadError Text--- >--- >data MyStore = MyStore { currentTrajectory :: Maybe Trajectory, launchUpdate :: UpdatePending }--- >--- >data MyStoreAction = LaunchTheMissiles Target--- >                   | MissilesLaunched Trajectory--- >                   | UnableToLaunchMissiles Text--- >  deriving (Typeable, Generic, NFData)--- >--- >instance StoreData MyStore where--- >    type StoreAction MyStore = MyStoreAction--- >--- >    transform (LaunchTheMissiles t) s = do--- >        jsonAjax "PUT" "/launch-the-missiles" [] t $ \case--- >            Left (_, msg) -> return [SomeStoreAction myStore $ UnableToLaunchMissiles msg]--- >            Right traj -> return [SomeStoreAction myStore $ MissilesLaunched traj]--- >        return s { launchUpdate = UpdatePending ("Requesting missle launch against " ++ T.pack (show t)) }--- >--- >    transform (MissilesLaunched traj) s =--- >        return s { currentTrajectory = Just traj, launchUpdate = NoUpdatePending }--- >--- >    transform (UnableToLaunchMissiles err) s =--- >        return s { launchUpdate = PreviousUpdateHadError err }--- >--- >myStore :: ReactStore MyStore--- >myStore = mkStore $ MyStore Nothing NoUpdatePending------ And then in your view, you can render this using something like:--- --- >myView :: ReactView ()--- >myView = defineControllerView "launch the missles" myStore $ \s () -> do--- >    case launchUpdate s of--- >        NoUpdatePending -> return ()--- >        UpdatePending msg -> span_ $ faIcon_ "rocket" <> elemText (T.unpack msg)--- >        PreviousUpdateHadErroer err -> span_ $ faIcon_ "exclamation" <> elemText (T.unpack err)--- >    clbutton_ ["pure-button button-success"] ([SomeStoreAction myStore $ LaunchTheMissiles AlienShip]) $ do--- >        faIcon_ "rocket"--- >        "Launch the missles against the alien ship!"--- >    p_ $ elemText $ "Current trajectory " ++ show (currentTrajectory s)----jsonAjax :: (ToJSON body, FromJSON response)-         => Text -- ^ the method-         -> Text -- ^ the URI-         -> [(Text, Text)] -- ^ the headers.  In addition to these headers, 'jsonAjax' adds two headers:-                           -- @Content-Type: application/json@ and @Accept: application/json@.-         -> body -- ^ the body-         -> (Either (Int, Text) response -> IO [SomeStoreAction])-            -- ^ Once @XMLHttpRequest@ changes the @readyState@ to done this handler will be-            -- executed and the resulting actions dispatched to the stores.-            ---            --   * If the response status is @200@, the body will be parsed as JSON and a-            --     'Right' value will be passed to this handler.   If there is an-            --     error parsing the JSON response, a 'Left' value with @500@ and the error message-            --     from aeson is given to the handler. -            ---            --   * If the response status is anything besides @200@, a 'Left' value with a pair-            --     of the response status and response text is passed to the handler.-         -> IO ()-#ifdef __GHCJS__-jsonAjax method uri headers body handler = do-    bodyRef <- toJSVal_aeson body >>= js_JSONstringify-    let extraHeaders = [("Content-Type", "application/json"), ("Accept", "application/json")]-    let req = AjaxRequest-              { reqMethod = JSS.textToJSString method-              , reqURI = JSS.textToJSString uri-              , reqHeaders = extraHeaders ++ map (JSS.textToJSString *** JSS.textToJSString) headers-              , reqBody = bodyRef-              }-    ajax req $ \resp ->-        if respStatus resp == 200-            then do-                j <- js_JSONParse $ respResponseText resp-                mv <- fromJSVal j-                case mv of-                    Nothing -> handler $ Left (500, "Unable to convert response body")-                    Just v -> case fromJSON v of-                        Success v' -> handler $ Right v'-                        Error e -> handler $ Left (500, T.pack e)-            else handler $ Left (respStatus resp, JSS.textFromJSString $ respResponseText resp)-#else-jsonAjax _ _ _ _ _ = return ()-#endif--#ifdef __GHCJS__-foreign import javascript unsafe-    "hsreact$ajax($1, $2)"-    js_ajax :: JSVal -> Export HandlerWrapper -> IO ()--foreign import javascript unsafe-    "hsreact$setAjaxCallback($1)"-    js_setAjaxCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO ()--foreign import javascript unsafe-    "JSON['parse']($1)"-    js_JSONParse :: JSString -> IO JSVal--foreign import javascript unsafe-    "JSON['stringify']($1)"-    js_JSONstringify :: JSVal -> IO JSVal--foreign import javascript unsafe-    "$1.hs"-    js_getHandlerWrapper :: JSVal -> IO (Export HandlerWrapper)--foreign import javascript unsafe-    "$1['status']"-    js_xhrStatus :: JSVal -> IO Int--foreign import javascript unsafe-    "$1['responseText']"-    js_xhrResponseText :: JSVal -> IO JSString--foreign import javascript unsafe-    "h$release($1)"-    js_release :: Export HandlerWrapper -> IO ()--instance ToJSVal AjaxRequest-#endif
test/client/test-client.html view
@@ -4,8 +4,8 @@     </head>     <body>         <div id="app"></div>-        <script src="https://fb.me/react-with-addons-15.0.1.js"></script>-        <script src="https://fb.me/react-dom-15.0.1.js"></script>+        <script src="https://fb.me/react-with-addons-15.3.0.js"></script>+        <script src="https://fb.me/react-dom-15.3.0.js"></script>         <script src="node_modules/react-intl/dist/react-intl.js"></script>         <script src="node_modules/react-intl/locale-data/en.js"></script>         <script src="../../js-build/install-root/bin/test-client.jsexe/all.js"></script>