on develop help sign in
Merge branch 'release-0.1'
commit6bbb979ee289e120c4b02b0afbabd4e0ec7c2fcf authorRyan Trinkle <ryan.trinkle@gmail.com> dateTue 5 May 2015, 19:15 (11 years ago) parentf0b78a4de3c0b5
20 files changed+1134−289
.ghci+3−2 .gitignore+1 LICENSE+12 README.md+13 Setup.hs+2 default.nix+6−3 reflex-dom.cabal+34−19 src-ghc/Reflex/Dom/Internal/Foreign.hs+56 src-ghc/Reflex/Dom/Xhr/Foreign.hs+155 src-ghcjs/Reflex/Dom/Internal/Foreign.hs+9 src-ghcjs/Reflex/Dom/Xhr/Foreign.hs+396 src/Reflex/Dom.hs+5−1 src/Reflex/Dom/Class.hs+24−2 src/Reflex/Dom/Internal.hs+73−15 src/Reflex/Dom/Time.hs+50 src/Reflex/Dom/Widget.hs+2 src/Reflex/Dom/Widget/Basic.hs+24−6 src/Reflex/Dom/Widget/Input.hs+149−198 src/Reflex/Dom/Widget/Lazy.hs+67 src/Reflex/Dom/Xhr.hs+53−43
.ghci+3−2
@@ -1,3 +1,4 @@
11:set -isrc
2−:set -hide-package MonadCatchIO-mtl
3−:set -hide-package monads-fd
2+
3+-- Since we're in GHCi, we can't be using GHCJS
4+:set -isrc-ghc
.gitignore+1
@@ -29,3 +29,4 @@ tags
2929hsenv.log
3030\#*#
3131.#*
32+*_stub.h
LICENSE+12
@@ -0,0 +1,12 @@
1+Copyright (c) 2015, Obsidian Systems LLC
2+All rights reserved.
3+
4+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
6+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7+
8+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9+
10+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11+
12+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.mdnew file+13
@@ -0,0 +1,13 @@
1+## Reflex-DOM
2+### A Framework for Functional Reactive Web Apps in Haskell
3+
4+Reflex-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).
5+
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).
7+
8+### Additional resources
9+[try reflex](https://github.com/ryantrinkle/try-reflex)
10+
11+[reddit/r/reflexfrp](http://www.reddit.com/r/reflexfrp)
12+
13+irc.freenode.net #reflex-frp
Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
default.nix+6−3
@@ -1,6 +1,7 @@
11{ mkDerivation, dependent-map, ghcjs-dom, lens
2−, mtl, reflex, safe, text, these
3−, transformers, data-default, semigroups
2+, mtl, ref-tf, reflex, safe, text, these
3+, transformers, data-default, semigroups, aeson
4+, ghc, webkitgtk3-javascriptcore
45}:
56
67mkDerivation {
@@ -19,6 +20,8 @@ mkDerivation {
1920 text
2021 data-default
2122 semigroups
22− ];
23+ ref-tf
24+ aeson
25+ ] ++ (if (ghc.pname or null) == "ghcjs" then [ ] else [ webkitgtk3-javascriptcore ]);
2326 license = null;
2427}
reflex-dom.cabal+34−19
@@ -2,39 +2,49 @@ Name: reflex-dom
22Version: 0.1
33Synopsis: Glitch-free Functional Reactive Programming
44Description: Reflex is a Functional Reactive Programming implementation that provides strong guarantees of deterministic execution and scalable runtime performance
5−License: AllRightsReserved
5+License: BSD3
66License-file: LICENSE
77Author: Ryan Trinkle
88Maintainer: ryan.trinkle@gmail.com
99Stability: Experimental
1010Category: FRP
1111Build-type: Simple
12−Cabal-version: >=1.2
12+Cabal-version: >=1.9.2
1313
1414library
1515 hs-source-dirs: src
1616 build-depends:
17− base,
18− reflex,
19− dependent-map,
20− dependent-sum,
21− mtl,
22− transformers,
23− containers,
24− these,
25− lens,
26− ghcjs-dom,
27− safe,
28− text,
29− bytestring,
30− data-default,
31− semigroups
17+ base >= 4.7 && < 4.9,
18+ reflex == 0.2.*,
19+ dependent-sum == 0.2.*,
20+ dependent-map == 0.1.*,
21+ semigroups == 0.16.*,
22+ mtl >= 2.1 && < 2.3,
23+ containers == 0.5.*,
24+ these == 0.4.*,
25+ ref-tf == 0.4.*,
26+ transformers == 0.4.*,
27+ lens >= 4.7 && < 4.10,
28+ ghcjs-dom >= 0.1.1.3 && < 0.2,
29+ safe == 0.3.*,
30+ text == 1.2.*,
31+ bytestring == 0.10.*,
32+ data-default == 0.5.*,
33+ aeson == 0.8.*,
34+ time == 1.5.*
3235
3336 if impl(ghcjs)
37+ hs-source-dirs: src-ghcjs
3438 cpp-options: -D__GHCJS__
3539 build-depends:
36− aeson,
3740 ghcjs-base
41+ else
42+ hs-source-dirs: src-ghc
43+ build-depends:
44+ glib == 0.13.*,
45+ gtk3 == 0.13.*,
46+ webkitgtk3 == 0.13.*,
47+ webkitgtk3-javascriptcore == 0.13.*
3848
3949 exposed-modules:
4050 Reflex.Dom
@@ -43,8 +53,13 @@ library
4353 Reflex.Dom.Widget
4454 Reflex.Dom.Widget.Basic
4555 Reflex.Dom.Widget.Input
56+ Reflex.Dom.Widget.Lazy
4657 Reflex.Dom.Xhr
58+ Reflex.Dom.Time
59+ other-modules:
60+ Reflex.Dom.Internal.Foreign
61+ Reflex.Dom.Xhr.Foreign
4762
4863 other-extensions: TemplateHaskell
4964 ghc-prof-options: -fprof-auto
50− ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2
65+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
src-ghc/Reflex/Dom/Internal/Foreign.hsnew file+56
@@ -0,0 +1,56 @@
1+module Reflex.Dom.Internal.Foreign where
2+
3+import GHCJS.DOM hiding (runWebGUI)
4+import Control.Concurrent
5+import Control.Monad.State.Strict hiding (mapM, mapM_, forM, forM_, sequence, sequence_, get)
6+import GHCJS.DOM.Navigator
7+import GHCJS.DOM.DOMWindow
8+import Graphics.UI.Gtk hiding (Widget)
9+import Graphics.UI.Gtk.WebKit.WebView
10+import Graphics.UI.Gtk.WebKit.Types hiding (Event, Widget, unWidget)
11+import Graphics.UI.Gtk.WebKit.WebSettings
12+import Graphics.UI.Gtk.WebKit.WebFrame
13+import Data.List
14+
15+makeDefaultWebView :: String -> (WebView -> IO ()) -> IO ()
16+makeDefaultWebView userAgentKey main = do
17+ _ <- initGUI
18+ window <- windowNew
19+ _ <- timeoutAddFull (yield >> return True) priorityHigh 10
20+ windowSetDefaultSize window 900 600
21+ windowSetPosition window WinPosCenter
22+ scrollWin <- scrolledWindowNew Nothing Nothing
23+ webView <- webViewNew
24+ settings <- webViewGetWebSettings webView
25+ userAgent <- settings `get` webSettingsUserAgent
26+ settings `set` [ webSettingsUserAgent := userAgent ++ " " ++ userAgentKey
27+ , webSettingsEnableUniversalAccessFromFileUris := True
28+ ]
29+ webViewSetWebSettings webView settings
30+ window `containerAdd` scrollWin
31+ scrollWin `containerAdd` webView
32+ _ <- on window objectDestroy . liftIO $ mainQuit
33+ widgetShowAll window
34+ _ <- webView `on` loadFinished $ \_ -> do
35+ main webView
36+ wf <- webViewGetMainFrame webView
37+ webFrameLoadString wf "" Nothing "file:///"
38+ main webView
39+ mainGUI
40+
41+runWebGUI :: (WebView -> IO ()) -> IO ()
42+runWebGUI = runWebGUI' "GHCJS"
43+
44+runWebGUI' :: String -> (WebView -> IO ()) -> IO ()
45+runWebGUI' userAgentKey main = do
46+ -- Are we in a java script inside some kind of browser
47+ mbWindow <- currentWindow
48+ case mbWindow of
49+ Just window -> do
50+ -- Check if we are running in javascript inside the the native version
51+ Just n <- domWindowGetNavigator window
52+ agent <- navigatorGetUserAgent n
53+ unless ((" " ++ userAgentKey) `isSuffixOf` agent) $ main (castToWebView window)
54+ Nothing -> do
55+ makeDefaultWebView userAgentKey main
56+
src-ghc/Reflex/Dom/Xhr/Foreign.hsnew file+155
@@ -0,0 +1,155 @@
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+module Reflex.Dom.Xhr.Foreign where
3+
4+import Control.Lens.Indexed
5+import qualified Data.Text as T
6+import Data.Text (Text)
7+import System.Glib.FFI
8+import Graphics.UI.Gtk.WebKit.WebView
9+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
10+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSObjectRef
11+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
12+import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
13+import Graphics.UI.Gtk.WebKit.JavaScriptCore.WebFrame
14+
15+data XMLHttpRequest = XMLHttpRequest JSValueRef JSContextRef deriving (Eq, Ord)
16+
17+xhrContext :: XMLHttpRequest -> JSContextRef
18+xhrContext (XMLHttpRequest _ c) = c
19+
20+xhrValue :: XMLHttpRequest -> JSValueRef
21+xhrValue (XMLHttpRequest v _) = v
22+
23+toJSObject :: JSContextRef -> [Ptr OpaqueJSValue] -> IO JSObjectRef
24+toJSObject ctx args = do
25+ o <- jsobjectmake ctx nullPtr nullPtr
26+ iforM_ args $ \n a -> do
27+ prop <- jsstringcreatewithutf8cstring $ show n
28+ jsobjectsetproperty ctx o prop a 1 nullPtr
29+ return o
30+
31+toResponseType :: a -> a
32+toResponseType = id
33+
34+responseTextToText :: Maybe String -> Maybe Text
35+responseTextToText = fmap T.pack
36+
37+stringToJSValue :: JSContextRef -> String -> IO JSValueRef
38+stringToJSValue ctx s = jsvaluemakestring ctx =<< jsstringcreatewithutf8cstring s
39+
40+xmlHttpRequestNew :: WebView -> IO XMLHttpRequest
41+xmlHttpRequestNew wv = do
42+ --wv <- readIORef globalWebViewRef
43+ wf <- webViewGetMainFrame wv
44+ jsContext <- webFrameGetGlobalContext wf
45+ xhrScript <- jsstringcreatewithutf8cstring "new XMLHttpRequest()"
46+ xhr' <- jsevaluatescript jsContext xhrScript nullPtr nullPtr 1 nullPtr
47+ jsvalueprotect jsContext xhr'
48+ return $ XMLHttpRequest xhr' jsContext
49+
50+xmlHttpRequestOpen :: XMLHttpRequest -> String -> String -> Bool -> String -> String -> IO ()
51+xmlHttpRequestOpen xhr method url async user password = do
52+ let c = xhrContext xhr
53+ method' <- stringToJSValue c method
54+ url' <- stringToJSValue c url
55+ async' <- jsvaluemakeboolean (xhrContext xhr) async
56+ user' <- stringToJSValue c user
57+ password' <- stringToJSValue c password
58+ o <- toJSObject c [xhrValue xhr, method', url', async', user', password']
59+ script <- jsstringcreatewithutf8cstring "this[0].open(this[1], this[2], this[3], this[4], this[5])"
60+ _ <- jsevaluatescript c script o nullPtr 1 nullPtr
61+ return ()
62+
63+foreign import ccall "wrapper"
64+ wrapper :: JSObjectCallAsFunctionCallback' -> IO JSObjectCallAsFunctionCallback
65+
66+xmlHttpRequestOnreadystatechange :: XMLHttpRequest -> IO () -> IO ()
67+xmlHttpRequestOnreadystatechange xhr userCallback = do
68+ let c = xhrContext xhr
69+ fp <- wrapper $ \_ _ _ _ _ _ -> do
70+ userCallback
71+ jsvaluemakeundefined c
72+ cb <- jsobjectmakefunctionwithcallback c nullPtr fp
73+ o <- toJSObject c [xhrValue xhr, cb]
74+ script <- jsstringcreatewithutf8cstring "this[0].onreadystatechange=this[1]"
75+ _ <- jsevaluatescript c script o nullPtr 1 nullPtr
76+ return ()
77+
78+xmlHttpRequestGetReadyState :: XMLHttpRequest -> IO Word
79+xmlHttpRequestGetReadyState xhr = do
80+ let c = xhrContext xhr
81+ script <- jsstringcreatewithutf8cstring "this.readyState"
82+ rs <- jsevaluatescript c script (xhrValue xhr) nullPtr 1 nullPtr
83+ d <- jsvaluetonumber c rs nullPtr
84+ return $ truncate d
85+
86+xmlHttpRequestGetResponseText :: XMLHttpRequest -> IO (Maybe String)
87+xmlHttpRequestGetResponseText xhr = do
88+ let c = xhrContext xhr
89+ script <- jsstringcreatewithutf8cstring "this.responseText"
90+ t <- jsevaluatescript c script (xhrValue xhr) nullPtr 1 nullPtr
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+
102+xmlHttpRequestSend :: XMLHttpRequest -> Maybe String -> IO ()
103+xmlHttpRequestSend xhr payload = do
104+ let c = xhrContext xhr
105+ (o,s) <- case payload of
106+ Nothing -> do
107+ o <- toJSObject c [xhrValue xhr]
108+ s <- jsstringcreatewithutf8cstring "this[0].send();"
109+ return (o,s)
110+ Just payload' -> do
111+ d <- stringToJSValue c payload'
112+ o <- toJSObject c [xhrValue xhr, d]
113+ s <- jsstringcreatewithutf8cstring "this[0].send(this[1])"
114+ return (o,s)
115+ _ <- jsevaluatescript c s o nullPtr 1 nullPtr
116+ return ()
117+
118+xmlHttpRequestSetRequestHeader :: XMLHttpRequest -> String -> String -> IO ()
119+xmlHttpRequestSetRequestHeader xhr header value = do
120+ let c = xhrContext xhr
121+ header' <- stringToJSValue c header
122+ value' <- stringToJSValue c value
123+ o <- toJSObject c [xhrValue xhr, header', value']
124+ script <- jsstringcreatewithutf8cstring "this[0].setRequestHeader(this[1], this[2])"
125+ _ <- jsevaluatescript c script o nullPtr 1 nullPtr
126+ return ()
127+
128+xmlHttpRequestSetResponseType :: XMLHttpRequest -> String -> IO ()
129+xmlHttpRequestSetResponseType xhr t = do
130+ let c = xhrContext xhr
131+ t' <- stringToJSValue c t
132+ o <- toJSObject c [xhrValue xhr, t']
133+ script <- jsstringcreatewithutf8cstring "this[0].responseType = this[1]"
134+ _ <- jsevaluatescript c script o nullPtr 1 nullPtr
135+ return ()
136+
137+xmlHttpRequestGetStatus :: XMLHttpRequest -> IO Word
138+xmlHttpRequestGetStatus xhr = do
139+ let c = xhrContext xhr
140+ script <- jsstringcreatewithutf8cstring "this.status"
141+ s <- jsevaluatescript c script (xhrValue xhr) nullPtr 1 nullPtr
142+ d <- jsvaluetonumber c s nullPtr
143+ return $ truncate d
144+
145+xmlHttpRequestGetStatusText :: XMLHttpRequest -> IO String
146+xmlHttpRequestGetStatusText xhr = do
147+ let c = xhrContext xhr
148+ script <- jsstringcreatewithutf8cstring "this.statusText"
149+ t <- jsevaluatescript c script (xhrValue xhr) nullPtr 1 nullPtr
150+ j <- jsvaluetostringcopy c t nullPtr
151+ l <- jsstringgetmaximumutf8cstringsize j
152+ s <- allocaBytes (fromIntegral l) $ \ps -> do
153+ _ <- jsstringgetutf8cstring'_ j ps (fromIntegral l)
154+ peekCString ps
155+ return s
src-ghcjs/Reflex/Dom/Internal/Foreign.hsnew file+9
@@ -0,0 +1,9 @@
1+module Reflex.Dom.Internal.Foreign (runWebGUI) where
2+
3+import GHCJS.DOM
4+import GHCJS.DOM.Types
5+import GHCJS.Types
6+import Data.Function
7+
8+instance Eq Node where
9+ (==) = eqRef `on` unNode
src-ghcjs/Reflex/Dom/Xhr/Foreign.hsnew file+396
@@ -0,0 +1,396 @@
1+{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI #-}
2+
3+module Reflex.Dom.Xhr.Foreign where
4+
5+import GHCJS.Types
6+import GHCJS.Foreign
7+import GHCJS.Marshal
8+import qualified Data.Text as T
9+import Data.Text (Text)
10+import Data.Word
11+import GHCJS.DOM.Types hiding (Text)
12+import Control.Applicative ((<$>))
13+import GHCJS.DOM.EventM
14+import GHCJS.DOM
15+import Data.Function
16+
17+prepareWebView :: WebView -> IO ()
18+prepareWebView _ = return ()
19+
20+foreign import javascript unsafe "h$isInstanceOf $1 $2"
21+ typeInstanceIsA' :: JSRef a -> JSRef GType -> Bool
22+
23+typeInstanceIsA :: JSRef a -> GType -> Bool
24+typeInstanceIsA o (GType t) = typeInstanceIsA' o t
25+
26+castTo :: (GObjectClass obj, GObjectClass obj') => GType -> String
27+ -> (obj -> obj')
28+castTo gtype objTypeName obj =
29+ case toGObject obj of
30+ gobj@(GObject objRef)
31+ | typeInstanceIsA objRef gtype
32+ -> unsafeCastGObject gobj
33+ | otherwise -> error $ "Cannot cast object to " ++ objTypeName
34+
35+
36+newtype XMLHttpRequest = XMLHttpRequest { unXMLHttpRequest :: JSRef XMLHttpRequest }
37+
38+instance Eq XMLHttpRequest where
39+ (==) = eqRef `on` unXMLHttpRequest
40+
41+instance ToJSRef XMLHttpRequest where
42+ toJSRef = return . unXMLHttpRequest
43+ {-# INLINE toJSRef #-}
44+
45+instance FromJSRef XMLHttpRequest where
46+ fromJSRef = return . fmap XMLHttpRequest . maybeJSNull
47+ {-# INLINE fromJSRef #-}
48+
49+class GObjectClass o => IsXMLHttpRequest o
50+toXMLHttpRequest :: IsXMLHttpRequest o => o -> XMLHttpRequest
51+toXMLHttpRequest = unsafeCastGObject . toGObject
52+
53+instance IsXMLHttpRequest XMLHttpRequest
54+instance GObjectClass XMLHttpRequest where
55+ toGObject = GObject . castRef . unXMLHttpRequest
56+ unsafeCastGObject = XMLHttpRequest . castRef . unGObject
57+
58+castToXMLHttpRequest :: GObjectClass obj => obj -> XMLHttpRequest
59+castToXMLHttpRequest = castTo gTypeXMLHttpRequest "XMLHttpRequest"
60+
61+foreign import javascript unsafe "window[\"XMLHttpRequest\"]" gTypeXMLHttpRequest' :: JSRef GType
62+gTypeXMLHttpRequest :: GType
63+gTypeXMLHttpRequest = GType gTypeXMLHttpRequest'
64+
65+
66+newtype XMLHttpRequestUpload = XMLHttpRequestUpload { unXMLHttpRequestUpload :: JSRef XMLHttpRequestUpload }
67+
68+instance Eq XMLHttpRequestUpload where
69+ (==) = eqRef `on` unXMLHttpRequestUpload
70+
71+instance ToJSRef XMLHttpRequestUpload where
72+ toJSRef = return . unXMLHttpRequestUpload
73+ {-# INLINE toJSRef #-}
74+
75+instance FromJSRef XMLHttpRequestUpload where
76+ fromJSRef = return . fmap XMLHttpRequestUpload . maybeJSNull
77+ {-# INLINE fromJSRef #-}
78+
79+class GObjectClass o => IsXMLHttpRequestUpload o
80+toXMLHttpRequestUpload :: IsXMLHttpRequestUpload o => o -> XMLHttpRequestUpload
81+toXMLHttpRequestUpload = unsafeCastGObject . toGObject
82+
83+instance IsXMLHttpRequestUpload XMLHttpRequestUpload
84+instance GObjectClass XMLHttpRequestUpload where
85+ toGObject = GObject . castRef . unXMLHttpRequestUpload
86+ unsafeCastGObject = XMLHttpRequestUpload . castRef . unGObject
87+
88+castToXMLHttpRequestUpload :: GObjectClass obj => obj -> XMLHttpRequestUpload
89+castToXMLHttpRequestUpload = castTo gTypeXMLHttpRequestUpload "XMLHttpRequestUpload"
90+
91+foreign import javascript unsafe "window[\"XMLHttpRequestUpload\"]" gTypeXMLHttpRequestUpload' :: JSRef GType
92+gTypeXMLHttpRequestUpload :: GType
93+gTypeXMLHttpRequestUpload = GType gTypeXMLHttpRequestUpload'
94+
95+foreign import javascript unsafe "new XMLHttpRequest()"
96+ ghcjs_dom_xml_http_request_new ::
97+ IO (JSRef XMLHttpRequest)
98+
99+xmlHttpRequestNew :: a -> IO XMLHttpRequest
100+xmlHttpRequestNew _ = XMLHttpRequest <$> ghcjs_dom_xml_http_request_new
101+
102+foreign import javascript unsafe "$1[\"open\"]($2, $3, $4, $5, $6)"
103+ ghcjs_dom_xml_http_request_open ::
104+ JSRef XMLHttpRequest -> JSString -> JSString -> JSBool -> JSString -> JSString -> IO ()
105+
106+xmlHttpRequestOpen ::
107+ (IsXMLHttpRequest self, ToJSString method, ToJSString url, ToJSString user, ToJSString password) =>
108+ self -> method -> url -> Bool -> user -> password -> IO ()
109+xmlHttpRequestOpen self method url async user password
110+ = ghcjs_dom_xml_http_request_open
111+ (unXMLHttpRequest (toXMLHttpRequest self))
112+ (toJSString method)
113+ (toJSString url)
114+ (toJSBool async)
115+ (toJSString user)
116+ (toJSString password)
117+
118+foreign import javascript unsafe "($2===null)?$1[\"send\"]():$1[\"send\"]($2)"
119+ ghcjs_dom_xml_http_request_send ::
120+ JSRef XMLHttpRequest -> JSString -> IO ()
121+
122+xmlHttpRequestSend ::
123+ (IsXMLHttpRequest self, ToJSString payload) =>
124+ self -> Maybe payload -> IO ()
125+xmlHttpRequestSend self payload
126+ = ghcjs_dom_xml_http_request_send
127+ (unXMLHttpRequest (toXMLHttpRequest self))
128+ (case payload of
129+ Just p -> toJSString p
130+ Nothing -> jsNull)
131+
132+
133+foreign import javascript unsafe "$1[\"setRequestHeader\"]($2, $3)"
134+ ghcjs_dom_xml_http_request_set_request_header ::
135+ JSRef XMLHttpRequest -> JSString -> JSString -> IO ()
136+
137+xmlHttpRequestSetRequestHeader ::
138+ (IsXMLHttpRequest self, ToJSString header, ToJSString value) =>
139+ self -> header -> value -> IO ()
140+xmlHttpRequestSetRequestHeader self header value
141+ = ghcjs_dom_xml_http_request_set_request_header
142+ (unXMLHttpRequest (toXMLHttpRequest self))
143+ (toJSString header)
144+ (toJSString value)
145+
146+foreign import javascript unsafe "$1[\"abort\"]()"
147+ ghcjs_dom_xml_http_request_abort :: JSRef XMLHttpRequest -> IO ()
148+
149+xmlHttpRequestAbort :: (IsXMLHttpRequest self) => self -> IO ()
150+xmlHttpRequestAbort self
151+ = ghcjs_dom_xml_http_request_abort
152+ (unXMLHttpRequest (toXMLHttpRequest self))
153+
154+foreign import javascript unsafe "$1[\"getAllResponseHeaders\"]()"
155+ ghcjs_dom_xml_http_request_get_all_response_headers ::
156+ JSRef XMLHttpRequest -> IO JSString
157+
158+xmlHttpRequestGetAllResponseHeaders ::
159+ (IsXMLHttpRequest self, FromJSString result) =>
160+ self -> IO result
161+xmlHttpRequestGetAllResponseHeaders self
162+ = fromJSString <$>
163+ (ghcjs_dom_xml_http_request_get_all_response_headers
164+ (unXMLHttpRequest (toXMLHttpRequest self)))
165+
166+foreign import javascript unsafe "$1[\"getResponseHeader\"]($2)"
167+ ghcjs_dom_xml_http_request_get_response_header ::
168+ JSRef XMLHttpRequest -> JSString -> IO JSString
169+
170+xmlHttpRequestGetResponseHeader ::
171+ (IsXMLHttpRequest self, ToJSString header, FromJSString result) =>
172+ self -> header -> IO result
173+xmlHttpRequestGetResponseHeader self header
174+ = fromJSString <$>
175+ (ghcjs_dom_xml_http_request_get_response_header
176+ (unXMLHttpRequest (toXMLHttpRequest self))
177+ (toJSString header))
178+
179+foreign import javascript unsafe "$1[\"overrideMimeType\"]($2)"
180+ ghcjs_dom_xml_http_request_override_mime_type ::
181+ JSRef XMLHttpRequest -> JSString -> IO ()
182+
183+xmlHttpRequestOverrideMimeType ::
184+ (IsXMLHttpRequest self, ToJSString override) =>
185+ self -> override -> IO ()
186+xmlHttpRequestOverrideMimeType self override
187+ = ghcjs_dom_xml_http_request_override_mime_type
188+ (unXMLHttpRequest (toXMLHttpRequest self))
189+ (toJSString override)
190+
191+foreign import javascript unsafe
192+ "($1[\"dispatchEvent\"]($2) ? 1 : 0)"
193+ ghcjs_dom_xml_http_request_dispatch_event ::
194+ JSRef XMLHttpRequest -> JSRef Event -> IO Bool
195+
196+xmlHttpRequestDispatchEvent ::
197+ (IsXMLHttpRequest self, IsEvent evt) =>
198+ self -> Maybe evt -> IO Bool
199+xmlHttpRequestDispatchEvent self evt
200+ = ghcjs_dom_xml_http_request_dispatch_event
201+ (unXMLHttpRequest (toXMLHttpRequest self))
202+ (maybe jsNull (unEvent . toEvent) evt)
203+
204+cUNSENT :: Integer
205+cUNSENT = 0
206+
207+cOPENED :: Integer
208+cOPENED = 1
209+
210+cHEADERS_RECEIVED :: Integer
211+cHEADERS_RECEIVED = 2
212+
213+cLOADING :: Integer
214+cLOADING = 3
215+
216+cDONE :: Integer
217+cDONE = 4
218+
219+xmlHttpRequestOnabort ::
220+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
221+xmlHttpRequestOnabort = (connect "abort")
222+
223+xmlHttpRequestOnerror ::
224+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
225+xmlHttpRequestOnerror = (connect "error")
226+
227+xmlHttpRequestOnload ::
228+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
229+xmlHttpRequestOnload = (connect "load")
230+
231+xmlHttpRequestOnloadend ::
232+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
233+xmlHttpRequestOnloadend = (connect "loadend")
234+
235+xmlHttpRequestOnloadstart ::
236+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
237+xmlHttpRequestOnloadstart = (connect "loadstart")
238+
239+xmlHttpRequestOnprogress ::
240+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
241+xmlHttpRequestOnprogress = (connect "progress")
242+
243+xmlHttpRequestOntimeout ::
244+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
245+xmlHttpRequestOntimeout = (connect "timeout")
246+
247+xmlHttpRequestOnreadystatechange ::
248+ (IsXMLHttpRequest self) => Signal self (EventM UIEvent self ())
249+xmlHttpRequestOnreadystatechange = (connect "readystatechange")
250+
251+foreign import javascript unsafe "$1[\"timeout\"] = $2;"
252+ ghcjs_dom_xml_http_request_set_timeout ::
253+ JSRef XMLHttpRequest -> Word -> IO ()
254+
255+xmlHttpRequestSetTimeout ::
256+ (IsXMLHttpRequest self) => self -> Word -> IO ()
257+xmlHttpRequestSetTimeout self val
258+ = ghcjs_dom_xml_http_request_set_timeout
259+ (unXMLHttpRequest (toXMLHttpRequest self))
260+ val
261+
262+foreign import javascript unsafe "$1[\"timeout\"]"
263+ ghcjs_dom_xml_http_request_get_timeout ::
264+ JSRef XMLHttpRequest -> IO Word
265+
266+xmlHttpRequestGetTimeout ::
267+ (IsXMLHttpRequest self) => self -> IO Word
268+xmlHttpRequestGetTimeout self
269+ = ghcjs_dom_xml_http_request_get_timeout
270+ (unXMLHttpRequest (toXMLHttpRequest self))
271+
272+foreign import javascript unsafe "$1[\"readyState\"]"
273+ ghcjs_dom_xml_http_request_get_ready_state ::
274+ JSRef XMLHttpRequest -> IO Word
275+
276+xmlHttpRequestGetReadyState ::
277+ (IsXMLHttpRequest self) => self -> IO Word
278+xmlHttpRequestGetReadyState self
279+ = ghcjs_dom_xml_http_request_get_ready_state
280+ (unXMLHttpRequest (toXMLHttpRequest self))
281+
282+foreign import javascript unsafe "$1[\"withCredentials\"] = $2;"
283+ ghcjs_dom_xml_http_request_set_with_credentials ::
284+ JSRef XMLHttpRequest -> Bool -> IO ()
285+
286+xmlHttpRequestSetWithCredentials ::
287+ (IsXMLHttpRequest self) => self -> Bool -> IO ()
288+xmlHttpRequestSetWithCredentials self val
289+ = ghcjs_dom_xml_http_request_set_with_credentials
290+ (unXMLHttpRequest (toXMLHttpRequest self))
291+ val
292+
293+foreign import javascript unsafe
294+ "($1[\"withCredentials\"] ? 1 : 0)"
295+ ghcjs_dom_xml_http_request_get_with_credentials ::
296+ JSRef XMLHttpRequest -> IO Bool
297+
298+xmlHttpRequestGetWithCredentials ::
299+ (IsXMLHttpRequest self) => self -> IO Bool
300+xmlHttpRequestGetWithCredentials self
301+ = ghcjs_dom_xml_http_request_get_with_credentials
302+ (unXMLHttpRequest (toXMLHttpRequest self))
303+
304+foreign import javascript unsafe "$1[\"upload\"]"
305+ ghcjs_dom_xml_http_request_get_upload ::
306+ JSRef XMLHttpRequest -> IO (JSRef XMLHttpRequestUpload)
307+
308+xmlHttpRequestGetUpload ::
309+ (IsXMLHttpRequest self) => self -> IO (Maybe XMLHttpRequestUpload)
310+xmlHttpRequestGetUpload self
311+ = fmap XMLHttpRequestUpload . maybeJSNull <$>
312+ (ghcjs_dom_xml_http_request_get_upload
313+ (unXMLHttpRequest (toXMLHttpRequest self)))
314+
315+foreign import javascript unsafe "$1[\"responseText\"]"
316+ ghcjs_dom_xml_http_request_get_response_text ::
317+ JSRef XMLHttpRequest -> IO JSString
318+
319+xmlHttpRequestGetResponseText ::
320+ (IsXMLHttpRequest self, FromJSString result) => self -> IO (Maybe result)
321+xmlHttpRequestGetResponseText self
322+ = fmap fromJSString . maybeJSNull <$>
323+ (ghcjs_dom_xml_http_request_get_response_text
324+ (unXMLHttpRequest (toXMLHttpRequest self)))
325+
326+responseTextToText :: (Maybe JSString) -> Maybe Text
327+responseTextToText r = fmap (T.pack . fromJSString) r
328+
329+foreign import javascript unsafe "$1[\"responseXML\"]"
330+ ghcjs_dom_xml_http_request_get_response_xml ::
331+ JSRef XMLHttpRequest -> IO (JSRef Document)
332+
333+xmlHttpRequestGetResponseXML ::
334+ (IsXMLHttpRequest self) => self -> IO (Maybe Document)
335+xmlHttpRequestGetResponseXML self
336+ = fmap Document . maybeJSNull <$>
337+ (ghcjs_dom_xml_http_request_get_response_xml
338+ (unXMLHttpRequest (toXMLHttpRequest self)))
339+
340+foreign import javascript unsafe "$1[\"responseType\"] = $2;"
341+ ghcjs_dom_xml_http_request_set_response_type ::
342+ JSRef XMLHttpRequest -> JSString -> IO ()
343+
344+xmlHttpRequestSetResponseType ::
345+ (IsXMLHttpRequest self, ToJSString val) => self -> val -> IO ()
346+xmlHttpRequestSetResponseType self val
347+ = ghcjs_dom_xml_http_request_set_response_type
348+ (unXMLHttpRequest (toXMLHttpRequest self))
349+ (toJSString val)
350+
351+toResponseType :: (ToJSString a) => a -> JSString
352+toResponseType = toJSString
353+
354+foreign import javascript unsafe "$1[\"responseType\"]"
355+ ghcjs_dom_xml_http_request_get_response_type ::
356+ JSRef XMLHttpRequest -> IO JSString
357+
358+xmlHttpRequestGetResponseType ::
359+ (IsXMLHttpRequest self, FromJSString result) => self -> IO result
360+xmlHttpRequestGetResponseType self
361+ = fromJSString <$>
362+ (ghcjs_dom_xml_http_request_get_response_type
363+ (unXMLHttpRequest (toXMLHttpRequest self)))
364+
365+foreign import javascript unsafe "$1[\"status\"]"
366+ ghcjs_dom_xml_http_request_get_status ::
367+ JSRef XMLHttpRequest -> IO Word
368+
369+xmlHttpRequestGetStatus ::
370+ (IsXMLHttpRequest self) => self -> IO Word
371+xmlHttpRequestGetStatus self
372+ = ghcjs_dom_xml_http_request_get_status
373+ (unXMLHttpRequest (toXMLHttpRequest self))
374+
375+foreign import javascript unsafe "$1[\"statusText\"]"
376+ ghcjs_dom_xml_http_request_get_status_text ::
377+ JSRef XMLHttpRequest -> IO JSString
378+
379+xmlHttpRequestGetStatusText ::
380+ (IsXMLHttpRequest self, FromJSString result) => self -> IO result
381+xmlHttpRequestGetStatusText self
382+ = fromJSString <$>
383+ (ghcjs_dom_xml_http_request_get_status_text
384+ (unXMLHttpRequest (toXMLHttpRequest self)))
385+
386+foreign import javascript unsafe "$1[\"responseURL\"]"
387+ ghcjs_dom_xml_http_request_get_response_url ::
388+ JSRef XMLHttpRequest -> IO JSString
389+
390+xmlHttpRequestGetResponseURL ::
391+ (IsXMLHttpRequest self, FromJSString result) => self -> IO result
392+xmlHttpRequestGetResponseURL self
393+ = fromJSString <$>
394+ (ghcjs_dom_xml_http_request_get_response_url
395+ (unXMLHttpRequest (toXMLHttpRequest self)))
396+
src/Reflex/Dom.hs+5−1
@@ -1,10 +1,14 @@
1−module Reflex.Dom ( module Reflex.Dom.Class
1+module Reflex.Dom ( module Reflex
2+ , module Reflex.Dom.Class
23 , module Reflex.Dom.Internal
34 , module Reflex.Dom.Widget
45 , module Reflex.Dom.Xhr
6+ , module Reflex.Dom.Time
57 ) where
68
9+import Reflex
710import Reflex.Dom.Class
811import Reflex.Dom.Internal
912import Reflex.Dom.Widget
1013import Reflex.Dom.Xhr
14+import Reflex.Dom.Time
src/Reflex/Dom/Class.hs+24−2
@@ -15,6 +15,7 @@ import Control.Monad.Reader hiding (mapM, mapM_, forM, forM_, sequence)
1515import Control.Monad.State hiding (mapM, mapM_, forM, forM_, sequence)
1616import Data.Dependent.Sum (DSum (..))
1717import GHCJS.DOM.Types hiding (Event)
18+import GHCJS.DOM (WebView)
1819
1920-- | Alias for Data.Map.singleton
2021(=:) :: k -> a -> Map k a
@@ -27,9 +28,10 @@ keycodeEscape :: Int
2728keycodeEscape = 27
2829
2930class ( Reflex t, MonadHold t m, MonadIO m, Functor m, MonadReflexCreateTrigger t m
30− , HasDocument m
31+ , HasDocument m, HasWebView m, HasWebView (WidgetHost m), HasWebView (GuiAction m)
3132 , MonadIO (WidgetHost m), MonadIO (GuiAction m), Functor (WidgetHost m), MonadSample t (WidgetHost m)
32− , HasPostGui t (GuiAction m) (WidgetHost m), HasPostGui t (GuiAction m) m, MonadRef m, MonadRef (WidgetHost m)
33+ , HasPostGui t (GuiAction m) (WidgetHost m), HasPostGui t (GuiAction m) m, HasPostGui t (GuiAction m) (GuiAction m)
34+ , MonadRef m, MonadRef (WidgetHost m)
3335 , Ref m ~ Ref IO, Ref (WidgetHost m) ~ Ref IO --TODO: Eliminate this reliance on IO
3436 , MonadFix m
3537 ) => MonadWidget t m | m -> t where
@@ -52,6 +54,26 @@ instance HasDocument m => HasDocument (ReaderT r m) where
5254instance HasDocument m => HasDocument (StateT r m) where
5355 askDocument = lift askDocument
5456
57+class Monad m => HasWebView m where
58+ askWebView :: m WebView
59+
60+instance HasWebView m => HasWebView (ReaderT r m) where
61+ askWebView = lift askWebView
62+
63+instance HasWebView m => HasWebView (StateT r m) where
64+ askWebView = lift askWebView
65+
66+newtype Restore m = Restore { restore :: forall a. m a -> IO a }
67+
68+class Monad m => MonadIORestore m where
69+ askRestore :: m (Restore m)
70+
71+instance MonadIORestore m => MonadIORestore (ReaderT r m) where
72+ askRestore = do
73+ r <- ask
74+ parentRestore <- lift askRestore
75+ return $ Restore $ \a -> restore parentRestore $ runReaderT a r
76+
5577class (MonadRef h, Ref h ~ Ref m, MonadRef m) => HasPostGui t h m | m -> t h where
5678 askPostGui :: m (h () -> IO ())
5779 askRunWithActions :: m ([DSum (EventTrigger t)] -> h ())
src/Reflex/Dom/Internal.hs+73−15
@@ -1,11 +1,12 @@
1−{-# LANGUAGE TypeFamilies, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, RankNTypes, GADTs, ScopedTypeVariables, FunctionalDependencies, RecursiveDo, UndecidableInstances, GeneralizedNewtypeDeriving, StandaloneDeriving, EmptyDataDecls, NoMonomorphismRestriction, TypeOperators, DeriveDataTypeable, PackageImports, TemplateHaskell, LambdaCase, ConstraintKinds #-}
1+{-# LANGUAGE TypeFamilies, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, RankNTypes, GADTs, ScopedTypeVariables, FunctionalDependencies, RecursiveDo, UndecidableInstances, GeneralizedNewtypeDeriving, StandaloneDeriving, EmptyDataDecls, NoMonomorphismRestriction, TypeOperators, DeriveDataTypeable, PackageImports, TemplateHaskell, LambdaCase, ConstraintKinds, CPP #-}
22module Reflex.Dom.Internal where
33
44import Prelude hiding (mapM, mapM_, concat, sequence, sequence_)
55
6+import Reflex.Dom.Internal.Foreign
67import Reflex.Dom.Class
78
8−import GHCJS.DOM
9+import GHCJS.DOM hiding (runWebGUI)
910import GHCJS.DOM.Types hiding (Widget, unWidget, Event)
1011import GHCJS.DOM.Node
1112import GHCJS.DOM.HTMLElement
@@ -17,8 +18,9 @@ import Control.Lens
1718import Control.Monad hiding (mapM, mapM_, forM, forM_, sequence, sequence_)
1819import Control.Monad.Reader hiding (mapM, mapM_, forM, forM_, sequence, sequence_)
1920import Control.Monad.Ref
20−import Control.Monad.State.Strict hiding (mapM, mapM_, forM, forM_, sequence, sequence_)
21+import Control.Monad.State.Strict hiding (mapM, mapM_, forM, forM_, sequence, sequence_, get)
2122import Control.Concurrent
23+import Control.Applicative
2224import Data.ByteString (ByteString)
2325import Data.Dependent.Sum (DSum (..))
2426import Data.Foldable
@@ -31,6 +33,7 @@ data GuiEnv t h
3133 = GuiEnv { _guiEnvDocument :: !HTMLDocument
3234 , _guiEnvPostGui :: !(h () -> IO ())
3335 , _guiEnvRunWithActions :: !([DSum (EventTrigger t)] -> h ())
36+ , _guiEnvWebView :: !WebView
3437 }
3538
3639--TODO: Poorly named
@@ -47,6 +50,8 @@ instance MonadRef m => MonadRef (Gui t h m) where
4750 newRef = lift . newRef
4851 readRef = lift . readRef
4952 writeRef r = lift . writeRef r
53+
54+instance MonadAtomicRef m => MonadAtomicRef (Gui t h m) where
5055 atomicModifyRef r f = lift $ atomicModifyRef r f
5156
5257instance MonadSample t m => MonadSample t (Gui t h m) where
@@ -79,6 +84,17 @@ instance Monad m => HasDocument (Gui t h m) where
7984instance HasDocument m => HasDocument (Widget t m) where
8085 askDocument = lift askDocument
8186
87+instance Monad m => HasWebView (Gui t h m) where
88+ askWebView = Gui $ view guiEnvWebView
89+
90+instance MonadIORestore m => MonadIORestore (Gui t h m) where
91+ askRestore = Gui $ do
92+ r <- askRestore
93+ return $ Restore $ restore r . unGui
94+
95+instance HasWebView m => HasWebView (Widget t m) where
96+ askWebView = lift askWebView
97+
8298instance (MonadRef h, Ref h ~ Ref m, MonadRef m) => HasPostGui t h (Gui t h m) where
8399 askPostGui = Gui $ view guiEnvPostGui
84100 askRunWithActions = Gui $ view guiEnvRunWithActions
@@ -109,6 +125,8 @@ instance MonadRef m => MonadRef (Widget t m) where
109125 newRef = lift . newRef
110126 readRef = lift . readRef
111127 writeRef r = lift . writeRef r
128+
129+instance MonadAtomicRef m => MonadAtomicRef (Widget t m) where
112130 atomicModifyRef r f = lift $ atomicModifyRef r f
113131
114132instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (Widget t m) where
@@ -117,7 +135,7 @@ instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (Widget t m)
117135instance ( MonadRef m, Ref m ~ Ref IO, MonadRef h, Ref h ~ Ref IO --TODO: Shouldn't need to be IO
118136 , MonadIO m, MonadIO h, Functor m
119137 , ReflexHost t, MonadReflexCreateTrigger t m, MonadSample t m, MonadHold t m
120− , MonadFix m
138+ , MonadFix m, HasWebView h, HasPostGui t h h
121139 ) => MonadWidget t (Widget t (Gui t h m)) where
122140 type WidgetHost (Widget t (Gui t h m)) = Gui t h m
123141 type GuiAction (Widget t (Gui t h m)) = h
@@ -155,33 +173,73 @@ holdOnStartup a0 ma = do
155173 runFrameWithTriggerRef startupDoneTriggerRef a
156174 hold a0 startupDone
157175
158−mainWidget :: Widget Spider (Gui Spider SpiderHost (HostFrame Spider)) () -> IO ()
176+mainWidget :: Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO ()
159177mainWidget w = runWebGUI $ \webView -> do
160178 Just doc <- liftM (fmap castToHTMLDocument) $ webViewGetDomDocument webView
161179 Just body <- documentGetBody doc
162− attachWidget body w
180+ attachWidget body webView w
163181
164−mainWidgetWithHead :: Widget Spider (Gui Spider SpiderHost (HostFrame Spider)) () -> Widget Spider (Gui Spider SpiderHost (HostFrame Spider)) () -> IO ()
182+mainWidgetWithHead :: Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO ()
165183mainWidgetWithHead h b = runWebGUI $ \webView -> do
166184 Just doc <- liftM (fmap castToHTMLDocument) $ webViewGetDomDocument webView
167185 Just headElement <- liftM (fmap castToHTMLElement) $ documentGetHead doc
168− attachWidget headElement h
186+ attachWidget headElement webView h
169187 Just body <- documentGetBody doc
170− attachWidget body b
188+ attachWidget body webView b
171189
172−mainWidgetWithCss :: ByteString -> Widget Spider (Gui Spider SpiderHost (HostFrame Spider)) () -> IO ()
190+mainWidgetWithCss :: ByteString -> Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO ()
173191mainWidgetWithCss css w = runWebGUI $ \webView -> do
174192 Just doc <- liftM (fmap castToHTMLDocument) $ webViewGetDomDocument webView
175193 Just headElement <- liftM (fmap castToHTMLElement) $ documentGetHead doc
176194 htmlElementSetInnerHTML headElement $ "<style>" <> T.unpack (decodeUtf8 css) <> "</style>" --TODO: Fix this
177195 Just body <- documentGetBody doc
178− attachWidget body w
196+ attachWidget body webView w
197+
198+newtype WithWebView m a = WithWebView { unWithWebView :: ReaderT WebView m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
199+
200+instance (Monad m) => HasWebView (WithWebView m) where
201+ askWebView = WithWebView ask
202+
203+instance HasPostGui t h m => HasPostGui t (WithWebView h) (WithWebView m) where
204+ askPostGui = do
205+ postGui <- lift askPostGui
206+ webView <- askWebView
207+ return $ \h -> postGui $ runWithWebView h webView
208+ askRunWithActions = do
209+ runWithActions <- lift askRunWithActions
210+ return $ lift . runWithActions
211+
212+instance HasPostGui Spider SpiderHost SpiderHost where
213+ askPostGui = return $ \h -> liftIO $ runSpiderHost h
214+ askRunWithActions = return fireEvents
215+
216+instance MonadTrans WithWebView where
217+ lift = WithWebView . lift
218+
219+instance MonadRef m => MonadRef (WithWebView m) where
220+ type Ref (WithWebView m) = Ref m
221+ newRef = lift . newRef
222+ readRef = lift . readRef
223+ writeRef r = lift . writeRef r
224+
225+instance MonadAtomicRef m => MonadAtomicRef (WithWebView m) where
226+ atomicModifyRef r = lift . atomicModifyRef r
227+
228+deriving instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (WithWebView m)
229+instance MonadReflexHost t m => MonadReflexHost t (WithWebView m) where
230+ fireEventsAndRead dm a = lift $ fireEventsAndRead dm a
231+ subscribeEvent = lift . subscribeEvent
232+ runFrame = lift . runFrame
233+ runHostFrame = lift . runHostFrame
234+
235+runWithWebView :: WithWebView m a -> WebView -> m a
236+runWithWebView = runReaderT . unWithWebView
179237
180−attachWidget :: (IsHTMLElement e) => e -> Widget Spider (Gui Spider SpiderHost (HostFrame Spider)) a -> IO a
181−attachWidget rootElement w = runSpiderHost $ do --TODO: It seems to re-run this handler if the URL changes, even if it's only the fragment
238+attachWidget :: (IsHTMLElement e) => e -> WebView -> Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) a -> IO a
239+attachWidget rootElement wv w = runSpiderHost $ flip runWithWebView wv $ do --TODO: It seems to re-run this handler if the URL changes, even if it's only the fragment
182240 Just doc <- liftM (fmap castToHTMLDocument) $ liftIO $ nodeGetOwnerDocument rootElement
183241 frames <- liftIO newChan
184− rec let guiEnv = GuiEnv doc (writeChan frames . runSpiderHost) runWithActions :: GuiEnv Spider SpiderHost
242+ rec let guiEnv = GuiEnv doc (writeChan frames . runSpiderHost . flip runWithWebView wv) runWithActions wv :: GuiEnv Spider (WithWebView SpiderHost)
185243 runWithActions dm = do
186244 voidActionNeeded <- fireEventsAndRead dm $ do
187245 sequence =<< readEvent voidActionHandle
@@ -189,7 +247,7 @@ attachWidget rootElement w = runSpiderHost $ do --TODO: It seems to re-run this
189247 Just df <- liftIO $ documentCreateDocumentFragment doc
190248 (result, voidAction) <- runHostFrame $ flip runGui guiEnv $ do
191249 (r, postBuild, va) <- runWidget df w
192− postBuild
250+ postBuild -- This probably shouldn't be run inside the frame; we need to make sure we don't run a frame inside of a frame
193251 return (r, va)
194252 liftIO $ htmlElementSetInnerHTML rootElement ""
195253 _ <- liftIO $ nodeAppendChild rootElement $ Just df
src/Reflex/Dom/Time.hsnew file+50
@@ -0,0 +1,50 @@
1+module Reflex.Dom.Time where
2+
3+import Reflex
4+import Reflex.Dom.Class
5+
6+import Control.Concurrent
7+import Control.Monad
8+import Control.Monad.IO.Class
9+import Data.Fixed
10+import Data.Time.Clock
11+
12+data TickInfo
13+ = TickInfo { _tickInfo_lastUTC :: UTCTime
14+ -- ^ UTC time immediately after the last tick.
15+ , _tickInfo_n :: Integer
16+ -- ^ Number of time periods since t0
17+ , _tickInfo_alreadyElapsed :: NominalDiffTime
18+ -- ^ Amount of time already elapsed in the current tick period.
19+ }
20+ deriving (Eq)
21+
22+-- | Special case of tickLossyFrom that uses the post-build event to start the
23+-- tick thread.
24+tickLossy :: MonadWidget t m => NominalDiffTime -> UTCTime -> m (Event t TickInfo)
25+tickLossy dt t0 = tickLossyFrom dt t0 =<< getPostBuild
26+
27+-- | Send events over time with the given basis time and interval
28+-- If the system starts running behind, occurrences will be dropped rather than buffered
29+-- Each occurrence of the resulting event will contain the index of the current interval, with 0 representing the basis time
30+tickLossyFrom
31+ :: MonadWidget t m
32+ => NominalDiffTime
33+ -> UTCTime
34+ -> Event t a
35+ -- ^ Event that starts a tick generation thread. Usually you want this to
36+ -- be something like the result of getPostBuild that only fires once. But
37+ -- there could be uses for starting multiple timer threads.
38+ -> m (Event t TickInfo)
39+tickLossyFrom dt t0 e = performEventAsync $ fmap callAtNextInterval e
40+ where callAtNextInterval _ cb = void $ liftIO $ forkIO $ forever $ do
41+ t <- getCurrentTime
42+ let offset = t `diffUTCTime` t0
43+ (n, alreadyElapsed) = offset `divMod'` dt
44+ threadDelay $ ceiling $ (dt - alreadyElapsed) * 1000000
45+ cb $ TickInfo t n alreadyElapsed
46+
47+delay :: MonadWidget t m => NominalDiffTime -> Event t a -> m (Event t a)
48+delay dt e = performEventAsync $ ffor e $ \a cb -> liftIO $ void $ forkIO $ do
49+ threadDelay $ ceiling $ dt * 1000000
50+ cb a
src/Reflex/Dom/Widget.hs+2
@@ -1,6 +1,8 @@
11module Reflex.Dom.Widget ( module Reflex.Dom.Widget.Basic
22 , module Reflex.Dom.Widget.Input
3+ , module Reflex.Dom.Widget.Lazy
34 ) where
45
56import Reflex.Dom.Widget.Basic
67import Reflex.Dom.Widget.Input
8+import Reflex.Dom.Widget.Lazy
src/Reflex/Dom/Widget/Basic.hs+24−6
@@ -3,7 +3,9 @@
33module Reflex.Dom.Widget.Basic where
44
55import Reflex.Dom.Class
6+import Reflex.Dom.Internal.Foreign ()
67
8+import Prelude hiding (mapM, mapM_, sequence, sequence_)
79import Reflex
810import Reflex.Host.Class
911import Data.Functor.Misc
@@ -12,9 +14,11 @@ import qualified Data.Map as Map
1214import Data.Set (Set)
1315import qualified Data.Set as Set
1416import Data.Dependent.Sum (DSum (..))
17+import Data.Foldable
18+import Data.Traversable
1519import Control.Monad.Trans
16−import Control.Monad.Reader
17−import Control.Monad.State hiding (state)
20+import Control.Monad.Reader hiding (mapM, mapM_, forM, forM_, sequence, sequence_)
21+import Control.Monad.State hiding (state, mapM, mapM_, forM, forM_, sequence, sequence_)
1822import GHCJS.DOM.Node
1923import GHCJS.DOM.UIEvent
2024import GHCJS.DOM.EventM (event, EventM)
@@ -339,7 +343,7 @@ deleteBetweenExclusive s e = do
339343 Just currentParent -> do
340344 let go = do
341345 Just x <- nodeGetPreviousSibling e -- This can't be Nothing because we should hit 's' first
342− when (unNode (toNode s) /= unNode (toNode x)) $ do
346+ when (toNode s /= toNode x) $ do
343347 _ <- nodeRemoveChild currentParent $ Just x
344348 go
345349 go
@@ -354,7 +358,7 @@ deleteBetweenInclusive s e = do
354358 let go = do
355359 Just x <- nodeGetPreviousSibling e -- This can't be Nothing because we should hit 's' first
356360 _ <- nodeRemoveChild currentParent $ Just x
357− when (unNode (toNode s) /= unNode (toNode x)) go
361+ when (toNode s /= toNode x) go
358362 go
359363 _ <- nodeRemoveChild currentParent $ Just e
360364 return ()
@@ -378,10 +382,20 @@ wrapDomEventMaybe element elementOnevent getValue = do
378382 {-# SCC "e" #-} unsubscribe
379383 return $! {-# SCC "f" #-} e
380384
385+getKeyEvent :: EventM UIEvent e Int
386+getKeyEvent = do
387+ e <- event
388+ liftIO $ do
389+ which <- uiEventGetWhich e
390+ if which /= 0 then return which else do
391+ charCode <- uiEventGetCharCode e
392+ if charCode /= 0 then return charCode else
393+ uiEventGetKeyCode e
394+
381395wrapElement :: (Functor (Event t), MonadIO m, MonadSample t m, MonadReflexCreateTrigger t m, Reflex t, HasPostGui t h m) => HTMLElement -> m (El t)
382396wrapElement e = do
383397 clicked <- wrapDomEvent e elementOnclick (return ())
384− keypress <- wrapDomEvent e elementOnkeypress $ liftIO . uiEventGetKeyCode =<< event
398+ keypress <- wrapDomEvent e elementOnkeypress getKeyEvent
385399 scrolled <- wrapDomEvent e elementOnscroll $ liftIO $ elementGetScrollTop e
386400 return $ El e clicked keypress scrolled
387401
@@ -480,11 +494,15 @@ button s = do
480494
481495newtype Workflow t m a = Workflow { unWorkflow :: m (a, Event t (Workflow t m a)) }
482496
497+workflow :: forall t m a. MonadWidget t m => Workflow t m a -> m (Dynamic t a)
498+workflow w0 = do
499+ rec eResult <- widgetHold (unWorkflow w0) $ fmap unWorkflow $ switch $ fmap snd $ current eResult
500+ mapDyn fst eResult
501+
483502workflowView :: forall t m a. MonadWidget t m => Workflow t m a -> m (Event t a)
484503workflowView w0 = do
485504 rec eResult <- dyn =<< mapDyn unWorkflow =<< holdDyn w0 eReplace
486505 eReplace <- liftM switch $ hold never $ fmap snd eResult
487− eResult `seq` eReplace `seq` return ()
488506 return $ fmap fst eResult
489507
490508divClass :: forall t m a. MonadWidget t m => String -> m a -> m a
src/Reflex/Dom/Widget/Input.hs+149−198
@@ -1,5 +1,5 @@
11{-# LANGUAGE ConstraintKinds, TypeFamilies, FlexibleContexts, DataKinds, GADTs, ScopedTypeVariables, FlexibleInstances, RecursiveDo, TemplateHaskell #-}
2−module Reflex.Dom.Widget.Input where
2+module Reflex.Dom.Widget.Input (module Reflex.Dom.Widget.Input, def, (&), (.~)) where
33
44import Prelude
55
@@ -23,11 +23,32 @@ import Data.Default
2323import Data.Maybe
2424import Safe
2525import Data.Dependent.Sum (DSum (..))
26−import Data.List.NonEmpty (NonEmpty (..))
27−import Data.Foldable
2826
29−input' :: MonadWidget t m => String -> String -> Event t String -> Dynamic t (Map String String) -> m (TextInput t)
30−input' inputType initial eSetValue dAttrs = do
27+data TextInput t
28+ = TextInput { _textInput_value :: Dynamic t String
29+ , _textInput_keypress :: Event t Int
30+ , _textInput_keydown :: Event t Int
31+ , _textInput_keyup :: Event t Int
32+ , _textInput_hasFocus :: Dynamic t Bool
33+ , _textInput_element :: HTMLInputElement
34+ }
35+
36+data TextInputConfig t
37+ = TextInputConfig { _textInputConfig_inputType :: String
38+ , _textInputConfig_initialValue :: String
39+ , _textInputConfig_setValue :: Event t String
40+ , _textInputConfig_attributes :: Dynamic t (Map String String)
41+ }
42+
43+instance Reflex t => Default (TextInputConfig t) where
44+ def = TextInputConfig { _textInputConfig_inputType = "text"
45+ , _textInputConfig_initialValue = ""
46+ , _textInputConfig_setValue = never
47+ , _textInputConfig_attributes = constDyn mempty
48+ }
49+
50+textInput :: MonadWidget t m => TextInputConfig t -> m (TextInput t)
51+textInput (TextInputConfig inputType initial eSetValue dAttrs) = do
3152 e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" inputType) dAttrs
3253 liftIO $ htmlInputElementSetValue e initial
3354 performEvent_ $ fmap (liftIO . htmlInputElementSetValue e) eSetValue
@@ -41,82 +62,20 @@ input' inputType initial eSetValue dAttrs = do
4162 postGui $ runWithActions [eChangeFocusTrigger :=> True]
4263 return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
4364 dFocus <- holdDyn False eChangeFocus
44− eKeypress <- wrapDomEvent e elementOnkeypress $ liftIO . uiEventGetKeyCode =<< event
45− eKeydown <- wrapDomEvent e elementOnkeydown $ liftIO . uiEventGetKeyCode =<< event
46− eKeyup <- wrapDomEvent e elementOnkeyup $ liftIO . uiEventGetKeyCode =<< event
65+ eKeypress <- wrapDomEvent e elementOnkeypress getKeyEvent
66+ eKeydown <- wrapDomEvent e elementOnkeydown getKeyEvent
67+ eKeyup <- wrapDomEvent e elementOnkeyup getKeyEvent
4768 dValue <- holdDyn initial $ leftmost [eSetValue, eChange]
4869 return $ TextInput dValue eKeypress eKeydown eKeyup dFocus e
4970
50−{-
51− dSetValue <- holdDyn "" eSetValue
52− dynText =<< combineDyn (\v a -> "Input placeholder: " <> show (inputType, v, a)) dSetValue dAttrs
53− return $ TextInput (constDyn "") never never never (constDyn False) (constDyn Nothing)
54−
55−input' inputType eSetValue dAttrs = do
56− dEffectiveAttrs <- mapDyn (Map.insert "type" inputType) dAttrs
57− let mkSelf (initialValue, initialAttrs) = do
58− doc <- askDocument
59− Just e <- liftIO $ liftM (fmap castToHTMLInputElement) $ documentCreateElement doc "input"
60− liftIO $ htmlInputElementSetValue e initialValue
61− iforM_ initialAttrs $ \attr value -> liftIO $ elementSetAttribute e attr value
62− eChange <- wrapDomEvent e elementOninput $ liftIO $ htmlInputElementGetValue e
63− runFrame <- askRunFrame
64− eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
65− unsubscribeOnblur <- liftIO $ elementOnblur e $ liftIO $ do
66− runFrame $ DMap.singleton eChangeFocusTrigger False
67− unsubscribeOnfocus <- liftIO $ elementOnfocus e $ liftIO $ do
68− runFrame $ DMap.singleton eChangeFocusTrigger True
69− return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
70− eKeypress <- wrapDomEvent e elementOnkeypress $ liftIO . uiEventGetKeyCode =<< event
71− eKeydown <- wrapDomEvent e elementOnkeydown $ liftIO . uiEventGetKeyCode =<< event
72− eKeyup <- wrapDomEvent e elementOnkeyup $ liftIO . uiEventGetKeyCode =<< event
73− return (e, eChange, eKeypress, eKeydown, eKeyup, eChangeFocus)
74− dInitialValue <- holdDyn "" eSetValue
75− dInitial <- combineDyn (,) dInitialValue dEffectiveAttrs
76− eCreated <- performEvent . fmap mkSelf . tagDyn dInitial =<< getEInit
77− dMyElement <- holdDyn Nothing $ fmap (Just . (^. _1)) eCreated
78− performEvent_ . updated =<< combineDyn (maybe (const $ return ()) $ \e attrs -> liftIO $ setElementAttributes e attrs) dMyElement dEffectiveAttrs
79− putEChildren $ fmap ((:[]) . toNode . (^. _1)) eCreated
80− performEvent_ $ fmapMaybe (fmap $ \(e,v) -> liftIO $ htmlInputElementSetValue e v) $ attachWith (\e v -> case e of
81− Nothing -> Nothing
82− Just e' -> Just (e',v)) (current dMyElement) eSetValue
83− eChange <- liftM switch $ hold never $ fmap (^. _2) eCreated
84− dFocus <- holdDyn False . switch =<< hold never (fmap (^. _6) eCreated) --TODO: Check that 'False' is always the correct starting value - i.e.: an element will always receive an 'onfocus' event, even if it gets focus immediately
85− dValue <- holdDyn "" $ leftmost [eSetValue, eChange]
86− eKeypress <- liftM switch $ hold never $ fmap (^. _3) eCreated
87− eKeydown <- liftM switch $ hold never $ fmap (^. _4) eCreated
88− eKeyup <- liftM switch $ hold never $ fmap (^. _5) eCreated
89− return $ TextInput dValue eKeypress eKeydown eKeyup dFocus dMyElement
90−-}
91−
92−class HasValue a where
93− type Value a :: *
94− value :: a -> Value a
95−
96−data TextInput t
97− = TextInput { _textInput_value :: Dynamic t String
98− , _textInput_keypress :: Event t Int
99− , _textInput_keydown :: Event t Int
100− , _textInput_keyup :: Event t Int
101− , _textInput_hasFocus :: Dynamic t Bool
102− , _textInput_element :: HTMLInputElement
103− }
104−
105−instance HasValue (TextInput t) where
106− type Value (TextInput t) = Dynamic t String
107− value = _textInput_value
108−
109−textInput :: MonadWidget t m => m (TextInput t)
110−textInput = input' "text" "" never (constDyn $ Map.empty)
111−
11271textInputGetEnter :: Reflex t => TextInput t -> Event t ()
11372textInputGetEnter i = fmapMaybe (\n -> if n == keycodeEnter then Just () else Nothing) $ _textInput_keypress i
11473
11574data TextAreaConfig t
116− = TextAreaConfig { _textAreaConfig_initialValue :: String
117− , _textAreaConfig_setValue :: Event t String
118− , _textAreaConfig_attributes :: Dynamic t (Map String String)
119− }
75+ = TextAreaConfig { _textAreaConfig_initialValue :: String
76+ , _textAreaConfig_setValue :: Event t String
77+ , _textAreaConfig_attributes :: Dynamic t (Map String String)
78+ }
12079
12180instance Reflex t => Default (TextAreaConfig t) where
12281 def = TextAreaConfig { _textAreaConfig_initialValue = ""
@@ -125,15 +84,11 @@ instance Reflex t => Default (TextAreaConfig t) where
12584 }
12685
12786data TextArea t
128− = TextArea { _textArea_value :: Dynamic t String
129− , _textArea_element :: HTMLTextAreaElement
130− , _textArea_hasFocus :: Dynamic t Bool
131− , _textArea_keypress :: Event t Int
132− }
133−
134−instance HasValue (TextArea t) where
135− type Value (TextArea t) = Dynamic t String
136− value = _textArea_value
87+ = TextArea { _textArea_value :: Dynamic t String
88+ , _textArea_element :: HTMLTextAreaElement
89+ , _textArea_hasFocus :: Dynamic t Bool
90+ , _textArea_keypress :: Event t Int
91+ }
13792
13893textArea :: MonadWidget t m => TextAreaConfig t -> m (TextArea t)
13994textArea (TextAreaConfig initial eSet attrs) = do
@@ -150,68 +105,31 @@ textArea (TextAreaConfig initial eSet attrs) = do
150105 performEvent_ $ fmap (liftIO . htmlTextAreaElementSetValue e) eSet
151106 f <- holdDyn False eChangeFocus
152107 ev <- wrapDomEvent e elementOninput $ liftIO $ htmlTextAreaElementGetValue e
153− v <- holdDyn "" $ leftmost [eSet, ev]
154− eKeypress <- wrapDomEvent e elementOnkeypress $ liftIO . uiEventGetKeyCode =<< event
108+ v <- holdDyn initial $ leftmost [eSet, ev]
109+ eKeypress <- wrapDomEvent e elementOnkeypress getKeyEvent
155110 return $ TextArea v e f eKeypress
156111
157−{-
158−type family Controller sm t a where
159− Controller Edit t a = (a, Event t a) -- Initial value and setter
160− Controller View t a = Dynamic t a -- Value (always)
161−
162−type family Output sm t a where
163− Output Edit t a = Dynamic t a -- Value (always)
164− Output View t a = Event t a -- Requested changes
165−
166−data CheckboxConfig sm t
167− = CheckboxConfig { _checkbox_input :: Controller sm t Bool
168− , _checkbox_attributes :: Attributes
169− }
170−
171−instance Reflex t => Default (CheckboxConfig Edit t) where
172− def = CheckboxConfig (False, never) mempty
173−
174−data Checkbox sm t
175− = Checkbox { _checkbox_output :: Output sm t Bool
176− }
177−
178−data StateMode = Edit | View
179−
180−--TODO: There must be a more generic way to get this witness and allow us to case on the type-level StateMode
181−data StateModeWitness (sm :: StateMode) where
182− EditWitness :: StateModeWitness Edit
183− ViewWitness :: StateModeWitness View
184−
185−class HasStateModeWitness (sm :: StateMode) where
186− stateModeWitness :: StateModeWitness sm
187−
188−instance HasStateModeWitness Edit where
189− stateModeWitness = EditWitness
190−
191−instance HasStateModeWitness View where
192− stateModeWitness = ViewWitness
193−-}
194−
195112data CheckboxConfig t
196− = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
197− , _checkboxConfig_attributes :: Map String String
198− }
113+ = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
114+ , _checkboxConfig_attributes :: Dynamic t (Map String String)
115+ }
199116
200117instance Reflex t => Default (CheckboxConfig t) where
201118 def = CheckboxConfig { _checkboxConfig_setValue = never
202− , _checkboxConfig_attributes = Map.empty
119+ , _checkboxConfig_attributes = constDyn mempty
203120 }
204121
205122data Checkbox t
206− = Checkbox { _checkbox_value :: Dynamic t Bool
207− }
123+ = Checkbox { _checkbox_value :: Dynamic t Bool
124+ }
208125
209126--TODO: Make attributes possibly dynamic
210127-- | Create an editable checkbox
211128-- Note: if the "type" or "checked" attributes are provided as attributes, they will be ignored
212129checkbox :: MonadWidget t m => Bool -> CheckboxConfig t -> m (Checkbox t)
213130checkbox checked config = do
214− e <- liftM castToHTMLInputElement $ buildEmptyElement "input" $ Map.insert "type" "checkbox" $ (if checked then Map.insert "checked" "checked" else Map.delete "checked") (_checkboxConfig_attributes config)
131+ attrs <- mapDyn (\c -> Map.insert "type" "checkbox" $ (if checked then Map.insert "checked" "checked" else Map.delete "checked") c) (_checkboxConfig_attributes config)
132+ e <- liftM castToHTMLInputElement $ buildEmptyElement "input" attrs
215133 eClick <- wrapDomEvent e elementOnclick $ liftIO $ htmlInputElementGetChecked e
216134 performEvent_ $ fmap (\v -> liftIO $ htmlInputElementSetChecked e $! v) $ _checkboxConfig_setValue config
217135 dValue <- holdDyn checked $ leftmost [_checkboxConfig_setValue config, eClick]
@@ -229,26 +147,26 @@ checkboxView dAttrs dValue = do
229147 performEvent_ $ fmap (\v -> liftIO $ htmlInputElementSetChecked e $! v) $ updated dValue
230148 return eClicked
231149
232−checkboxWithLabel :: forall t m. MonadWidget t m => String -> m (Checkbox t)
233−checkboxWithLabel l = checkboxWithLabelAttrs l Map.empty
234−
235−checkboxWithLabelAttrs :: forall t m. MonadWidget t m => String -> Map String String -> m (Checkbox t)
236−checkboxWithLabelAttrs l attrs = elAttr "label" attrs $ do
237− c <- checkbox False def
238− text $ " " <> l
239− return c
150+data Dropdown t k
151+ = Dropdown { _dropdown_value :: Dynamic t k
152+ }
240153
154+data DropdownConfig t k
155+ = DropdownConfig { _dropdownConfig_setValue :: Event t k
156+ , _dropdownConfig_attributes :: Dynamic t (Map String String)
157+ }
241158
242−data Dropdown t k
243− = Dropdown { _dropdown_value :: Dynamic t k
244− }
159+instance (Reflex t, Ord k, Show k, Read k) => Default (DropdownConfig t k) where
160+ def = DropdownConfig { _dropdownConfig_setValue = never
161+ , _dropdownConfig_attributes = constDyn mempty
162+ }
245163
246164--TODO: We should allow the user to specify an ordering instead of relying on the ordering of the Map
247165--TODO: Get rid of Show k and Read k by indexing the possible values ourselves
248166-- | Create a dropdown box
249167-- The first argument gives the initial value of the dropdown; if it is not present in the map of options provided, it will be added with an empty string as its text
250−dropdownDynAttr :: forall k t m. (MonadWidget t m, Ord k, Show k, Read k) => Dynamic t (Map String String) -> k -> Event t k -> Dynamic t (Map k String) -> m (Dropdown t k)
251−dropdownDynAttr attrs k0 setK options = do
168+dropdown :: forall k t m. (MonadWidget t m, Ord k, Show k, Read k) => k -> Dynamic t (Map k String) -> DropdownConfig t k -> m (Dropdown t k)
169+dropdown k0 options (DropdownConfig setK attrs) = do
252170 (eRaw, _) <- elDynAttr' "select" attrs $ do
253171 optionsWithDefault <- mapDyn (`Map.union` (k0 =: "")) options
254172 listWithKey optionsWithDefault $ \k v -> do
@@ -265,65 +183,15 @@ dropdownDynAttr attrs k0 setK options = do
265183 dValue <- combineDyn readKey options =<< holdDyn (Just k0) (leftmost [eChange, fmap Just setK])
266184 return $ Dropdown dValue
267185
268−dropdown :: forall k t m. (MonadWidget t m, Ord k, Show k, Read k) => k -> Dynamic t (Map k String) -> m (Dropdown t k)
269−dropdown k = dropdownDynAttr (constDyn Map.empty) k never
270−
271−--TODO: Get rid of Show k and Read k by indexing the possible values ourselves
272−dropdown' :: forall k t m. (MonadWidget t m, Eq k, Show k, Read k) => k -> NonEmpty (k, String) -> m (Dropdown t k)
273−dropdown' k0 ks = do
274− (eRaw, _) <- elAttr' "select" ("class" =: "form-control") $ do
275− forM_ ks $ \(k, name) -> do
276− elAttr "option" ("value" =: show k <> if k == k0 then "selected" =: "selected" else mempty) $ text name
277− let e = castToHTMLSelectElement $ _el_element eRaw
278− eChange <- wrapDomEvent e elementOnchange $ do
279− kStr <- liftIO $ htmlSelectElementGetValue e
280− return $ readMay kStr
281− let readKey mk = fromMaybe k0 mk
282− dValue <- mapDyn readKey =<< holdDyn (Just k0) eChange
283− return $ Dropdown dValue
284−
285−listDropdown :: (MonadWidget t m) => Dynamic t [a] -> (a -> String) -> Dynamic t (Map String String) -> String -> m (Dynamic t (Maybe a))
286−listDropdown xs f attrs defS = do
287− m <- mapDyn (Map.fromList . zip [(1::Int)..]) xs
288− sel <- liftM _dropdown_value $ dropdownDynAttr attrs 0 never =<< mapDyn ((Map.insert 0 defS) . Map.map f) m
289− combineDyn Map.lookup sel m
290−
291−--TODO Remove CSS Classes
292−searchInputResult :: forall t m a. MonadWidget t m => Dynamic t (String, a) -> m (Event t (String, a))
293−searchInputResult r = el "li" $ do
294− (li, _) <- elAttr' "a" (Map.singleton "style" "cursor: pointer;") $ dynText =<< mapDyn fst r
295− return $ tag (current r) (_el_clicked li)
296−
297−searchInput :: forall t m a k. (MonadWidget t m, Ord k) => Map k (String, a) -> Event t (Map k (String, a)) -> m (Event t String, Event t (String, a))
298−searchInput initial results = searchInput' initial results searchInputResultsList
299−
300−searchInput' :: forall t m a k. (MonadWidget t m, Ord k) => Map k (String, a) -> Event t (Map k (String, a)) -> (Dynamic t (Map k (String, a)) -> m (Event t (String, a))) -> m (Event t String, Event t (String, a))
301−searchInput' initial results listBuilder = do
302− rec input <- input' "text" "" eSetValue $ constDyn $ Map.fromList [("class", "form-control"), ("placeholder", "Search")]
303− dResults <- holdDyn initial $ leftmost [eClearResults, results]
304− eMadeChoice <- listBuilder dResults
305− let eSetValue = fmap fst eMadeChoice
306− eSelectionMade = fmap (const Nothing) eSetValue
307− eInputChanged = fmapMaybe id $ leftmost [eSelectionMade, fmap Just (updated $ _textInput_value input)]
308− eInputEmpty = fmapMaybe id $ fmap (\i -> if i == "" then Just Map.empty else Nothing) eInputChanged
309− eClearResults = leftmost [eInputEmpty, fmap (const Map.empty) eMadeChoice]
310− return (eInputChanged, eMadeChoice)
311−
312−searchInputResultsList :: forall t m a k. (MonadWidget t m, Ord k) => Dynamic t (Map k (String, a)) -> m (Event t (String, a))
313−searchInputResultsList results = searchInputResultsList' results (flip list searchInputResult)
314−
315−searchInputResultsList' :: forall t m a k. (MonadWidget t m, Ord k) => Dynamic t (Map k (String, a)) -> (Dynamic t (Map k (String, a)) -> m (Dynamic t (Map k (Event t (String, a))))) -> m (Event t (String, a))
316−searchInputResultsList' results builder = do
317− let hideDropdown = Map.fromList [("class", "dropdown-menu"), ("style", "display: none;")]
318− showDropdown = Map.fromList [("class", "dropdown-menu"), ("style", "display: block;")]
319− attrs <- mapDyn (\rs -> if Map.null rs then hideDropdown else showDropdown) results
320− resultsList <- elDynAttr "ul" attrs $ builder results
321− liftM switch $ hold never $ fmap (leftmost . Map.elems) (updated resultsList)
322−
323186liftM concat $ mapM makeLenses
324187 [ ''TextAreaConfig
325188 , ''TextArea
189+ , ''TextInputConfig
326190 , ''TextInput
191+ , ''DropdownConfig
192+ , ''Dropdown
193+ , ''CheckboxConfig
194+ , ''Checkbox
327195 ]
328196
329197class HasAttributes a where
@@ -334,6 +202,18 @@ instance HasAttributes (TextAreaConfig t) where
334202 type Attrs (TextAreaConfig t) = Dynamic t (Map String String)
335203 attributes = textAreaConfig_attributes
336204
205+instance HasAttributes (TextInputConfig t) where
206+ type Attrs (TextInputConfig t) = Dynamic t (Map String String)
207+ attributes = textInputConfig_attributes
208+
209+instance HasAttributes (DropdownConfig t k) where
210+ type Attrs (DropdownConfig t k) = Dynamic t (Map String String)
211+ attributes = dropdownConfig_attributes
212+
213+instance HasAttributes (CheckboxConfig t) where
214+ type Attrs (CheckboxConfig t) = Dynamic t (Map String String)
215+ attributes = checkboxConfig_attributes
216+
337217class HasSetValue a where
338218 type SetValue a :: *
339219 setValue :: Lens' a (SetValue a)
@@ -341,3 +221,74 @@ class HasSetValue a where
341221instance HasSetValue (TextAreaConfig t) where
342222 type SetValue (TextAreaConfig t) = Event t String
343223 setValue = textAreaConfig_setValue
224+
225+instance HasSetValue (TextInputConfig t) where
226+ type SetValue (TextInputConfig t) = Event t String
227+ setValue = textInputConfig_setValue
228+
229+instance HasSetValue (DropdownConfig t k) where
230+ type SetValue (DropdownConfig t k) = Event t k
231+ setValue = dropdownConfig_setValue
232+
233+instance HasSetValue (CheckboxConfig t) where
234+ type SetValue (CheckboxConfig t) = Event t Bool
235+ setValue = checkboxConfig_setValue
236+
237+class HasValue a where
238+ type Value a :: *
239+ value :: a -> Value a
240+
241+instance HasValue (TextArea t) where
242+ type Value (TextArea t) = Dynamic t String
243+ value = _textArea_value
244+
245+instance HasValue (TextInput t) where
246+ type Value (TextInput t) = Dynamic t String
247+ value = _textInput_value
248+
249+instance HasValue (Dropdown t k) where
250+ type Value (Dropdown t k) = Dynamic t k
251+ value = _dropdown_value
252+
253+instance HasValue (Checkbox t) where
254+ type Value (Checkbox t) = Dynamic t Bool
255+ value = _checkbox_value
256+
257+{-
258+type family Controller sm t a where
259+ Controller Edit t a = (a, Event t a) -- Initial value and setter
260+ Controller View t a = Dynamic t a -- Value (always)
261+
262+type family Output sm t a where
263+ Output Edit t a = Dynamic t a -- Value (always)
264+ Output View t a = Event t a -- Requested changes
265+
266+data CheckboxConfig sm t
267+ = CheckboxConfig { _checkbox_input :: Controller sm t Bool
268+ , _checkbox_attributes :: Attributes
269+ }
270+
271+instance Reflex t => Default (CheckboxConfig Edit t) where
272+ def = CheckboxConfig (False, never) mempty
273+
274+data Checkbox sm t
275+ = Checkbox { _checkbox_output :: Output sm t Bool
276+ }
277+
278+data StateMode = Edit | View
279+
280+--TODO: There must be a more generic way to get this witness and allow us to case on the type-level StateMode
281+data StateModeWitness (sm :: StateMode) where
282+ EditWitness :: StateModeWitness Edit
283+ ViewWitness :: StateModeWitness View
284+
285+class HasStateModeWitness (sm :: StateMode) where
286+ stateModeWitness :: StateModeWitness sm
287+
288+instance HasStateModeWitness Edit where
289+ stateModeWitness = EditWitness
290+
291+instance HasStateModeWitness View where
292+ stateModeWitness = ViewWitness
293+-}
294+
src/Reflex/Dom/Widget/Lazy.hsnew file+67
@@ -0,0 +1,67 @@
1+{-# LANGUAGE RecursiveDo, ScopedTypeVariables #-}
2+
3+module Reflex.Dom.Widget.Lazy where
4+
5+import Reflex
6+import Reflex.Dom.Class
7+import Reflex.Dom.Widget.Basic
8+
9+import Control.Monad.IO.Class
10+import Data.Fixed
11+import Data.Monoid
12+import qualified Data.Map as Map
13+import Data.Map (Map)
14+import GHCJS.DOM.Element
15+
16+-- |A list view for long lists. Creates a scrollable element and only renders child row elements near the current scroll position.
17+virtualListWithSelection :: forall t m k v. (MonadWidget t m, Ord k)
18+ => Int -- ^ The height of the visible region in pixels
19+ -> Int -- ^ The height of each row in pixels
20+ -> Dynamic t Int -- ^ The total number of items
21+ -> Int -- ^ The index of the row to scroll to on initialization
22+ -> Event t Int -- ^ An 'Event' containing a row index. Used to scroll to the given index.
23+ -> String -- ^ The element tag for the list
24+ -> Dynamic t (Map String String) -- ^ The attributes of the list
25+ -> String -- ^ The element tag for a row
26+ -> Dynamic t (Map String String) -- ^ The attributes of each row
27+ -> (k -> Dynamic t v -> m ()) -- ^ The row child element builder
28+ -> Dynamic t (Map k v) -- ^ The 'Map' of items
29+ -> m (Dynamic t (Int, Int), Event t k) -- ^ A tuple containing: a 'Dynamic' of the index (based on the current scroll position) and number of items currently being rendered, and an 'Event' of the selected key
30+virtualListWithSelection heightPx rowPx maxIndex i0 setI listTag listAttrs rowTag rowAttrs itemBuilder items = do
31+ totalHeightStyle <- mapDyn (toHeightStyle . (*) rowPx) maxIndex
32+ rec (container, itemList) <- elAttr "div" outerStyle $ elAttr' "div" containerStyle $ do
33+ currentTop <- mapDyn (listWrapperStyle . fst) window
34+ (_, lis) <- elDynAttr "div" totalHeightStyle $ tagWrapper listTag listAttrs currentTop $ listWithKey itemsInWindow $ \k v -> do
35+ (li,_) <- tagWrapper rowTag rowAttrs (constDyn $ toHeightStyle rowPx) $ itemBuilder k v
36+ return $ fmap (const k) (_el_clicked li)
37+ return lis
38+ scrollPosition <- holdDyn 0 $ _el_scrolled container
39+ window <- mapDyn (findWindow heightPx rowPx) scrollPosition
40+ itemsInWindow <- combineDyn (\(_,(idx,num)) is -> Map.fromList $ take num $ drop idx $ Map.toList is) window items
41+ postBuild <- getPostBuild
42+ performEvent_ $ fmap (\i -> liftIO $ elementSetScrollTop (_el_element container) (i * rowPx)) $ leftmost [setI, fmap (const i0) postBuild]
43+ indexAndLength <- mapDyn snd window
44+ sel <- mapDyn (leftmost . Map.elems) itemList
45+ return (indexAndLength, switch $ current sel)
46+ where
47+ toStyleAttr m = "style" =: (Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m)
48+ outerStyle = toStyleAttr $ "position" =: "relative" <>
49+ "height" =: (show heightPx <> "px")
50+ containerStyle = toStyleAttr $ "overflow" =: "auto" <>
51+ "position" =: "absolute" <>
52+ "left" =: "0" <>
53+ "right" =: "0" <>
54+ "height" =: (show heightPx <> "px")
55+ listWrapperStyle t = toStyleAttr $ "position" =: "relative" <>
56+ "top" =: (show t <> "px")
57+ toHeightStyle h = toStyleAttr ("height" =: (show h <> "px"))
58+ tagWrapper elTag attrs attrsOverride c = do
59+ attrs' <- combineDyn Map.union attrsOverride attrs
60+ elDynAttr' elTag attrs' c
61+ findWindow windowSize sizeIncrement startingPosition =
62+ let (startingIndex, topOffsetPx) = startingPosition `divMod'` sizeIncrement
63+ topPx = startingPosition - topOffsetPx
64+ numItems = windowSize `div` sizeIncrement + 1
65+ preItems = min startingIndex numItems
66+ in (topPx - preItems * sizeIncrement, (startingIndex - preItems, preItems + numItems * 2))
67+
src/Reflex/Dom/Xhr.hs+53−43
@@ -1,10 +1,23 @@
1−{-# LANGUAGE CPP #-}
2−module Reflex.Dom.Xhr where
1+module Reflex.Dom.Xhr
2+ ( module Reflex.Dom.Xhr
3+ , XMLHttpRequest
4+ , responseTextToText
5+ , xmlHttpRequestGetReadyState
6+ , xmlHttpRequestGetResponseText
7+ , xmlHttpRequestGetStatus
8+ , xmlHttpRequestGetStatusText
9+ , xmlHttpRequestNew
10+ , xmlHttpRequestOnreadystatechange
11+ , xmlHttpRequestOpen
12+ , xmlHttpRequestSend
13+ , xmlHttpRequestSetRequestHeader
14+ , xmlHttpRequestSetResponseType
15+ )
16+where
317
4−#ifdef __GHCJS__
518import Control.Concurrent
619import Control.Lens
7−import Control.Monad
20+import Control.Monad hiding (forM)
821import Control.Monad.IO.Class
922import Data.Aeson
1023import qualified Data.ByteString.Lazy as BL
@@ -14,11 +27,10 @@ import qualified Data.Map as Map
1427import Data.Maybe
1528import Data.Text (Text)
1629import Data.Text.Encoding
17−import GHCJS.Foreign
18−import GHCJS.Types
19−import GHCJS.DOM.XMLHttpRequest
30+import Data.Traversable
2031import Reflex
2132import Reflex.Dom.Class
33+import Reflex.Dom.Xhr.Foreign
2234
2335data XhrRequest
2436 = XhrRequest { _xhrRequest_method :: String
@@ -35,7 +47,7 @@ data XhrRequestConfig
3547 }
3648
3749data XhrResponse
38− = XhrResponse { _xhrResponse_body :: Maybe JSString
50+ = XhrResponse { _xhrResponse_body :: Maybe Text
3951 }
4052
4153instance Default XhrRequestConfig where
@@ -49,43 +61,45 @@ instance Default XhrRequestConfig where
4961xhrRequest :: String -> String -> XhrRequestConfig -> XhrRequest
5062xhrRequest = XhrRequest
5163
52−newXMLHttpRequest :: XhrRequest -> (XhrResponse -> IO a) -> IO XMLHttpRequest
64+newXMLHttpRequest :: (HasWebView m, MonadIO m, HasPostGui t h m) => XhrRequest -> (XhrResponse -> h ()) -> m XMLHttpRequest
5365newXMLHttpRequest req cb = do
54− xhr <- xmlHttpRequestNew
55− let c = _xhrRequest_config req
56− xmlHttpRequestOpen
57− xhr
58− (_xhrRequest_method req)
59− (_xhrRequest_url req)
60− True
61− (fromMaybe "" $ _xhrRequestConfig_user c)
62− (fromMaybe "" $ _xhrRequestConfig_password c)
63− iforM_ (_xhrRequestConfig_headers c) $ xmlHttpRequestSetRequestHeader xhr
64− maybe (return ()) (xmlHttpRequestSetResponseType xhr . toJSString) (_xhrRequestConfig_responseType c)
65− _ <- xmlHttpRequestOnreadystatechange xhr $ do
66− readyState <- liftIO $ xmlHttpRequestGetReadyState xhr
67− if readyState == 4
68− then do
69− r <- liftIO $ xmlHttpRequestGetResponseText xhr
70− _ <- liftIO $ cb $ XhrResponse r
71− return ()
72− else return ()
73− _ <- xmlHttpRequestSend xhr (_xhrRequestConfig_sendData c)
74− return xhr
66+ wv <- askWebView
67+ postGui <- askPostGui
68+ liftIO $ do
69+ xhr <- xmlHttpRequestNew wv
70+ let c = _xhrRequest_config req
71+ xmlHttpRequestOpen
72+ xhr
73+ (_xhrRequest_method req)
74+ (_xhrRequest_url req)
75+ True
76+ (fromMaybe "" $ _xhrRequestConfig_user c)
77+ (fromMaybe "" $ _xhrRequestConfig_password c)
78+ iforM_ (_xhrRequestConfig_headers c) $ xmlHttpRequestSetRequestHeader xhr
79+ maybe (return ()) (xmlHttpRequestSetResponseType xhr . toResponseType) (_xhrRequestConfig_responseType c)
80+ _ <- xmlHttpRequestOnreadystatechange xhr $ do
81+ readyState <- liftIO $ xmlHttpRequestGetReadyState xhr
82+ if readyState == 4
83+ then do
84+ r <- liftIO $ xmlHttpRequestGetResponseText xhr
85+ _ <- liftIO $ postGui $ cb $ XhrResponse $ responseTextToText r
86+ return ()
87+ else return ()
88+ _ <- xmlHttpRequestSend xhr (_xhrRequestConfig_sendData c)
89+ return xhr
7590
76−performRequestAsync :: MonadWidget t m => Event t XhrRequest -> m (Event t XhrResponse)
91+performRequestAsync :: (MonadWidget t m) => Event t XhrRequest -> m (Event t XhrResponse)
7792performRequestAsync req = performEventAsync $ ffor req $ \r cb -> do
78− _ <- liftIO $ newXMLHttpRequest r cb
93+ _ <- newXMLHttpRequest r $ liftIO . cb
7994 return ()
8095
8196performRequestsAsync :: (Traversable f, MonadWidget t m) => Event t (f XhrRequest) -> m (Event t (f XhrResponse))
8297performRequestsAsync req = performEventAsync $ ffor req $ \rs cb -> do
83− _ <- liftIO $ do
84− resps <- forM rs $ \r -> do
85− resp <- newEmptyMVar
86− _ <- newXMLHttpRequest r $ putMVar resp
87− return resp
88− forkIO $ cb =<< forM resps takeMVar
98+ resps <- forM rs $ \r -> do
99+ resp <- liftIO newEmptyMVar
100+ _ <- newXMLHttpRequest r $ liftIO . putMVar resp
101+ return resp
102+ _ <- liftIO $ forkIO $ cb =<< forM resps takeMVar
89103 return ()
90104
91105getAndDecode :: (FromJSON a, MonadWidget t m) => Event t String -> m (Event t (Maybe a))
@@ -101,10 +115,6 @@ getMay f e = do
101115decodeText :: FromJSON a => Text -> Maybe a
102116decodeText = decode . BL.fromStrict . encodeUtf8
103117
104−decodeJSString :: FromJSON a => JSString -> Maybe a
105−decodeJSString = decodeText . fromJSString
106−
107118decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
108−decodeXhrResponse = join . fmap decodeJSString . _xhrResponse_body
119+decodeXhrResponse = join . fmap decodeText . _xhrResponse_body
109120
110−#endif