on develop help sign in
Merge remote-tracking branch 'origin/develop' into HEAD

Conflicts: reflex-dom.cabal * Fix build in GHC. * Please let this be the right thing to do with respect to git. lol.

commitaf398f1017b04623fa677127be6c0d2042982dae authorCale Gibbard <cgibbard@gmail.com> dateThu 19 Nov 2015, 22:06 (11 years ago) parent853e2c5ddcc75b
16 files changed+425−27
README.md+1−1 reflex-dom.cabal+8−5 src-ghc/Reflex/Dom/Internal/Foreign.hs+32 src-ghc/Reflex/Dom/WebSocket/Foreign.hs+84 src-ghc/Reflex/Dom/Xhr/Foreign.hs+5−11 src-ghcjs/Reflex/Dom/WebSocket/Foreign.hs+49 src-ghcjs/Reflex/Dom/Xhr/Foreign.hs+3 src/Reflex/Dom.hs+2 src/Reflex/Dom/Time.hs+16−5 src/Reflex/Dom/WebSocket.hs+63 src/Reflex/Dom/Widget.hs+2 src/Reflex/Dom/Widget/Basic.hs+11−4 src/Reflex/Dom/Widget/Input.hs+1 src/Reflex/Dom/Widget/Lazy.hs+69 src/Reflex/Dom/Widget/Resize.hs+62 src/Reflex/Dom/Xhr.hs+17−1
README.md+1−1
@@ -3,7 +3,7 @@
33
44Reflex-DOM is a framework for building web applications in Haskell, based on the [Functional Reactive Programming](https://wiki.haskell.org/Functional_Reactive_Programming) library [Reflex](https://github.com/ryantrinkle/reflex).
55
6−Comprehensive documentation is still a work in progress, but a [demo](https://github.com/ryantrinkle/try-reflex) is available and an introductory talk is available at [Part 1](https://www.youtube.com/watch?v=mYvkcskJbc4) / [Part 2](https://www.youtube.com/watch?v=3qfc9XFVo2c).
6+Comprehensive documentation is still a work in progress, but a [demo](https://github.com/ryantrinkle/try-reflex) is available and an introductory talk is available at [Part 1](https://www.youtube.com/watch?v=mYvkcskJbc4) / [Part 2](https://www.youtube.com/watch?v=3qfc9XFVo2c) ([Slides](https://obsidian.systems/reflex-nyhug/)).
77
88### Additional resources
99[try reflex](https://github.com/ryantrinkle/try-reflex)
reflex-dom.cabal+8−5
@@ -27,11 +27,11 @@ library
2727 semigroups == 0.16.*,
2828 mtl >= 2.1 && < 2.3,
2929 containers == 0.5.*,
30− these >= 0.4,
30+ these >= 0.4 && < 0.7,
3131 ref-tf == 0.4.*,
3232 random == 1.1.*,
3333 transformers == 0.3.* || == 0.4.*,
34− lens >= 4.7 && < 4.10,
34+ lens >= 4.7 && < 4.14,
3535 ghcjs-dom >= 0.1.1.3 && < 0.2,
3636 safe == 0.3.*,
3737 text == 1.2.*,
@@ -42,8 +42,8 @@ library
4242 exception-transformers == 0.4.*,
4343 directory == 1.2.*,
4444 dependent-sum-template >= 0.0.0.4 && < 0.1,
45− bifunctors == 4.2.*,
46− zenc == 0.1.*
45+ zenc == 0.1.*,
46+ bifunctors >= 4.2 && < 5.1
4747
4848 if impl(ghcjs)
4949 hs-source-dirs: src-ghcjs
@@ -65,15 +65,18 @@ library
6565 Reflex.Dom
6666 Reflex.Dom.Class
6767 Reflex.Dom.Internal
68+ Reflex.Dom.Time
69+ Reflex.Dom.WebSocket
6870 Reflex.Dom.Widget
6971 Reflex.Dom.Widget.Basic
7072 Reflex.Dom.Widget.Input
7173 Reflex.Dom.Widget.Lazy
74+ Reflex.Dom.Widget.Resize
7275 Reflex.Dom.Xhr
73− Reflex.Dom.Time
7476 Foreign.JavaScript.TH
7577 other-modules:
7678 Reflex.Dom.Internal.Foreign
79+ Reflex.Dom.WebSocket.Foreign
7780 Reflex.Dom.Xhr.Foreign
7881
7982 other-extensions: TemplateHaskell
src-ghc/Reflex/Dom/Internal/Foreign.hs+32
@@ -1,12 +1,18 @@
11{-# LANGUAGE ForeignFunctionInterface #-}
22module Reflex.Dom.Internal.Foreign where
33
4+import Control.Lens hiding (set)
45import GHCJS.DOM hiding (runWebGUI)
56import Control.Concurrent
67import Control.Monad.State.Strict hiding (mapM, mapM_, forM, forM_, sequence, sequence_, get)
8+import Foreign.Ptr
79import GHCJS.DOM.Navigator
810import GHCJS.DOM.DOMWindow
911import Graphics.UI.Gtk hiding (Widget)
12+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
13+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSObjectRef
14+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
15+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
1016import Graphics.UI.Gtk.WebKit.WebView
1117import Graphics.UI.Gtk.WebKit.Types hiding (Event, Widget, unWidget)
1218import Graphics.UI.Gtk.WebKit.WebSettings
@@ -14,6 +20,7 @@ import Graphics.UI.Gtk.WebKit.WebFrame
1420import Graphics.UI.Gtk.WebKit.WebInspector
1521import Data.List
1622import System.Directory
23+import System.Glib.FFI hiding (void)
1724
1825makeDefaultWebView :: String -> (WebView -> IO ()) -> IO ()
1926makeDefaultWebView userAgentKey main = do
@@ -67,3 +74,28 @@ runWebGUI' userAgentKey main = do
6774 unless ((" " ++ userAgentKey) `isSuffixOf` agent) $ main (castToWebView window)
6875 Nothing -> do
6976 makeDefaultWebView userAgentKey main
77+
78+foreign import ccall "wrapper"
79+ wrapper :: JSObjectCallAsFunctionCallback' -> IO JSObjectCallAsFunctionCallback
80+
81+toJSObject :: JSContextRef -> [Ptr OpaqueJSValue] -> IO JSObjectRef
82+toJSObject ctx args = do
83+ o <- jsobjectmake ctx nullPtr nullPtr
84+ iforM_ args $ \n a -> do
85+ prop <- jsstringcreatewithutf8cstring $ show n
86+ jsobjectsetproperty ctx o prop a 1 nullPtr
87+ return o
88+
89+fromJSStringMaybe :: JSContextRef -> JSValueRef -> IO (Maybe String)
90+fromJSStringMaybe c t = do
91+ isNull <- jsvalueisnull c t
92+ case isNull of
93+ True -> return Nothing
94+ False -> do
95+ j <- jsvaluetostringcopy c t nullPtr
96+ l <- jsstringgetmaximumutf8cstringsize j
97+ s <- allocaBytes (fromIntegral l) $ \ps -> do
98+ _ <- jsstringgetutf8cstring'_ j ps (fromIntegral l)
99+ peekCString ps
100+ return $ Just s
101+
src-ghc/Reflex/Dom/WebSocket/Foreign.hsnew file+84
@@ -0,0 +1,84 @@
1+{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, CPP, TemplateHaskell, NoMonomorphismRestriction, EmptyDataDecls, RankNTypes, GADTs, RecursiveDo, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, TypeFamilies, FlexibleContexts, DeriveDataTypeable, GeneralizedNewtypeDeriving, StandaloneDeriving, ConstraintKinds, UndecidableInstances, PolyKinds, AllowAmbiguousTypes #-}
2+
3+module Reflex.Dom.WebSocket.Foreign where
4+
5+import Prelude hiding (div, span, mapM, mapM_, concat, concatMap, all, sequence)
6+
7+import Control.Exception
8+import Control.Monad.State
9+import Data.ByteString (ByteString)
10+import Data.Text.Encoding
11+import Foreign.Marshal hiding (void)
12+import Foreign.Ptr
13+import Foreign.Storable
14+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
15+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSObjectRef
16+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
17+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
18+import Graphics.UI.Gtk.WebKit.JavaScriptCore.WebFrame
19+import Graphics.UI.Gtk.WebKit.WebView
20+import qualified Data.ByteString as BS
21+import qualified Data.Text as T
22+
23+import Reflex.Dom.Internal.Foreign
24+
25+data JSWebSocket = JSWebSocket { wsValue :: JSValueRef
26+ , wsContext :: JSContextRef
27+ }
28+
29+getLocationHost :: WebView -> IO String
30+getLocationHost wv = do
31+ c <- webFrameGetGlobalContext =<< webViewGetMainFrame wv
32+ script <- jsstringcreatewithutf8cstring "location.host"
33+ lh <- jsevaluatescript c script nullPtr nullPtr 1 nullPtr
34+ lh' <- fromJSStringMaybe c lh
35+ return $ maybe "" id lh'
36+
37+getLocationProtocol :: WebView -> IO String
38+getLocationProtocol wv = do
39+ c <- webFrameGetGlobalContext =<< webViewGetMainFrame wv
40+ script <- jsstringcreatewithutf8cstring "location.protocol"
41+ lp <- jsevaluatescript c script nullPtr nullPtr 1 nullPtr
42+ lp' <- fromJSStringMaybe c lp
43+ return $ maybe "" id lp'
44+
45+newWebSocket :: WebView -> String -> (ByteString -> IO ()) -> IO () -> IO JSWebSocket
46+newWebSocket wv url onMessage onClose = do
47+ c <- webFrameGetGlobalContext =<< webViewGetMainFrame wv
48+ url' <- jsvaluemakestring c =<< jsstringcreatewithutf8cstring url
49+ newWSArgs <- toJSObject c [url']
50+ newWS <- jsstringcreatewithutf8cstring "(function(that) { var ws = new WebSocket(that[0]); ws['binaryType'] = 'arraybuffer'; return ws; })(this)"
51+ ws <- jsevaluatescript c newWS newWSArgs nullPtr 1 nullPtr
52+ onMessage' <- wrapper $ \_ _ _ _ args _ -> do
53+ e <- peekElemOff args 0
54+ dataProp <- jsstringcreatewithutf8cstring "data"
55+ msg <- jsobjectgetproperty c e dataProp nullPtr
56+ msg' <- fromJSStringMaybe c msg
57+ case msg' of
58+ Nothing -> return ()
59+ Just m -> onMessage $ encodeUtf8 $ T.pack m
60+ jsvaluemakeundefined c
61+ onMessageCb <- jsobjectmakefunctionwithcallback c nullPtr onMessage'
62+ onClose' <- wrapper $ \_ _ _ _ _ _ -> do
63+ onClose
64+ jsvaluemakeundefined c
65+ onCloseCb <- jsobjectmakefunctionwithcallback c nullPtr onClose'
66+ o <- toJSObject c [ws, onMessageCb, onCloseCb]
67+ addCbs <- jsstringcreatewithutf8cstring "this[0]['onmessage'] = this[1]; this[0]['onclose'] = this[2];"
68+ _ <- jsevaluatescript c addCbs o nullPtr 1 nullPtr
69+ return $ JSWebSocket ws c
70+
71+webSocketSend :: JSWebSocket -> ByteString -> IO ()
72+webSocketSend (JSWebSocket ws c) bs = do
73+ elems <- forM (BS.unpack bs) $ \x -> jsvaluemakenumber c $ fromIntegral x
74+ let numElems = length elems
75+ bs' <- bracket (mallocArray numElems) free $ \elemsArr -> do
76+ pokeArray elemsArr elems
77+ a <- jsobjectmakearray c (fromIntegral numElems) elemsArr nullPtr
78+ newUint8Array <- jsstringcreatewithutf8cstring "new Uint8Array(this)"
79+ jsevaluatescript c newUint8Array a nullPtr 1 nullPtr
80+ send <- jsstringcreatewithutf8cstring "this[0]['send'](String['fromCharCode']['apply'](null, this[1]))"
81+ sendArgs <- toJSObject c [ws, bs']
82+ _ <- jsevaluatescript c send sendArgs nullPtr 1 nullPtr
83+ return ()
84+
src-ghc/Reflex/Dom/Xhr/Foreign.hs+5−11
@@ -13,23 +13,20 @@ import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
1313import Graphics.UI.Gtk.WebKit.JavaScriptCore.WebFrame
1414import GHCJS.DOM.File
1515
16+import Reflex.Dom.Internal.Foreign
17+
1618data XMLHttpRequest
1719 = XMLHttpRequest { xhrValue :: JSValueRef
1820 , xhrContext :: JSContextRef --TODO: Get rid of this
1921 }
2022 deriving (Eq, Ord)
2123
22−toJSObject :: JSContextRef -> [Ptr OpaqueJSValue] -> IO JSObjectRef
23−toJSObject ctx args = do
24− o <- jsobjectmake ctx nullPtr nullPtr
25− iforM_ args $ \n a -> do
26− prop <- jsstringcreatewithutf8cstring $ show n
27− jsobjectsetproperty ctx o prop a 1 nullPtr
28− return o
29−
3024responseTextToText :: Maybe String -> Maybe Text
3125responseTextToText = fmap T.pack
3226
27+statusTextToText :: String -> Text
28+statusTextToText = T.pack
29+
3330stringToJSValue :: JSContextRef -> String -> IO JSValueRef
3431stringToJSValue ctx s = jsvaluemakestring ctx =<< jsstringcreatewithutf8cstring s
3532
@@ -58,9 +55,6 @@ xmlHttpRequestOpen xhr method url async user password = do
5855 _ <- jsevaluatescript c script o nullPtr 1 nullPtr
5956 return ()
6057
61−foreign import ccall "wrapper"
62− wrapper :: JSObjectCallAsFunctionCallback' -> IO JSObjectCallAsFunctionCallback
63−
6458xmlHttpRequestOnreadystatechange :: XMLHttpRequest -> IO () -> IO ()
6559xmlHttpRequestOnreadystatechange xhr userCallback = do
6660 let c = xhrContext xhr
src-ghcjs/Reflex/Dom/WebSocket/Foreign.hsnew file+49
@@ -0,0 +1,49 @@
1+{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, CPP, TemplateHaskell, NoMonomorphismRestriction, EmptyDataDecls, RankNTypes, GADTs, RecursiveDo, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, TypeFamilies, FlexibleContexts, DeriveDataTypeable, GeneralizedNewtypeDeriving, StandaloneDeriving, ConstraintKinds, UndecidableInstances, PolyKinds, AllowAmbiguousTypes #-}
2+
3+module Reflex.Dom.WebSocket.Foreign where
4+
5+import Prelude hiding (div, span, mapM, mapM_, concat, concatMap, all, sequence)
6+
7+import Control.Monad hiding (forM, forM_, mapM, mapM_, sequence)
8+import Data.ByteString (ByteString)
9+import Data.Text.Encoding
10+import qualified Data.ByteString as BS
11+import Foreign.Ptr
12+import GHCJS.Foreign
13+import GHCJS.Types
14+
15+#define JS(name, js, type) foreign import javascript unsafe js name :: type
16+
17+newtype JSWebSocket = JSWebSocket { unWebSocket :: JSRef JSWebSocket }
18+
19+data JSByteArray
20+JS(extractByteArray, "new Uint8Array($1_1.buf, $1_2, $2)", Ptr a -> Int -> IO (JSRef JSByteArray))
21+
22+JS(newWebSocket_, "(function() { var ws = new WebSocket($1); ws['binaryType'] = 'arraybuffer'; ws['onmessage'] = function(e){ $2(e['data']); }; ws['onclose'] = function(e){ $3(); }; return ws; })()", JSString -> JSFun (JSString -> IO ()) -> JSFun (IO ()) -> IO (JSRef JSWebSocket))
23+
24+JS(webSocketSend_, "$1['send'](String.fromCharCode.apply(null, $2))", JSRef JSWebSocket -> JSRef JSByteArray -> IO ())
25+
26+webSocketSend :: JSWebSocket -> ByteString -> IO ()
27+webSocketSend ws bs = BS.useAsCString bs $ \cStr -> do
28+ ba <- extractByteArray cStr $ BS.length bs
29+ webSocketSend_ (unWebSocket ws) ba
30+
31+JS(getLocationHost_, "location.host", IO JSString)
32+
33+getLocationHost :: FromJSString r => a -> IO r
34+getLocationHost _ = liftM fromJSString getLocationHost_
35+
36+JS(getLocationProtocol_, "location.protocol", IO JSString)
37+
38+getLocationProtocol :: FromJSString r => a -> IO r
39+getLocationProtocol _ = liftM fromJSString getLocationProtocol_
40+
41+newWebSocket :: a -> String -> (ByteString -> IO ()) -> IO () -> IO JSWebSocket
42+newWebSocket _ url onMessage onClose = do
43+ onMessageFun <- syncCallback1 AlwaysRetain True $ onMessage <=< return . encodeUtf8 . fromJSString
44+ rec onCloseFun <- syncCallback AlwaysRetain True $ do
45+ release onMessageFun
46+ release onCloseFun
47+ onClose
48+ liftM JSWebSocket $ newWebSocket_ (toJSString url) onMessageFun onCloseFun
49+
src-ghcjs/Reflex/Dom/Xhr/Foreign.hs+3
@@ -333,6 +333,9 @@ xmlHttpRequestGetResponseText self
333333responseTextToText :: (Maybe JSString) -> Maybe Text
334334responseTextToText r = fmap (T.pack . fromJSString) r
335335
336+statusTextToText :: Text -> Text
337+statusTextToText = id
338+
336339foreign import javascript unsafe "$1[\"responseXML\"]"
337340 ghcjs_dom_xml_http_request_get_response_xml ::
338341 JSRef XMLHttpRequest -> IO (JSRef Document)
src/Reflex/Dom.hs+2
@@ -4,6 +4,7 @@ module Reflex.Dom ( module Reflex
44 , module Reflex.Dom.Widget
55 , module Reflex.Dom.Xhr
66 , module Reflex.Dom.Time
7+ , module Reflex.Dom.WebSocket
78 ) where
89
910import Reflex
@@ -12,3 +13,4 @@ import Reflex.Dom.Internal
1213import Reflex.Dom.Widget
1314import Reflex.Dom.Xhr
1415import Reflex.Dom.Time
16+import Reflex.Dom.WebSocket
src/Reflex/Dom/Time.hs+16−5
@@ -41,11 +41,22 @@ tickLossyFrom
4141 -> m (Event t TickInfo)
4242tickLossyFrom dt t0 e = performEventAsync $ fmap callAtNextInterval e
4343 where callAtNextInterval _ cb = void $ liftIO $ forkIO $ forever $ do
44− t <- getCurrentTime
45− let offset = t `diffUTCTime` t0
46− (n, alreadyElapsed) = offset `divMod'` dt
47− threadDelay $ ceiling $ (dt - alreadyElapsed) * 1000000
48− cb $ TickInfo t n alreadyElapsed
44+ tick <- getCurrentTick dt t0
45+ threadDelay $ ceiling $ (dt - _tickInfo_alreadyElapsed tick) * 1000000
46+ cb tick
47+
48+clockLossy :: MonadWidget t m => NominalDiffTime -> UTCTime -> m (Dynamic t TickInfo)
49+clockLossy dt t0 = do
50+ initial <- liftIO $ getCurrentTick dt t0
51+ e <- tickLossy dt t0
52+ holdDyn initial e
53+
54+getCurrentTick :: NominalDiffTime -> UTCTime -> IO TickInfo
55+getCurrentTick dt t0 = do
56+ t <- getCurrentTime
57+ let offset = t `diffUTCTime` t0
58+ (n, alreadyElapsed) = offset `divMod'` dt
59+ return $ TickInfo t n alreadyElapsed
4960
5061-- | Delay an Event's occurrences by a given amount in seconds.
5162delay :: MonadWidget t m => NominalDiffTime -> Event t a -> m (Event t a)
src/Reflex/Dom/WebSocket.hsnew file+63
@@ -0,0 +1,63 @@
1+{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, CPP, TemplateHaskell, NoMonomorphismRestriction, EmptyDataDecls, RankNTypes, GADTs, RecursiveDo, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, TypeFamilies, FlexibleContexts, DeriveDataTypeable, GeneralizedNewtypeDeriving, StandaloneDeriving, ConstraintKinds, UndecidableInstances, PolyKinds, AllowAmbiguousTypes #-}
2+
3+module Reflex.Dom.WebSocket where
4+
5+import Prelude hiding (div, span, mapM, mapM_, concat, concatMap, all, sequence)
6+
7+import Reflex
8+import Reflex.Host.Class
9+import Reflex.Dom.Class
10+import Reflex.Dom.WebSocket.Foreign
11+
12+import Control.Concurrent
13+import Control.Lens
14+import Control.Monad hiding (forM, forM_, mapM, mapM_, sequence)
15+import Control.Monad.IO.Class
16+import Control.Monad.Ref
17+import Control.Monad.State
18+import Data.ByteString (ByteString)
19+import Data.Default
20+import Data.Dependent.Map (DSum (..))
21+import Data.IORef
22+
23+data WebSocketConfig t
24+ = WebSocketConfig { _webSocketConfig_send :: Event t [ByteString]
25+ }
26+
27+instance Reflex t => Default (WebSocketConfig t) where
28+ def = WebSocketConfig never
29+
30+data WebSocket t
31+ = WebSocket { _webSocket_recv :: Event t ByteString
32+ }
33+
34+webSocket :: forall t m. (HasWebView m, MonadWidget t m) => String -> WebSocketConfig t -> m (WebSocket t)
35+webSocket url config = do
36+ wv <- fmap unWebViewSingleton askWebView
37+ postGui <- askPostGui
38+ runWithActions <- askRunWithActions
39+ (eRecv, eRecvTriggerRef) <- newEventWithTriggerRef
40+ currentSocketRef <- liftIO $ newIORef Nothing
41+ --TODO: Disconnect if value no longer needed
42+ let onMessage :: ByteString -> IO ()
43+ onMessage m = postGui $ do
44+ mt <- readRef eRecvTriggerRef
45+ forM_ mt $ \t -> runWithActions [t :=> m]
46+ start = do
47+ ws <- liftIO $ newWebSocket wv url onMessage $ do
48+ void $ forkIO $ do --TODO: Is the fork necessary, or do event handlers run in their own threads automatically?
49+ liftIO $ writeIORef currentSocketRef Nothing
50+ liftIO $ threadDelay 1000000
51+ start
52+ liftIO $ writeIORef currentSocketRef $ Just ws
53+ return ()
54+ schedulePostBuild $ liftIO start
55+ performEvent_ $ ffor (_webSocketConfig_send config) $ \payloads -> forM_ payloads $ \payload -> do
56+ mws <- liftIO $ readIORef currentSocketRef
57+ case mws of
58+ Nothing -> return () -- Discard --TODO: should we do something better here? probably buffer it, since we handle reconnection logic; how do we verify that the server has received things?
59+ Just ws -> do
60+ liftIO $ webSocketSend ws payload
61+ return $ WebSocket eRecv
62+
63+makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig
src/Reflex/Dom/Widget.hs+2
@@ -1,8 +1,10 @@
11module Reflex.Dom.Widget ( module Reflex.Dom.Widget.Basic
22 , module Reflex.Dom.Widget.Input
33 , module Reflex.Dom.Widget.Lazy
4+ , module Reflex.Dom.Widget.Resize
45 ) where
56
67import Reflex.Dom.Widget.Basic
78import Reflex.Dom.Widget.Input
89import Reflex.Dom.Widget.Lazy
10+import Reflex.Dom.Widget.Resize
src/Reflex/Dom/Widget/Basic.hs+11−4
@@ -277,19 +277,26 @@ listViewWithKey' :: (Ord k, MonadWidget t m) => Dynamic t (Map k v) -> (k -> Dyn
277277listViewWithKey' vals mkChild = liftM current $ listWithKey vals mkChild
278278
279279-- | Create a dynamically-changing set of widgets, one of which is selected at any time.
280−selectViewListWithKey_ :: forall t m k v a. (MonadWidget t m, Ord k)
280+selectViewListWithKey :: forall t m k v a. (MonadWidget t m, Ord k)
281281 => Dynamic t k -- ^ Current selection key
282282 -> Dynamic t (Map k v) -- ^ Dynamic key/value map
283283 -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -- ^ Function to create a widget for a given key from Dynamic value and Dynamic Bool indicating if this widget is currently selected
284− -> m (Event t k) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget.
285−selectViewListWithKey_ selection vals mkChild = do
284+ -> m (Event t (k, a)) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget.
285+selectViewListWithKey selection vals mkChild = do
286286 let selectionDemux = demux selection -- For good performance, this value must be shared across all children
287287 selectChild <- listWithKey vals $ \k v -> do
288288 selected <- getDemuxed selectionDemux k
289289 selectSelf <- mkChild k v selected
290− return $ fmap (const k) selectSelf
290+ return $ fmap ((,) k) selectSelf
291291 liftM switchPromptlyDyn $ mapDyn (leftmost . Map.elems) selectChild
292292
293+selectViewListWithKey_ :: forall t m k v a. (MonadWidget t m, Ord k)
294+ => Dynamic t k -- ^ Current selection key
295+ -> Dynamic t (Map k v) -- ^ Dynamic key/value map
296+ -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -- ^ Function to create a widget for a given key from Dynamic value and Dynamic Bool indicating if this widget is currently selected
297+ -> m (Event t k) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget.
298+selectViewListWithKey_ selection vals mkChild = liftM (fmap fst) $ selectViewListWithKey selection vals mkChild
299+
293300--------------------------------------------------------------------------------
294301-- Basic DOM manipulation helpers
295302--------------------------------------------------------------------------------
src/Reflex/Dom/Widget/Input.hs+1
@@ -49,6 +49,7 @@ instance Reflex t => Default (TextInputConfig t) where
4949 , _textInputConfig_attributes = constDyn mempty
5050 }
5151
52+-- | Create an input whose value is a string. By default, the "type" attribute is set to "text", but it can be changed using the _textInputConfig_inputType field. Note that only types for which the value is always a string will work - types whose value may be null will not work properly with this widget.
5253textInput :: MonadWidget t m => TextInputConfig t -> m (TextInput t)
5354textInput (TextInputConfig inputType initial eSetValue dAttrs) = do
5455 e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" inputType) dAttrs
src/Reflex/Dom/Widget/Lazy.hs+69
@@ -6,12 +6,15 @@ import Reflex
66import Reflex.Dom.Class
77import Reflex.Dom.Widget.Basic
88
9+import Control.Monad
910import Control.Monad.IO.Class
1011import Data.Fixed
1112import Data.Monoid
1213import qualified Data.Map as Map
1314import Data.Map (Map)
1415import GHCJS.DOM.Element
16+import Data.Maybe
17+import Data.Int
1518
1619-- |A list view for long lists. Creates a scrollable element and only renders child row elements near the current scroll position.
1720virtualListWithSelection :: forall t m k v. (MonadWidget t m, Ord k)
@@ -67,4 +70,70 @@ virtualListWithSelection heightPx rowPx maxIndex i0 setI listTag listAttrs rowTa
6770 preItems = min startingIndex numItems
6871 in (topPx - preItems * sizeIncrement, (startingIndex - preItems, preItems + numItems * 2))
6972
73+virtualList :: forall t m k v a. (MonadWidget t m, Ord k)
74+ => Dynamic t Int -- ^ A 'Dynamic' of the visible region's height in pixels
75+ -> Int -- ^ The fixed height of each row in pixels
76+ -> Dynamic t Int -- ^ A 'Dynamic' of the total number of items
77+ -> Int -- ^ The index of the row to scroll to on initialization
78+ -> Event t Int -- ^ An 'Event' containing a row index. Used to scroll to the given index.
79+ -> (k -> Int) -- ^ Key to Index function, used to position items.
80+ -> Map k v -- ^ The initial 'Map' of items
81+ -> Event t (Map k (Maybe v)) -- ^ The update 'Event'. Nothing values are removed from the list and Just values are added or updated.
82+ -> (k -> v -> Event t v -> m a) -- ^ The row child element builder.
83+ -> m (Dynamic t (Int, Int), Dynamic t (Map k a)) -- ^ A tuple containing: a 'Dynamic' of the index (based on the current scroll position) and number of items currently being rendered, and the 'Dynamic' list result
84+virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder = do
85+ virtualH <- mapDyn (mkVirtualHeight . (*) rowPx) maxIndex
86+ containerStyle <- mapDyn mkContainer heightPx
87+ viewportStyle <- mapDyn mkViewport heightPx
88+ pb <- getPostBuild
89+ rec (viewport, result) <- elDynAttr "div" containerStyle $ elDynAttr' "div" viewportStyle $ elDynAttr "div" virtualH $
90+ listWithKeyShallowDiff items0 itemsUpdate $ \k v e -> elAttr "div" (mkRow k) $ itemBuilder k v e
91+ scrollPosition <- holdDyn 0 $ leftmost [ domEvent Scroll viewport
92+ , fmap (const (i0 * rowPx)) pb
93+ ]
94+ window <- combineDyn (\h -> findWindow h rowPx) heightPx scrollPosition
95+ performEvent_ $ fmap (\i -> liftIO $ elementSetScrollTop (_el_element viewport) (i * rowPx)) $ leftmost [setI, fmap (const i0) pb]
96+ return (nubDyn window, result)
97+ where
98+ toStyleAttr m = "style" =: (Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m)
99+ mkViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
100+ "left" =: "0" <> "right" =: "0" <> "height" =: (show h <> "px")
101+ mkContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (show h <> "px")
102+ mkVirtualHeight h = let h' = h * rowPx
103+ in toStyleAttr $ "height" =: (show h <> "px") <>
104+ "overflow" =: "hidden" <>
105+ "position" =: "relative"
106+ mkRow k = toStyleAttr $ "height" =: (show rowPx <> "px") <>
107+ "top" =: ((<>"px") $ show $ keyToIndex k * rowPx) <>
108+ "position" =: "absolute" <>
109+ "width" =: "100%"
110+ findWindow windowSize sizeIncrement startingPosition =
111+ let (startingIndex, topOffsetPx) = startingPosition `divMod'` sizeIncrement
112+ numItems = (windowSize + sizeIncrement - 1) `div` sizeIncrement
113+ in (startingIndex, numItems)
70114
115+virtualListBuffered
116+ :: (Ord k, MonadWidget t m)
117+ => Int
118+ -> Dynamic t Int
119+ -> Int
120+ -> Dynamic t Int
121+ -> Int
122+ -> Event t Int
123+ -> (k -> Int)
124+ -> Map k v
125+ -> Event t (Map k (Maybe v))
126+ -> (k -> v -> Event t v -> m a)
127+ -> m (Event t (Int, Int), Dynamic t (Map k a))
128+virtualListBuffered buffer heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder = do
129+ (win, m) <- virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder
130+ pb <- getPostBuild
131+ let extendWin o l = (max 0 (o - l * (buffer-1) `div` 2), l * buffer)
132+ rec let winHitEdge = fmapMaybe id $ attachWith (\(oldOffset, oldLimit) (winOffset, winLimit) ->
133+ if winOffset > oldOffset && winOffset + winLimit < oldOffset + oldLimit
134+ then Nothing
135+ else Just (extendWin winOffset winLimit)) (current winBuffered) (updated win)
136+ winBuffered <- holdDyn (0, 0) $ leftmost [ winHitEdge
137+ , fmap (uncurry extendWin) $ tagDyn win pb
138+ ]
139+ return (updated winBuffered, m)
src/Reflex/Dom/Widget/Resize.hsnew file+62
@@ -0,0 +1,62 @@
1+{-# LANGUAGE RecursiveDo #-}
2+module Reflex.Dom.Widget.Resize where
3+
4+import Reflex
5+import Reflex.Dom.Class
6+import Reflex.Dom.Widget.Basic
7+
8+import Control.Monad
9+import Control.Monad.IO.Class
10+import Data.Monoid
11+import GHCJS.DOM.Element
12+
13+-- | A widget that wraps the given widget in a div and fires an event when resized.
14+-- Adapted from github.com/marcj/css-element-queries
15+resizeDetector :: MonadWidget t m => m a -> m (Event t (), a)
16+resizeDetector = resizeDetectorWithStyle ""
17+
18+resizeDetectorWithStyle :: MonadWidget t m
19+ => String -- ^ A css style string. Warning: It should not contain the "position" style attribute.
20+ -> m a -- ^ The embedded widget
21+ -> m (Event t (), a) -- ^ An 'Event' that fires on resize, and the result of the embedded widget
22+resizeDetectorWithStyle styleString w = do
23+ let childStyle = "position: absolute; left: 0; top: 0;"
24+ containerAttrs = "style" =: "position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
25+ (parent, (expand, expandChild, shrink, w')) <- elAttr' "div" ("style" =: ("position: relative;" <> styleString)) $ do
26+ w' <- w
27+ elAttr "div" containerAttrs $ do
28+ (expand, (expandChild, _)) <- elAttr' "div" containerAttrs $ elAttr' "div" ("style" =: childStyle) $ return ()
29+ (shrink, _) <- elAttr' "div" containerAttrs $ elAttr "div" ("style" =: (childStyle <> "width: 200%; height: 200%;")) $ return ()
30+ return (expand, expandChild, shrink, w')
31+ let reset = do
32+ let e = _el_element expand
33+ s = _el_element shrink
34+ eow <- elementGetOffsetWidth e
35+ eoh <- elementGetOffsetHeight e
36+ let ecw = eow + 10
37+ ech = eoh + 10
38+ elementSetAttribute (_el_element expandChild) "style" (childStyle <> "width: " <> show ecw <> "px;" <> "height: " <> show ech <> "px;")
39+ esw <- elementGetScrollWidth e
40+ elementSetScrollLeft e esw
41+ esh <- elementGetScrollHeight e
42+ elementSetScrollTop e esh
43+ ssw <- elementGetScrollWidth s
44+ elementSetScrollLeft s ssw
45+ ssh <- elementGetScrollHeight s
46+ elementSetScrollTop s ssh
47+ lastWidth <- elementGetOffsetWidth (_el_element parent)
48+ lastHeight <- elementGetOffsetHeight (_el_element parent)
49+ return (Just lastWidth, Just lastHeight)
50+ resetIfChanged ds = do
51+ pow <- elementGetOffsetWidth (_el_element parent)
52+ poh <- elementGetOffsetHeight (_el_element parent)
53+ if ds == (Just pow, Just poh)
54+ then return Nothing
55+ else liftM Just reset
56+ pb <- getPostBuild
57+ expandScroll <- wrapDomEvent (_el_element expand) elementOnscroll $ return ()
58+ shrinkScroll <- wrapDomEvent (_el_element shrink) elementOnscroll $ return ()
59+ size0 <- performEvent $ fmap (const $ liftIO reset) pb
60+ rec resize <- performEventAsync $ fmap (\d cb -> liftIO $ cb =<< resetIfChanged d) $ tag (current dimensions) $ leftmost [expandScroll, shrinkScroll]
61+ dimensions <- holdDyn (Nothing, Nothing) $ leftmost [ size0, fmapMaybe id resize ]
62+ return (fmap (const ()) $ fmapMaybe id resize, w')
src/Reflex/Dom/Xhr.hs+17−1
@@ -21,6 +21,9 @@ import Control.Lens
2121import Control.Monad hiding (forM)
2222import Control.Monad.IO.Class
2323import Data.Aeson
24+import Data.Aeson.Encode
25+import qualified Data.Text.Lazy as LT
26+import qualified Data.Text.Lazy.Builder as B
2427import qualified Data.ByteString.Lazy as BL
2528import Data.Default
2629import Data.Map (Map)
@@ -52,6 +55,8 @@ data XhrRequestConfig a
5255
5356data XhrResponse
5457 = XhrResponse { _xhrResponse_body :: Maybe Text
58+ , _xhrResponse_status :: Word
59+ , _xhrResponse_statusText :: Text
5560 }
5661 deriving (Show, Read, Eq, Ord, Typeable)
5762
@@ -85,10 +90,12 @@ newXMLHttpRequest req cb = do
8590 maybe (return ()) (xmlHttpRequestSetResponseType xhr . toResponseType) (_xhrRequestConfig_responseType c)
8691 _ <- xmlHttpRequestOnreadystatechange xhr $ do
8792 readyState <- liftIO $ xmlHttpRequestGetReadyState xhr
93+ status <- liftIO $ xmlHttpRequestGetStatus xhr
94+ statusText <- liftIO $ xmlHttpRequestGetStatusText xhr
8895 if readyState == 4
8996 then do
9097 r <- liftIO $ xmlHttpRequestGetResponseText xhr
91− _ <- liftIO $ postGui $ cb $ XhrResponse $ responseTextToText r
98+ _ <- liftIO $ postGui $ cb $ XhrResponse (responseTextToText r) status (statusTextToText statusText)
9299 return ()
93100 else return ()
94101 _ <- xmlHttpRequestSend xhr (_xhrRequestConfig_sendData c)
@@ -116,6 +123,15 @@ getAndDecode url = do
116123 r <- performRequestAsync $ fmap (\x -> XhrRequest "GET" x def) url
117124 return $ fmap decodeXhrResponse r
118125
126+-- | Create a "POST" request from an URL and thing with a JSON representation
127+postJson :: (ToJSON a) => String -> a -> XhrRequest String
128+postJson url a =
129+ XhrRequest "POST" url $ def { _xhrRequestConfig_headers = headerUrlEnc
130+ , _xhrRequestConfig_sendData = body
131+ }
132+ where headerUrlEnc = "Content-type" =: "application/json"
133+ body = LT.unpack $ B.toLazyText $ encodeToTextBuilder $ toJSON a
134+
119135getMay :: MonadWidget t m => (Event t a -> m (Event t b)) -> Event t (Maybe a) -> m (Event t (Maybe b))
120136getMay f e = do
121137 e' <- f (fmapMaybe id e)