on develop help sign in
Merge branch 'develop' into haddomevent-fundep
baeaa7fJohn Ericson
CONTRIBUTING.mdnew file+65
@@ -0,0 +1,65 @@
1+# Contribution Guide
2+
3+Contributions and issue reports are encouraged and appreciated!
4+
5+- [Opening Issues](#opening-issues)
6+- [Submitting Changes](#submitting-changes)
7+ - [Guidelines for Commit Messages](#guidelines-for-commit-messages)
8+ - [Guidelines for Pull Requests](#guidelines-for-pull-requests)
9+ - [Code Quality](#code-quality)
10+ - [Documentation](#documentation)
11+
12+## Opening Issues
13+
14+Before opening an issue, please check whether your issue has already been reported. Assuming it has not:
15+
16+* Describe the issue you're encountering or the suggestion you're making
17+* Include any relevant steps to reproduce or code samples you can. It's always easier for us to debug if we have something that demonstrates the error.
18+* Let us know what version of this project you were using. If you're using a github checkout, provide the git hash.
19+* Describe how you're building this project (i.e., via reflex-platform, cabal install, stack, obelisk, etc.). If you're using reflex-platform or obelisk, provide the git hash of your checkout.
20+
21+## Submitting Changes
22+
23+### Guidelines for Commit Messages
24+
25+#### Summary Line
26+The summary line of your commit message should summarize the changes being made. Commit messages should be written in the imperative mood and should describe what happens when the commit is applied.
27+
28+One way to think about it is that your commit message should be able to complete the sentence:
29+"When applied, this commit will..."
30+
31+#### Body
32+For breaking changes, new features, refactors, or other major changes, the body of the commit message should describe the motivation behind the change in greater detail and may include references to the issue tracker. The body shouldn't repeat code/comments from the diff.
33+
34+### Guidelines for Pull Requests
35+
36+Wherever possible, pull requests should add a single feature or fix a single bug. Pull requests should not bundle several unrelated changes.
37+
38+### Code Quality
39+
40+#### Warnings
41+
42+Your pull request should add no new warnings to the project. It should also generally not disable any warnings.
43+
44+#### Build and Test
45+
46+Make sure the project builds and that the tests pass! This will generally also be checked by CI before merge, but trying it yourself first means you'll catch problems earlier and your contribution can be merged that much sooner!
47+
48+### Documentation
49+
50+#### In the code
51+We're always striving to improve documentation. Please include [haddock](https://haskell-haddock.readthedocs.io/en/latest/index.html) documentation for any added code, and update the documentation for any code you modify.
52+
53+#### In the Changelog
54+Add an entry to the changelog when your PR:
55+* Adds a feature
56+* Deprecates something
57+* Includes a breaking change
58+* Makes any other change that will impact users
59+
60+You may need to add changelog entries to one or both of the changelogs in this repository:
61+* [reflex-dom-core's changelog](reflex-dom-core/ChangeLog.md)
62+* [reflex-dom's changelog](reflex-dom/ChangeLog.md)
63+
64+#### In the [Readme](README.md)
65+The readme is the first place a lot of people look for information about the repository. Update any parts of the readme that are affected by your PR.
FAQ.mdnew file+31
@@ -0,0 +1,31 @@
1+# Frequently Asked Questions
2+
3+### How do I configure DOM events with flags like `preventDefault` and `stopPropagation`?
4+
5+Using `element`, a low-level DOM builder function. Its second argument, `ElementConfig`, has a `_elementConfig_eventSpec` field that can be modified using `addEventSpecFlags`. It takes a `Proxy` that specifies the `DomSpace`, an event name, and a callback which takes the event result and returns an event flag, which can enable behavior like `preventDefault` and `stopPropagation`.
6+
7+The type signature on `def` is needed because the fields of the `ElementConfig` can change the type of the `ElementConfig`. GHC can probably figure out the type of the resulting `ElementConfig`, but it doesn't know what the type of `def` was before the modifications, since it never gets used with that configuration.
8+
9+For example:
10+
11+```haskell
12+{-# LANGUAGE OverloadedStrings #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+
15+import Control.Lens ((&), (%~), (.~))
16+import Data.Proxy
17+import Reflex.Dom
18+
19+main :: IO ()
20+main = mainWidget $ do
21+ (click, ()) <- linkPreventDefault $ text "reflex-frp"
22+ display =<< count click
23+
24+linkPreventDefault :: forall t m a. DomBuilder t m => m a -> m (Event t (), a)
25+linkPreventDefault c = do
26+ let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
27+ & elementConfig_initialAttributes .~ ("href" =: "https://reflex-frp.org")
28+ & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (const preventDefault)
29+ (link, a) <- element "a" cfg c
30+ return (domEvent Click link, a)
31+```
Quickref.md+1−1
@@ -28,7 +28,7 @@ Widgets may return any type (this is 'a' in many of the functions below). Often
28-- This version returns the 'El' as well.
28-- This version returns the 'El' as well.
29[W] el' :: Text -> m a -> m (El, a)
29[W] el' :: Text -> m a -> m (El, a)
30
30
31−-- These two additionally apply attributes to the element, such as ("class" =: "blah")
31+-- These two additionally apply attributes to the element, such as ("class" =: "blah"). (=:) is 'Map.singleton'
32[W] elAttr :: Text -> Map Text Text -> m a -> m a
32[W] elAttr :: Text -> Map Text Text -> m a -> m a
33[W] elAttr' :: Text -> Map Text Text -> m a -> m (El, a)
33[W] elAttr' :: Text -> Map Text Text -> m a -> m (El, a)
34
34
README.md+11−5
@@ -1,17 +1,23 @@
1−## Reflex-DOM
1+## [Reflex-DOM](https://reflex-frp.org)
2+
3+[![Hackage](https://img.shields.io/hackage/v/reflex-dom.svg)](http://hackage.haskell.org/package/reflex-dom)
4+
2### A Framework for Functional Reactive Web Apps in Haskell
5### A Framework for Functional Reactive Web Apps in Haskell
3
6
4Reflex-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/reflex-frp/reflex).
7Reflex-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/reflex-frp/reflex).
5
8
6−Comprehensive documentation is still a work in progress, but a [demo](https://github.com/reflex-frp/reflex-platform) 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/)).
7−
8A summary of Reflex-DOM functions is available in the [quick reference](Quickref.md).
9A summary of Reflex-DOM functions is available in the [quick reference](Quickref.md).
9
10
11+**Visit https://reflex-frp.org/ for more information, tutorials, documentation and [examples](https://examples.reflex-frp.org/).**
12+
10### Additional resources
13### Additional resources
11[Get started with Reflex](https://github.com/reflex-frp/reflex-platform)
14[Get started with Reflex](https://github.com/reflex-frp/reflex-platform)
12
15
13[/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
16[/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
14
17
15−[hackage](https://hackage.haskell.org/package/reflex)
16−
17[irc.freenode.net #reflex-frp](http://webchat.freenode.net?channels=%23reflex-frp&uio=d4)
18[irc.freenode.net #reflex-frp](http://webchat.freenode.net?channels=%23reflex-frp&uio=d4)
19+
20+### Hacking
21+
22+Use the `./scripts/hack-on haskell-overlays/reflex-packages/dep/reflex-dom` script in [Reflex Platform](https://github.com/reflex-frp/reflex-platform) to checkout the source code of `reflex-dom` locally in `./dep/reflex-dom` directory.
23+Then do modifications to the source in place, and use the `./try-reflex` or `./scripts/work-on` scripts to create the shell to test your changes.
chrome-test-utils/LICENSEnew file+12
@@ -0,0 +1,12 @@
1+Copyright (c) 2019, 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.
chrome-test-utils/chrome-test-utils.cabalnew file+30
@@ -0,0 +1,30 @@
1+Name: chrome-test-utils
2+Version: 0.4
3+Synopsis: Utilities to write headless Chromium tests
4+License: BSD3
5+License-file: LICENSE
6+Author: Obsidian Systems LLC
7+Maintainer: maintainer@obsidian.systems
8+Stability: Experimental
9+Build-type: Simple
10+Cabal-version: >=1.9.2
11+
12+library
13+ hs-source-dirs: src
14+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
15+ build-depends: base
16+ , linux-namespaces
17+ , process
18+ , temporary
19+ , text
20+ , unix
21+ exposed-modules:
22+ Test.Util.UnshareNetwork
23+ Test.Util.ChromeFlags
24+ if !os(linux) || !arch(x86_64)
25+ buildable: False
26+
27+source-repository head
28+ type: git
29+ location: https://github.com/reflex-frp/reflex-dom
30+ subdir: chrome-test-utils
chrome-test-utils/src/Test/Util/ChromeFlags.hsnew file+17
@@ -0,0 +1,17 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Test.Util.ChromeFlags where
3+
4+import Data.Semigroup
5+import Data.Text
6+import System.IO.Temp
7+
8+withSandboxedChromeFlags :: Bool -> ([Text] -> IO a) -> IO a
9+withSandboxedChromeFlags headless action =
10+ withSystemTempDirectory "reflex-dom-core_test" $ \tmp ->
11+ action
12+ [ if headless then "--headless" else "--auto-open-devtools-for-tabs"
13+ , "--disable-gpu"
14+ , "--no-sandbox"
15+ , "--remote-debugging-port=9222"
16+ , "--user-data-dir=" <> pack tmp
17+ ]
chrome-test-utils/src/Test/Util/UnshareNetwork.hsnew file+12
@@ -0,0 +1,12 @@
1+module Test.Util.UnshareNetwork where
2+
3+import System.Linux.Namespaces
4+import System.Posix
5+import System.Process
6+
7+unshareNetork :: IO ()
8+unshareNetork = do
9+ uid <- getEffectiveUserID
10+ unshare [User, Network]
11+ writeUserMappings Nothing [UserMapping 0 uid 1]
12+ callCommand "ip link set lo up ; ip addr"
default.nixdeleted−6
@@ -1,6 +0,0 @@
1−haskellPackages: nixpkgs: {
2− reflex-dom = haskellPackages.callPackage ./reflex-dom {};
3− reflex-dom-core = haskellPackages.callPackage ./reflex-dom-core {
4− inherit (nixpkgs) iproute chromium;
5− };
6−}
dep/reflex-platform/default.nixnew file+7
@@ -0,0 +1,7 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import ((import <nixpkgs> {}).fetchFromGitHub (
3+ let json = builtins.fromJSON (builtins.readFile ./github.json);
4+ in { inherit (json) owner repo rev sha256;
5+ private = json.private or false;
6+ }
7+))
dep/reflex-platform/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "reflex-frp",
3+ "repo": "reflex-platform",
4+ "branch": "develop",
5+ "rev": "e7b76dd552a10916c7d8702c11292dac4f4299ea",
6+ "sha256": "0s1183arrwldcs50qhzgnv94v24n9bgq6dfq64wp0a3q2nzyvgwh"
7+}
reflex-dom-core/ChangeLog.mdnew file+42
@@ -0,0 +1,42 @@
1+# Revision history for reflex-dom-core
2+
3+## Unreleased
4+
5+* Deprecate a number of old inflexible widget helpers in `Reflex.Dom.Widget.Basic`:
6+
7+ * `Link`
8+ * `button`
9+ * `dtdd`
10+ * `linkClass`
11+ * `link`
12+ * `tabDisplay`
13+ * `tableDynAttr`
14+
15+ And in `Reflex.Dom.Widget.Input`:
16+
17+ * `TextInput`
18+ * `TextAreaConfig`
19+ * `CheckboxConfig`
20+ * `FileInput`
21+
22+## 0.5.2
23+
24+* Update to use new dependent-sum/map packages and drop dependency on `*Tag` classes (e.g., `ShowTag`).
25+* Update version bounds of base, containers, and stm
26+* Update to use the newly split `these`/`semialign` packages. To use the pre-split `these` package, set the `split-these` flag to false.
27+* Reintroduce "data-ssr": elements without this attribute are skipped during
28+ hydration.
29+* Fix an issue in the hydration tests that prevented the test from finding the chromium executable
30+* Relax constraints on `dyn` and `widgetHold` to match the ones in `networkView` and `networkHold` respectively
31+* Fix prerender for RequesterT so that it doesn't accidentally discard a request that is made at the same moment as getPostBuild's Event fires
32+
33+## 0.5.1
34+
35+* Added support for marking elements with a "data-skip-hydration" attribute, which will cause hydration to ignore and skip over them.
36+* Removed "data-ssr" attributes from statically rendered output.
37+
38+## 0.5
39+
40+* Add HydrationDomBuilderT to support hydration of statically rendered DOM nodes. See the note at the top of Reflex.Dom.Builder.Immediate.
41+* As a result of the hydration changes, the Prerender class has changed, the type of `prerender` has changed and it is now a class method.
42+* Add the Reflex.Dom.Xhr.FormData module to make posting formdata over xhr more convenient.
reflex-dom-core/default.nixdeleted−43
@@ -1,43 +0,0 @@
1−{ mkDerivation, ghc, aeson, base, bifunctors, bimap, blaze-builder
2−, bytestring, constraints, containers, contravariant, data-default
3−, dependent-map, dependent-sum, dependent-sum-template, directory
4−, exception-transformers, ghcjs-dom, hlint, jsaddle, keycode, lens
5−, monad-control, mtl, primitive, ref-tf, reflex, semigroups, stdenv
6−, stm, template-haskell, temporary, text, these, transformers
7−, unix, zenc, hashable, chromium, process, jsaddle-warp
8−, linux-namespaces, iproute, network-uri
9−}:
10−let addGcTestDepends = drv: if stdenv.system != "x86_64-linux" then drv else drv // {
11− testHaskellDepends = (drv.testHaskellDepends or []) ++ [ temporary jsaddle-warp process linux-namespaces ];
12− testSystemDepends = (drv.testSystemDepends or []) ++ [ chromium iproute ];
13− };
14−in mkDerivation (addGcTestDepends {
15− pname = "reflex-dom-core";
16− version = "0.4";
17− src = ./.;
18− libraryHaskellDepends = [
19− aeson base bifunctors bimap blaze-builder bytestring constraints
20− containers contravariant data-default dependent-map dependent-sum
21− dependent-sum-template directory exception-transformers ghcjs-dom
22− jsaddle keycode lens monad-control mtl primitive ref-tf
23− reflex semigroups stm template-haskell text these transformers
24− unix zenc network-uri
25− ] ++ (if ghc.isGhcjs or false then [
26− hashable
27− ] else []);
28−
29− #TODO: Get hlint working for cross-compilation
30− doCheck = stdenv.hostPlatform == stdenv.buildPlatform && !(ghc.isGhcjs or false);
31−
32− # The headless browser run as part of the tests will exit without this
33− preBuild = ''
34− export HOME="$PWD"
35− '';
36−
37− # Show some output while running tests, so we might notice what's wrong
38− testTarget = "--show-details=streaming";
39−
40− testHaskellDepends = [ base hlint ];
41− description = "Functional Reactive Web Apps with Reflex";
42− license = stdenv.lib.licenses.bsd3;
43−})
reflex-dom-core/reflex-dom-core.cabal+84−22
@@ -1,5 +1,5 @@
1Name: reflex-dom-core
1Name: reflex-dom-core
2−Version: 0.4
2+Version: 0.5.2.0
3Synopsis: Functional Reactive Web Apps with Reflex
3Synopsis: Functional Reactive Web Apps with Reflex
4Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
4Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
5License: BSD3
5License: BSD3
@@ -17,6 +17,7 @@ extra-source-files: src-ghc/Foreign/JavaScript/Internal/Utils.hs
17 src/Reflex/Dom/WebSocket/Foreign.hs
17 src/Reflex/Dom/WebSocket/Foreign.hs
18 src/Reflex/Dom/Xhr/ResponseType.hs
18 src/Reflex/Dom/Xhr/ResponseType.hs
19 src/Reflex/Dom/Xhr/Exception.hs
19 src/Reflex/Dom/Xhr/Exception.hs
20+ ChangeLog.md
20
21
21flag use-template-haskell
22flag use-template-haskell
22 description: Use template haskell to generate lenses
23 description: Use template haskell to generate lenses
@@ -38,22 +39,27 @@ flag profile-reflex
38 default: False
39 default: False
39 manual: True
40 manual: True
40
41
42+flag split-these
43+ description: Use split these/semialign packages
44+ manual: False
45+ default: True
46+
41library
47library
42 hs-source-dirs: src
48 hs-source-dirs: src
43 build-depends:
49 build-depends:
44− aeson >= 0.8 && < 1.3,
50+ aeson >= 0.8 && < 1.5,
45− base >= 4.7 && < 4.11,
51+ base >= 4.7 && < 4.13,
46 bifunctors >= 4.2 && < 6,
52 bifunctors >= 4.2 && < 6,
47 bimap >= 0.3 && < 0.4,
53 bimap >= 0.3 && < 0.4,
48 blaze-builder,
54 blaze-builder,
49 bytestring == 0.10.*,
55 bytestring == 0.10.*,
50− containers == 0.5.*,
56+ containers >= 0.6 && < 0.7,
51− constraints == 0.9.*,
57+ constraints >= 0.9 && < 0.12,
52− contravariant >= 1.4 && < 1.5,
58+ contravariant >= 1.4 && < 1.6,
53 data-default >= 0.5 && < 0.8,
59 data-default >= 0.5 && < 0.8,
54− dependent-map == 0.2.*,
60+ dependent-map >= 0.3 && < 0.4,
55− dependent-sum >= 0.3 && < 0.5,
61+ dependent-sum >= 0.6 && < 0.7,
56− dependent-sum-template >= 0.0.0.4 && < 0.1,
62+ dependent-sum-template >= 0.1 && < 0.2,
57 directory >= 1.2 && < 1.4,
63 directory >= 1.2 && < 1.4,
58 exception-transformers == 0.4.*,
64 exception-transformers == 0.4.*,
59 ghcjs-dom >= 0.9.1.0 && < 0.10,
65 ghcjs-dom >= 0.9.1.0 && < 0.10,
@@ -64,12 +70,12 @@ library
64 monad-control >= 1.0.1 && < 1.1,
70 monad-control >= 1.0.1 && < 1.1,
65 mtl >= 2.1 && < 2.3,
71 mtl >= 2.1 && < 2.3,
66 primitive >= 0.5 && < 0.7,
72 primitive >= 0.5 && < 0.7,
73+ random,
67 ref-tf == 0.4.*,
74 ref-tf == 0.4.*,
68− reflex == 0.5.*,
75+ reflex >= 0.6.2,
69 semigroups >= 0.16 && < 0.19,
76 semigroups >= 0.16 && < 0.19,
70− stm == 2.4.*,
77+ stm >= 2.4 && < 2.6,
71 text == 1.2.*,
78 text == 1.2.*,
72− these >= 0.4 && < 0.8,
73 transformers >= 0.3 && < 0.6,
79 transformers >= 0.3 && < 0.6,
74 network-uri >= 2.6.1 && < 2.7,
80 network-uri >= 2.6.1 && < 2.7,
75 zenc == 0.1.*
81 zenc == 0.1.*
@@ -84,6 +90,14 @@ library
84 if !os(windows)
90 if !os(windows)
85 build-depends: unix == 2.7.*
91 build-depends: unix == 2.7.*
86
92
93+ if flag(split-these)
94+ build-depends:
95+ semialign >= 1 && < 1.1,
96+ these >= 1 && < 1.1
97+ else
98+ build-depends:
99+ these >= 0.4 && < 0.9
100+
87 exposed-modules:
101 exposed-modules:
88 Foreign.JavaScript.TH
102 Foreign.JavaScript.TH
89 Foreign.JavaScript.Orphans
103 Foreign.JavaScript.Orphans
@@ -92,16 +106,15 @@ library
92 Reflex.Dom.Builder.Class.Events
106 Reflex.Dom.Builder.Class.Events
93 Reflex.Dom.Builder.Immediate
107 Reflex.Dom.Builder.Immediate
94 Reflex.Dom.Builder.InputDisabled
108 Reflex.Dom.Builder.InputDisabled
109+ Reflex.Dom.Builder.Hydratable
95 Reflex.Dom.Builder.Static
110 Reflex.Dom.Builder.Static
96 Reflex.Dom.Class
111 Reflex.Dom.Class
97 Reflex.Dom.Core
112 Reflex.Dom.Core
98 Reflex.Dom.Location
113 Reflex.Dom.Location
99 Reflex.Dom.Main
114 Reflex.Dom.Main
100− Reflex.Dom.Modals.Base
101 Reflex.Dom.Modals.Class
115 Reflex.Dom.Modals.Class
102 Reflex.Dom.Old
116 Reflex.Dom.Old
103 Reflex.Dom.Prerender
117 Reflex.Dom.Prerender
104− Reflex.Dom.Specializations
105 Reflex.Dom.Time
118 Reflex.Dom.Time
106 Reflex.Dom.WebSocket
119 Reflex.Dom.WebSocket
107 Reflex.Dom.WebSocket.Query
120 Reflex.Dom.WebSocket.Query
@@ -111,6 +124,7 @@ library
111 Reflex.Dom.Widget.Lazy
124 Reflex.Dom.Widget.Lazy
112 Reflex.Dom.Widget.Resize
125 Reflex.Dom.Widget.Resize
113 Reflex.Dom.Xhr
126 Reflex.Dom.Xhr
127+ Reflex.Dom.Xhr.FormData
114 other-modules:
128 other-modules:
115 Foreign.JavaScript.Internal.Utils
129 Foreign.JavaScript.Internal.Utils
116 Reflex.Dom.WebSocket.Foreign
130 Reflex.Dom.WebSocket.Foreign
@@ -118,7 +132,7 @@ library
118 Reflex.Dom.Xhr.ResponseType
132 Reflex.Dom.Xhr.ResponseType
119 Reflex.Dom.Xhr.Exception
133 Reflex.Dom.Xhr.Exception
120
134
121− ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans -fspecialise-aggressively -ddump-simpl -ddump-to-file -dsuppress-coercions -dsuppress-idinfo
135+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans -fspecialise-aggressively
122 ghc-prof-options: -fprof-auto
136 ghc-prof-options: -fprof-auto
123
137
124 if flag(expose-all-unfoldings)
138 if flag(expose-all-unfoldings)
@@ -132,8 +146,8 @@ library
132
146
133 if flag(use-template-haskell)
147 if flag(use-template-haskell)
134 build-depends:
148 build-depends:
135− dependent-sum >= 0.3 && < 0.5,
149+ dependent-sum >= 0.6,
136− dependent-sum-template >= 0.0.0.5 && < 0.1,
150+ dependent-sum-template >= 0.1 && < 0.2,
137 template-haskell
151 template-haskell
138 other-extensions: TemplateHaskell
152 other-extensions: TemplateHaskell
139 cpp-options: -DUSE_TEMPLATE_HASKELL
153 cpp-options: -DUSE_TEMPLATE_HASKELL
@@ -141,7 +155,7 @@ library
141 Reflex.Dom.Builder.Class.TH
155 Reflex.Dom.Builder.Class.TH
142 else
156 else
143 build-depends:
157 build-depends:
144− dependent-sum == 0.4.*
158+ dependent-sum == 0.6.*
145
159
146test-suite hlint
160test-suite hlint
147 build-depends: base, hlint
161 build-depends: base, hlint
@@ -149,16 +163,64 @@ test-suite hlint
149 main-is: hlint.hs
163 main-is: hlint.hs
150 type: exitcode-stdio-1.0
164 type: exitcode-stdio-1.0
151
165
152−test-suite gc
166+test-suite hydration
153 build-depends: base
167 build-depends: base
168+ , aeson
169+ , bytestring
170+ , chrome-test-utils
171+ , constraints
172+ , constraints-extras
173+ , containers
174+ , dependent-map
175+ , dependent-sum
176+ , dependent-sum-template
177+ , directory
178+ , exceptions
179+ , filepath
180+ , ghcjs-dom
181+ , hspec
182+ , hspec-core
183+ , hspec-webdriver
184+ , http-types
185+ , HUnit
186+ , jsaddle
187+ , jsaddle-warp
188+ , lens
189+ , lifted-base
190+ , network
191+ , random
192+ , ref-tf
154 , reflex
193 , reflex
155 , reflex-dom-core
194 , reflex-dom-core
195+ , process
196+ , silently
197+ , temporary
198+ , text
199+ , wai
200+ , wai-websockets
201+ , warp
202+ , webdriver
203+ , websockets
204+ , which
205+ hs-source-dirs: test
206+ ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
207+ ghc-prof-options: -fprof-auto -optP-DPROFILING
208+ main-is: hydration.hs
209+ type: exitcode-stdio-1.0
210+ if !os(linux) || !arch(x86_64) || flag(profile-reflex)
211+ buildable: False
212+
213+-- broken test on base 4.11 & ghc 8.4.3
214+-- needs to be updated for changes in GHC.Stats
215+test-suite gc
216+ build-depends: base
217+ , chrome-test-utils
156 , jsaddle
218 , jsaddle
157 , jsaddle-warp
219 , jsaddle-warp
158− , linux-namespaces
159 , process
220 , process
160− , temporary
221+ , reflex
161− , unix
222+ , reflex-dom-core
223+ , text
162 hs-source-dirs: test
224 hs-source-dirs: test
163 ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
225 ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
164 ghc-prof-options: -fprof-auto -optP-DPROFILING
226 ghc-prof-options: -fprof-auto -optP-DPROFILING
reflex-dom-core/src/Foreign/JavaScript/Orphans.hs+6−3
@@ -1,5 +1,8 @@
1+{-# OPTIONS_HADDOCK hide #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE CPP #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE UndecidableInstances #-}
5+
3{-# OPTIONS_GHC -fno-warn-orphans #-}
6{-# OPTIONS_GHC -fno-warn-orphans #-}
4module Foreign.JavaScript.Orphans where
7module Foreign.JavaScript.Orphans where
5
8
@@ -7,8 +10,8 @@ module Foreign.JavaScript.Orphans where
7
10
8import Control.Monad.Trans.Class (lift)
11import Control.Monad.Trans.Class (lift)
9import GHCJS.DOM.Types (MonadJSM (..))
12import GHCJS.DOM.Types (MonadJSM (..))
10−import Reflex.DynamicWriter (DynamicWriterT)
13+import Reflex.DynamicWriter.Base (DynamicWriterT)
11−import Reflex.EventWriter (EventWriterT)
14+import Reflex.EventWriter.Base (EventWriterT)
12import Reflex.Host.Class (HostFrame, ReflexHost)
15import Reflex.Host.Class (HostFrame, ReflexHost)
13import Reflex.PerformEvent.Base (PerformEventT (..))
16import Reflex.PerformEvent.Base (PerformEventT (..))
14import Reflex.PostBuild.Base (PostBuildT)
17import Reflex.PostBuild.Base (PostBuildT)
@@ -16,7 +19,7 @@ import Reflex.Requester.Base (RequesterT)
16import Reflex.TriggerEvent.Base
19import Reflex.TriggerEvent.Base
17import Reflex.Query.Base (QueryT)
20import Reflex.Query.Base (QueryT)
18
21
19−instance (MonadJSM m, ReflexHost t) => MonadJSM (PostBuildT t m) where
22+instance MonadJSM m => MonadJSM (PostBuildT t m) where
20 liftJSM' = lift . liftJSM'
23 liftJSM' = lift . liftJSM'
21instance (MonadJSM (HostFrame t), ReflexHost t) => MonadJSM (PerformEventT t m) where
24instance (MonadJSM (HostFrame t), ReflexHost t) => MonadJSM (PerformEventT t m) where
22 liftJSM' = PerformEventT . lift . liftJSM'
25 liftJSM' = PerformEventT . lift . liftJSM'
reflex-dom-core/src/Foreign/JavaScript/TH.hs+10−7
@@ -6,11 +6,9 @@
6{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE FunctionalDependencies #-}
7{-# LANGUAGE FunctionalDependencies #-}
8{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9−{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE RankNTypes #-}
9{-# LANGUAGE RankNTypes #-}
11{-# LANGUAGE StandaloneDeriving #-}
10{-# LANGUAGE StandaloneDeriving #-}
12{-# LANGUAGE TypeFamilies #-}
11{-# LANGUAGE TypeFamilies #-}
13−{-# LANGUAGE TypeSynonymInstances #-}
14{-# LANGUAGE UndecidableInstances #-}
12{-# LANGUAGE UndecidableInstances #-}
15#ifdef USE_TEMPLATE_HASKELL
13#ifdef USE_TEMPLATE_HASKELL
16{-# LANGUAGE TemplateHaskell #-}
14{-# LANGUAGE TemplateHaskell #-}
@@ -28,8 +26,9 @@ module Foreign.JavaScript.TH ( module Foreign.JavaScript.TH
28import Foreign.JavaScript.Orphans ()
26import Foreign.JavaScript.Orphans ()
29import Prelude hiding ((!!))
27import Prelude hiding ((!!))
30import Reflex.Class
28import Reflex.Class
29+import Reflex.Adjustable.Class
31−import Reflex.DynamicWriter
30+import Reflex.DynamicWriter.Base
32−import Reflex.EventWriter
31+import Reflex.EventWriter.Base
33import Reflex.Host.Class
32import Reflex.Host.Class
34import Reflex.PerformEvent.Base
33import Reflex.PerformEvent.Base
35import Reflex.PerformEvent.Class
34import Reflex.PerformEvent.Class
@@ -242,7 +241,7 @@ instance HasJS x m => HasJS x (ReaderT r m) where
242 type JSX (ReaderT r m) = JSX m
241 type JSX (ReaderT r m) = JSX m
243 liftJS = lift . liftJS
242 liftJS = lift . liftJS
244
243
245−instance (HasJS x m, ReflexHost t) => HasJS x (PostBuildT t m) where
244+instance HasJS x m => HasJS x (PostBuildT t m) where
246 type JSX (PostBuildT t m) = JSX m
245 type JSX (PostBuildT t m) = JSX m
247 liftJS = lift . liftJS
246 liftJS = lift . liftJS
248
247
@@ -254,6 +253,10 @@ instance HasJS x m => HasJS x (DynamicWriterT t w m) where
254 type JSX (DynamicWriterT t w m) = JSX m
253 type JSX (DynamicWriterT t w m) = JSX m
255 liftJS = lift . liftJS
254 liftJS = lift . liftJS
256
255
256+instance HasJS x m => HasJS x (EventWriterT t w m) where
257+ type JSX (EventWriterT t w m) = JSX m
258+ liftJS = lift . liftJS
259+
257instance HasJS x m => HasJS x (RequesterT t request response m) where
260instance HasJS x m => HasJS x (RequesterT t request response m) where
258 type JSX (RequesterT t request response m) = JSX m
261 type JSX (RequesterT t request response m) = JSX m
259 liftJS = lift . liftJS
262 liftJS = lift . liftJS
@@ -289,7 +292,7 @@ class Monad m => MonadJS x m | m -> x where
289#ifdef ghcjs_HOST_OS
292#ifdef ghcjs_HOST_OS
290
293
291data JSCtx_IO
294data JSCtx_IO
292−type HasJS' = HasJS JSCtx_IO
295+type JS' = JSCtx_IO
293
296
294instance MonadIO m => HasJS JSCtx_IO (WithJSContextSingleton x m) where
297instance MonadIO m => HasJS JSCtx_IO (WithJSContextSingleton x m) where
295 type JSX (WithJSContextSingleton x m) = IO
298 type JSX (WithJSContextSingleton x m) = IO
@@ -340,7 +343,7 @@ foreign import javascript unsafe "function(){ return $1(arguments); }" funWithAr
340#else
343#else
341
344
342data JSCtx_JavaScriptCore x
345data JSCtx_JavaScriptCore x
343−type HasJS' = HasJS (JSCtx_JavaScriptCore ())
346+type JS' = JSCtx_JavaScriptCore ()
344
347
345instance IsJSContext (JSCtx_JavaScriptCore x) where
348instance IsJSContext (JSCtx_JavaScriptCore x) where
346 newtype JSRef (JSCtx_JavaScriptCore x) = JSRef_JavaScriptCore { unJSRef_JavaScriptCore :: JSVal }
349 newtype JSRef (JSCtx_JavaScriptCore x) = JSRef_JavaScriptCore { unJSRef_JavaScriptCore :: JSVal }
reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+90−14
@@ -11,10 +11,9 @@
11{-# LANGUAGE ImpredicativeTypes #-}
11{-# LANGUAGE ImpredicativeTypes #-}
12#endif
12#endif
13{-# LANGUAGE LambdaCase #-}
13{-# LANGUAGE LambdaCase #-}
14−{-# LANGUAGE MultiParamTypeClasses #-}
15{-# LANGUAGE OverloadedStrings #-}
14{-# LANGUAGE OverloadedStrings #-}
16{-# LANGUAGE PolyKinds #-}
15{-# LANGUAGE PolyKinds #-}
17−{-# LANGUAGE RankNTypes #-}
16+{-# LANGUAGE Rank2Types #-}
18{-# LANGUAGE RecursiveDo #-}
17{-# LANGUAGE RecursiveDo #-}
19{-# LANGUAGE ScopedTypeVariables #-}
18{-# LANGUAGE ScopedTypeVariables #-}
20{-# LANGUAGE StandaloneDeriving #-}
19{-# LANGUAGE StandaloneDeriving #-}
@@ -29,13 +28,15 @@ module Reflex.Dom.Builder.Class
29 , module Reflex.NotReady.Class
28 , module Reflex.NotReady.Class
30 ) where
29 ) where
31
30
31+import Reflex.Adjustable.Class
32import Reflex.Class as Reflex
32import Reflex.Class as Reflex
33import Reflex.Dom.Builder.Class.Events
33import Reflex.Dom.Builder.Class.Events
34#ifdef USE_TEMPLATE_HASKELL
34#ifdef USE_TEMPLATE_HASKELL
35import Reflex.Dom.Builder.Class.TH
35import Reflex.Dom.Builder.Class.TH
36#endif
36#endif
37+import Reflex.BehaviorWriter.Base
37−import Reflex.DynamicWriter
38+import Reflex.DynamicWriter.Base
38−import Reflex.EventWriter
39+import Reflex.EventWriter.Base
39import Reflex.NotReady.Class
40import Reflex.NotReady.Class
40import Reflex.PerformEvent.Class
41import Reflex.PerformEvent.Class
41import Reflex.PostBuild.Base
42import Reflex.PostBuild.Base
@@ -46,6 +47,7 @@ import Reflex.Requester.Base
46import qualified Control.Category
47import qualified Control.Category
47import Control.Lens hiding (element)
48import Control.Lens hiding (element)
48import Control.Monad.Reader
49import Control.Monad.Reader
50+import qualified Control.Monad.State as Lazy
49import Control.Monad.State.Strict
51import Control.Monad.State.Strict
50import Control.Monad.Trans.Control
52import Control.Monad.Trans.Control
51import Data.Default
53import Data.Default
@@ -59,12 +61,14 @@ import Data.String
59import Data.Text (Text)
61import Data.Text (Text)
60import Data.Type.Coercion
62import Data.Type.Coercion
61import GHCJS.DOM.Types (JSM)
63import GHCJS.DOM.Types (JSM)
64+import qualified GHCJS.DOM.Types as DOM
62
65
63class Default (EventSpec d EventResult) => DomSpace d where
66class Default (EventSpec d EventResult) => DomSpace d where
64 type EventSpec d :: (EventTag -> *) -> *
67 type EventSpec d :: (EventTag -> *) -> *
68+ type RawDocument d :: *
65 type RawTextNode d :: *
69 type RawTextNode d :: *
70+ type RawCommentNode d :: *
66 type RawElement d :: *
71 type RawElement d :: *
67− type RawFile d :: *
68 type RawInputElement d :: *
72 type RawInputElement d :: *
69 type RawTextAreaElement d :: *
73 type RawTextAreaElement d :: *
70 type RawSelectElement d :: *
74 type RawSelectElement d :: *
@@ -83,6 +87,15 @@ class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), NotReady t m, Adjustable
83 => TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
87 => TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
84 textNode = lift . textNode
88 textNode = lift . textNode
85 {-# INLINABLE textNode #-}
89 {-# INLINABLE textNode #-}
90+ commentNode :: CommentNodeConfig t -> m (CommentNode (DomBuilderSpace m) t)
91+ default commentNode :: ( MonadTrans f
92+ , m ~ f m'
93+ , DomBuilderSpace m' ~ DomBuilderSpace m
94+ , DomBuilder t m'
95+ )
96+ => CommentNodeConfig t -> m (CommentNode (DomBuilderSpace m) t)
97+ commentNode = lift . commentNode
98+ {-# INLINABLE commentNode #-}
86 element :: Text -> ElementConfig er t (DomBuilderSpace m) -> m a -> m (Element er (DomBuilderSpace m) t, a)
99 element :: Text -> ElementConfig er t (DomBuilderSpace m) -> m a -> m (Element er (DomBuilderSpace m) t, a)
87 default element :: ( MonadTransControl f
100 default element :: ( MonadTransControl f
88 , StT f a ~ a
101 , StT f a ~ a
@@ -172,6 +185,28 @@ newtype TextNode d t = TextNode
172 { _textNode_raw :: RawTextNode d
185 { _textNode_raw :: RawTextNode d
173 }
186 }
174
187
188+data CommentNodeConfig t
189+ = CommentNodeConfig { _commentNodeConfig_initialContents :: {-# UNPACK #-} !Text
190+ , _commentNodeConfig_setContents :: !(Maybe (Event t Text))
191+ }
192+
193+#ifndef USE_TEMPLATE_HASKELL
194+commentNodeConfig_initialContents :: Lens' (CommentNodeConfig t) Text
195+commentNodeConfig_initialContents f (CommentNodeConfig a b) = (\a' -> CommentNodeConfig a' b) <$> f a
196+{-# INLINE commentNodeConfig_initialContents #-}
197+#endif
198+
199+instance (Reflex t) => Default (CommentNodeConfig t) where
200+ {-# INLINABLE def #-}
201+ def = CommentNodeConfig
202+ { _commentNodeConfig_initialContents = mempty
203+ , _commentNodeConfig_setContents = Nothing
204+ }
205+
206+newtype CommentNode d t = CommentNode
207+ { _commentNode_raw :: RawCommentNode d
208+ }
209+
175data AttributeName = AttributeName !(Maybe Namespace) !Text deriving (Show, Read, Eq, Ord)
210data AttributeName = AttributeName !(Maybe Namespace) !Text deriving (Show, Read, Eq, Ord)
176
211
177mapKeysToAttributeName :: Map Text v -> Map AttributeName v
212mapKeysToAttributeName :: Map Text v -> Map AttributeName v
@@ -288,7 +323,7 @@ data InputElement er d t
288 , _inputElement_hasFocus :: Dynamic t Bool
323 , _inputElement_hasFocus :: Dynamic t Bool
289 , _inputElement_element :: Element er d t
324 , _inputElement_element :: Element er d t
290 , _inputElement_raw :: RawInputElement d
325 , _inputElement_raw :: RawInputElement d
291− , _inputElement_files :: Dynamic t [RawFile d]
326+ , _inputElement_files :: Dynamic t [DOM.File]
292 }
327 }
293
328
294data TextAreaElementConfig er t m
329data TextAreaElementConfig er t m
@@ -390,6 +425,7 @@ data SelectElement er d t = SelectElement
390#ifdef USE_TEMPLATE_HASKELL
425#ifdef USE_TEMPLATE_HASKELL
391concat <$> mapM (uncurry makeLensesWithoutField)
426concat <$> mapM (uncurry makeLensesWithoutField)
392 [ (["_textNodeConfig_setContents"], ''TextNodeConfig)
427 [ (["_textNodeConfig_setContents"], ''TextNodeConfig)
428+ , (["_commentNodeConfig_setContents"], ''CommentNodeConfig)
393 , ([ "_inputElementConfig_setValue"
429 , ([ "_inputElementConfig_setValue"
394 , "_inputElementConfig_setChecked" ], ''InputElementConfig)
430 , "_inputElementConfig_setChecked" ], ''InputElementConfig)
395 , (["_rawElementConfig_modifyAttributes"], ''RawElementConfig)
431 , (["_rawElementConfig_modifyAttributes"], ''RawElementConfig)
@@ -400,49 +436,56 @@ concat <$> mapM (uncurry makeLensesWithoutField)
400#endif
436#endif
401
437
402-- | This lens is technically illegal. The implementation of 'TextNodeConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'TextNodeConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
438-- | This lens is technically illegal. The implementation of 'TextNodeConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'TextNodeConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
403−textNodeConfig_setContents :: Reflex t => Lens (TextNodeConfig t) (TextNodeConfig t) (Event t Text) (Event t Text)
439+textNodeConfig_setContents :: Reflex t => Lens' (TextNodeConfig t) (Event t Text)
404textNodeConfig_setContents =
440textNodeConfig_setContents =
405 let getter = fromMaybe never . _textNodeConfig_setContents
441 let getter = fromMaybe never . _textNodeConfig_setContents
406 setter t e = t { _textNodeConfig_setContents = Just e }
442 setter t e = t { _textNodeConfig_setContents = Just e }
407 in lens getter setter
443 in lens getter setter
408
444
445+-- | This lens is technically illegal. The implementation of 'CommentNodeConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'CommentNodeConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
446+commentNodeConfig_setContents :: Reflex t => Lens (CommentNodeConfig t) (CommentNodeConfig t) (Event t Text) (Event t Text)
447+commentNodeConfig_setContents =
448+ let getter = fromMaybe never . _commentNodeConfig_setContents
449+ setter t e = t { _commentNodeConfig_setContents = Just e }
450+ in lens getter setter
451+
409-- | This lens is technically illegal. The implementation of 'InputElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'InputElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
452-- | This lens is technically illegal. The implementation of 'InputElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'InputElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
410−inputElementConfig_setValue :: Reflex t => Lens (InputElementConfig er t m) (InputElementConfig er t m) (Event t Text) (Event t Text)
453+inputElementConfig_setValue :: Reflex t => Lens' (InputElementConfig er t m) (Event t Text)
411inputElementConfig_setValue =
454inputElementConfig_setValue =
412 let getter = fromMaybe never . _inputElementConfig_setValue
455 let getter = fromMaybe never . _inputElementConfig_setValue
413 setter t e = t { _inputElementConfig_setValue = Just e }
456 setter t e = t { _inputElementConfig_setValue = Just e }
414 in lens getter setter
457 in lens getter setter
415
458
416-- | This lens is technically illegal. The implementation of 'InputElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'InputElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
459-- | This lens is technically illegal. The implementation of 'InputElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'InputElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
417−inputElementConfig_setChecked :: Reflex t => Lens (InputElementConfig er t m) (InputElementConfig er t m) (Event t Bool) (Event t Bool)
460+inputElementConfig_setChecked :: Reflex t => Lens' (InputElementConfig er t m) (Event t Bool)
418inputElementConfig_setChecked =
461inputElementConfig_setChecked =
419 let getter = fromMaybe never . _inputElementConfig_setChecked
462 let getter = fromMaybe never . _inputElementConfig_setChecked
420 setter t e = t { _inputElementConfig_setChecked = Just e }
463 setter t e = t { _inputElementConfig_setChecked = Just e }
421 in lens getter setter
464 in lens getter setter
422
465
423-- | This lens is technically illegal. The implementation of 'RawElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'RawElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
466-- | This lens is technically illegal. The implementation of 'RawElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'RawElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
424−rawElementConfig_modifyAttributes :: Reflex t => Lens (RawElementConfig er t m) (RawElementConfig er t m) (Event t (Map AttributeName (Maybe Text))) (Event t (Map AttributeName (Maybe Text)))
467+rawElementConfig_modifyAttributes :: Reflex t => Lens' (RawElementConfig er t m) (Event t (Map AttributeName (Maybe Text)))
425rawElementConfig_modifyAttributes =
468rawElementConfig_modifyAttributes =
426 let getter = fromMaybe never . _rawElementConfig_modifyAttributes
469 let getter = fromMaybe never . _rawElementConfig_modifyAttributes
427 setter t e = t { _rawElementConfig_modifyAttributes = Just e }
470 setter t e = t { _rawElementConfig_modifyAttributes = Just e }
428 in lens getter setter
471 in lens getter setter
429
472
430-- | This lens is technically illegal. The implementation of 'RawElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'RawElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
473-- | This lens is technically illegal. The implementation of 'RawElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'RawElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
431−elementConfig_modifyAttributes :: Reflex t => Lens (ElementConfig er t m) (ElementConfig er t m) (Event t (Map AttributeName (Maybe Text))) (Event t (Map AttributeName (Maybe Text)))
474+elementConfig_modifyAttributes :: Reflex t => Lens' (ElementConfig er t m) (Event t (Map AttributeName (Maybe Text)))
432elementConfig_modifyAttributes =
475elementConfig_modifyAttributes =
433 let getter = fromMaybe never . _elementConfig_modifyAttributes
476 let getter = fromMaybe never . _elementConfig_modifyAttributes
434 setter t e = t { _elementConfig_modifyAttributes = Just e }
477 setter t e = t { _elementConfig_modifyAttributes = Just e }
435 in lens getter setter
478 in lens getter setter
436
479
437-- | This lens is technically illegal. The implementation of 'TextAreaElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'TextAreaElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
480-- | This lens is technically illegal. The implementation of 'TextAreaElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'TextAreaElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
438−textAreaElementConfig_setValue :: Reflex t => Lens (TextAreaElementConfig er t m) (TextAreaElementConfig er t m) (Event t Text) (Event t Text)
481+textAreaElementConfig_setValue :: Reflex t => Lens' (TextAreaElementConfig er t m) (Event t Text)
439textAreaElementConfig_setValue =
482textAreaElementConfig_setValue =
440 let getter = fromMaybe never . _textAreaElementConfig_setValue
483 let getter = fromMaybe never . _textAreaElementConfig_setValue
441 setter t e = t { _textAreaElementConfig_setValue = Just e }
484 setter t e = t { _textAreaElementConfig_setValue = Just e }
442 in lens getter setter
485 in lens getter setter
443
486
444-- | This lens is technically illegal. The implementation of 'SelectElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'SelectElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
487-- | This lens is technically illegal. The implementation of 'SelectElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'SelectElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
445−selectElementConfig_setValue :: Reflex t => Lens (SelectElementConfig er t m) (SelectElementConfig er t m) (Event t Text) (Event t Text)
488+selectElementConfig_setValue :: Reflex t => Lens' (SelectElementConfig er t m) (Event t Text)
446selectElementConfig_setValue =
489selectElementConfig_setValue =
447 let getter = fromMaybe never . _selectElementConfig_setValue
490 let getter = fromMaybe never . _selectElementConfig_setValue
448 setter t e = t { _selectElementConfig_setValue = Just e }
491 setter t e = t { _selectElementConfig_setValue = Just e }
@@ -518,6 +561,7 @@ instance (MountableDomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m)
518instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
561instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
519 type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m
562 type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m
520 textNode = liftTextNode
563 textNode = liftTextNode
564+ commentNode = liftCommentNode
521 element elementTag cfg (DynamicWriterT child) = DynamicWriterT $ do
565 element elementTag cfg (DynamicWriterT child) = DynamicWriterT $ do
522 s <- get
566 s <- get
523 (el, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
567 (el, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
@@ -536,6 +580,7 @@ instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (
536instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT t request response m) where
580instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT t request response m) where
537 type DomBuilderSpace (RequesterT t request response m) = DomBuilderSpace m
581 type DomBuilderSpace (RequesterT t request response m) = DomBuilderSpace m
538 textNode = liftTextNode
582 textNode = liftTextNode
583+ commentNode = liftCommentNode
539 element elementTag cfg (RequesterT child) = RequesterT $ do
584 element elementTag cfg (RequesterT child) = RequesterT $ do
540 r <- ask
585 r <- ask
541 old <- get
586 old <- get
@@ -556,6 +601,7 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT
556instance (DomBuilder t m, MonadHold t m, MonadFix m, Semigroup w) => DomBuilder t (EventWriterT t w m) where
601instance (DomBuilder t m, MonadHold t m, MonadFix m, Semigroup w) => DomBuilder t (EventWriterT t w m) where
557 type DomBuilderSpace (EventWriterT t w m) = DomBuilderSpace m
602 type DomBuilderSpace (EventWriterT t w m) = DomBuilderSpace m
558 textNode = liftTextNode
603 textNode = liftTextNode
604+ commentNode = liftCommentNode
559 element elementTag cfg (EventWriterT child) = EventWriterT $ do
605 element elementTag cfg (EventWriterT child) = EventWriterT $ do
560 old <- get
606 old <- get
561 (el, (a, new)) <- lift $ element elementTag cfg $ runStateT child old
607 (el, (a, new)) <- lift $ element elementTag cfg $ runStateT child old
@@ -571,9 +617,10 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m, Semigroup w) => DomBuilder
571 placeRawElement = lift . placeRawElement
617 placeRawElement = lift . placeRawElement
572 wrapRawElement e = lift . wrapRawElement e
618 wrapRawElement e = lift . wrapRawElement e
573
619
574−instance (DomBuilder t m, MonadFix m, MonadHold t m, Group q, Query q, Additive q) => DomBuilder t (QueryT t q m) where
620+instance (DomBuilder t m, MonadFix m, MonadHold t m, Group q, Query q, Additive q, Eq q) => DomBuilder t (QueryT t q m) where
575 type DomBuilderSpace (QueryT t q m) = DomBuilderSpace m
621 type DomBuilderSpace (QueryT t q m) = DomBuilderSpace m
576 textNode = liftTextNode
622 textNode = liftTextNode
623+ commentNode = liftCommentNode
577 element elementTag cfg (QueryT child) = QueryT $ do
624 element elementTag cfg (QueryT child) = QueryT $ do
578 s <- get
625 s <- get
579 (e, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
626 (e, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
@@ -642,6 +689,9 @@ liftWithStateless a = liftWith $ \run -> a $ fmap (fromStT (Proxy :: Proxy t)) .
642liftTextNode :: (MonadTrans f, DomBuilder t m) => TextNodeConfig t -> f m (TextNode (DomBuilderSpace m) t)
689liftTextNode :: (MonadTrans f, DomBuilder t m) => TextNodeConfig t -> f m (TextNode (DomBuilderSpace m) t)
643liftTextNode = lift . textNode
690liftTextNode = lift . textNode
644
691
692+liftCommentNode :: (MonadTrans f, DomBuilder t m) => CommentNodeConfig t -> f m (CommentNode (DomBuilderSpace m) t)
693+liftCommentNode = lift . commentNode
694+
645liftElement :: LiftDomBuilder t f m => Text -> ElementConfig er t (DomBuilderSpace m) -> f m a -> f m (Element er (DomBuilderSpace m) t, a)
695liftElement :: LiftDomBuilder t f m => Text -> ElementConfig er t (DomBuilderSpace m) -> f m a -> f m (Element er (DomBuilderSpace m) t, a)
646liftElement elementTag cfg child = liftWithStateless $ \run -> element elementTag cfg $ run child
696liftElement elementTag cfg child = liftWithStateless $ \run -> element elementTag cfg $ run child
647
697
@@ -660,7 +710,14 @@ instance DomRenderHook t m => DomRenderHook t (StateT e m) where
660 requestDomAction = lift . requestDomAction
710 requestDomAction = lift . requestDomAction
661 requestDomAction_ = lift . requestDomAction_
711 requestDomAction_ = lift . requestDomAction_
662
712
713+instance DomRenderHook t m => DomRenderHook t (Lazy.StateT e m) where
714+ withRenderHook hook (Lazy.StateT a) = Lazy.StateT $ \s -> withRenderHook hook $ a s
715+ requestDomAction = lift . requestDomAction
716+ requestDomAction_ = lift . requestDomAction_
717+
718+deriving instance DomRenderHook t m => DomRenderHook t (BehaviorWriterT t w m)
663deriving instance DomRenderHook t m => DomRenderHook t (EventWriterT t w m)
719deriving instance DomRenderHook t m => DomRenderHook t (EventWriterT t w m)
720+deriving instance DomRenderHook t m => DomRenderHook t (DynamicWriterT t w m)
664deriving instance DomRenderHook t m => DomRenderHook t (RequesterT t req rsp m)
721deriving instance DomRenderHook t m => DomRenderHook t (RequesterT t req rsp m)
665deriving instance DomRenderHook t m => DomRenderHook t (PostBuildT t m)
722deriving instance DomRenderHook t m => DomRenderHook t (PostBuildT t m)
666deriving instance DomRenderHook t m => DomRenderHook t (QueryT t q m)
723deriving instance DomRenderHook t m => DomRenderHook t (QueryT t q m)
@@ -668,3 +725,22 @@ deriving instance DomRenderHook t m => DomRenderHook t (QueryT t q m)
668{-# DEPRECATED liftElementConfig "Use 'id' instead; this function is no longer necessary" #-}
725{-# DEPRECATED liftElementConfig "Use 'id' instead; this function is no longer necessary" #-}
669liftElementConfig :: ElementConfig er t s -> ElementConfig er t s
726liftElementConfig :: ElementConfig er t s -> ElementConfig er t s
670liftElementConfig = id
727liftElementConfig = id
728+
729+class Monad m => HasDocument m where
730+ askDocument :: m (RawDocument (DomBuilderSpace m))
731+ default askDocument
732+ :: ( m ~ f m'
733+ , RawDocument (DomBuilderSpace m) ~ RawDocument (DomBuilderSpace m')
734+ , MonadTrans f
735+ , Monad m'
736+ , HasDocument m'
737+ )
738+ => m (RawDocument (DomBuilderSpace m))
739+ askDocument = lift askDocument
740+
741+instance HasDocument m => HasDocument (ReaderT r m)
742+instance HasDocument m => HasDocument (EventWriterT t w m)
743+instance HasDocument m => HasDocument (DynamicWriterT t w m)
744+instance HasDocument m => HasDocument (PostBuildT t m)
745+instance HasDocument m => HasDocument (RequesterT t request response m)
746+instance HasDocument m => HasDocument (QueryT t q m)
reflex-dom-core/src/Reflex/Dom/Builder/Class/Events.hs−1
@@ -1,7 +1,6 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
4−{-# LANGUAGE KindSignatures #-}
5{-# LANGUAGE PolyKinds #-}
4{-# LANGUAGE PolyKinds #-}
6#ifdef USE_TEMPLATE_HASKELL
5#ifdef USE_TEMPLATE_HASKELL
7{-# LANGUAGE TemplateHaskell #-}
6{-# LANGUAGE TemplateHaskell #-}
reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hsnew file+116
@@ -0,0 +1,116 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE StandaloneDeriving #-}
7+{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE UndecidableInstances #-}
9+module Reflex.Dom.Builder.Hydratable where
10+
11+import Control.Monad.Fix
12+import Control.Monad.Primitive
13+import Control.Monad.Ref
14+import Control.Monad.Trans
15+import Control.Monad.Trans.Control
16+import Data.Coerce
17+import qualified Data.Map as Map
18+import Foreign.JavaScript.TH
19+#ifndef ghcjs_HOST_OS
20+import GHCJS.DOM.Types (MonadJSM (..))
21+#endif
22+import Reflex
23+import Reflex.Dom.Builder.Class
24+import Reflex.Dom.Builder.Immediate (HasDocument (..))
25+import Reflex.Host.Class
26+
27+-- | A DomBuilder transformer that adds "data-ssr" to all elements such that the
28+-- hydration builder knows which bits of DOM were added by us, and which were
29+-- added by external scripts.
30+newtype HydratableT m a = HydratableT { runHydratableT :: m a } deriving (Functor, Applicative, Monad, MonadAtomicRef, MonadFix, MonadIO)
31+
32+#ifndef ghcjs_HOST_OS
33+instance MonadJSM m => MonadJSM (HydratableT m) where
34+ liftJSM' = HydratableT . liftJSM'
35+#endif
36+
37+deriving instance MonadSample t m => MonadSample t (HydratableT m)
38+deriving instance MonadHold t m => MonadHold t (HydratableT m)
39+
40+instance MonadTrans HydratableT where
41+ lift = HydratableT
42+
43+instance MonadTransControl HydratableT where
44+ type StT HydratableT a = a
45+ liftWith f = HydratableT $ f runHydratableT
46+ restoreT = HydratableT
47+
48+instance MonadRef m => MonadRef (HydratableT m) where
49+ type Ref (HydratableT m) = Ref m
50+ newRef = lift . newRef
51+ readRef = lift . readRef
52+ writeRef ref = lift . writeRef ref
53+
54+instance PerformEvent t m => PerformEvent t (HydratableT m) where
55+ type Performable (HydratableT m) = Performable m
56+ performEvent_ = lift . performEvent_
57+ performEvent = lift . performEvent
58+
59+instance PrimMonad m => PrimMonad (HydratableT m) where
60+ type PrimState (HydratableT m) = PrimState m
61+ primitive = lift . primitive
62+
63+makeHydratable :: Reflex t => ElementConfig er t m -> ElementConfig er t m
64+makeHydratable cfg = cfg
65+ { _elementConfig_initialAttributes = Map.insert "data-ssr" "" $ _elementConfig_initialAttributes cfg
66+ , _elementConfig_modifyAttributes = fmap (Map.delete "data-ssr") <$> _elementConfig_modifyAttributes cfg
67+ }
68+
69+instance PostBuild t m => PostBuild t (HydratableT m) where
70+ getPostBuild = lift getPostBuild
71+
72+deriving instance TriggerEvent t m => TriggerEvent t (HydratableT m)
73+
74+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydratableT m) where
75+ newEventWithTrigger = lift . newEventWithTrigger
76+ newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
77+
78+instance Adjustable t m => Adjustable t (HydratableT m) where
79+ runWithReplace a0 a' = HydratableT $ runWithReplace (coerce a0) (coerceEvent a')
80+ traverseDMapWithKeyWithAdjust f dm0 dm' = HydratableT $ traverseDMapWithKeyWithAdjust (\k v -> runHydratableT $ f k v) (coerce dm0) (coerceEvent dm')
81+ traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = HydratableT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runHydratableT $ f k v) (coerce dm0) (coerceEvent dm')
82+ traverseIntMapWithKeyWithAdjust f m0 m' = HydratableT $ traverseIntMapWithKeyWithAdjust (\k v -> runHydratableT $ f k v) (coerce m0) (coerceEvent m')
83+
84+instance NotReady t m => NotReady t (HydratableT m) where
85+ notReadyUntil = lift . notReadyUntil
86+ notReady = lift notReady
87+
88+instance DomBuilder t m => DomBuilder t (HydratableT m) where
89+ type DomBuilderSpace (HydratableT m) = DomBuilderSpace m
90+ element t cfg = lift . element t (makeHydratable cfg) . runHydratableT
91+ inputElement cfg = lift $ inputElement $ cfg
92+ { _inputElementConfig_elementConfig = makeHydratable $ _inputElementConfig_elementConfig cfg
93+ }
94+ textAreaElement cfg = lift $ textAreaElement $ cfg
95+ { _textAreaElementConfig_elementConfig = makeHydratable $ _textAreaElementConfig_elementConfig cfg
96+ }
97+ selectElement cfg child = do
98+ let cfg' = cfg
99+ { _selectElementConfig_elementConfig = makeHydratable $ _selectElementConfig_elementConfig cfg
100+ }
101+ lift $ selectElement cfg' $ runHydratableT child
102+
103+instance HasDocument m => HasDocument (HydratableT m)
104+
105+instance HasJSContext m => HasJSContext (HydratableT m) where
106+ type JSContextPhantom (HydratableT m) = JSContextPhantom m
107+ askJSContext = lift askJSContext
108+
109+instance HasJS js m => HasJS js (HydratableT m) where
110+ type JSX (HydratableT m) = JSX m
111+ liftJS = lift . liftJS
112+
113+instance DomRenderHook t m => DomRenderHook t (HydratableT m) where
114+ withRenderHook f = HydratableT . withRenderHook f . runHydratableT
115+ requestDomAction = HydratableT . requestDomAction
116+ requestDomAction_ = HydratableT . requestDomAction_
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+1651−583
@@ -2,7 +2,8 @@
2{-# LANGUAGE CPP #-}
2{-# LANGUAGE CPP #-}
3{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE ConstraintKinds #-}
4{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DataKinds #-}
5−{-# LANGUAGE DefaultSignatures #-}
5+{-# LANGUAGE DeriveFunctor #-}
6+{-# LANGUAGE DeriveTraversable #-}
6{-# LANGUAGE EmptyDataDecls #-}
7{-# LANGUAGE EmptyDataDecls #-}
7{-# LANGUAGE FlexibleContexts #-}
8{-# LANGUAGE FlexibleContexts #-}
8{-# LANGUAGE FlexibleInstances #-}
9{-# LANGUAGE FlexibleInstances #-}
@@ -11,14 +12,15 @@
11{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE MultiParamTypeClasses #-}
13{-# LANGUAGE MultiParamTypeClasses #-}
13{-# LANGUAGE OverloadedStrings #-}
14{-# LANGUAGE OverloadedStrings #-}
15+{-# LANGUAGE PatternGuards #-}
14{-# LANGUAGE PolyKinds #-}
16{-# LANGUAGE PolyKinds #-}
15{-# LANGUAGE RankNTypes #-}
17{-# LANGUAGE RankNTypes #-}
16{-# LANGUAGE RecursiveDo #-}
18{-# LANGUAGE RecursiveDo #-}
17{-# LANGUAGE ScopedTypeVariables #-}
19{-# LANGUAGE ScopedTypeVariables #-}
18−{-# LANGUAGE StandaloneDeriving #-}
19#ifdef USE_TEMPLATE_HASKELL
20#ifdef USE_TEMPLATE_HASKELL
20{-# LANGUAGE TemplateHaskell #-}
21{-# LANGUAGE TemplateHaskell #-}
21#endif
22#endif
23+{-# LANGUAGE TupleSections #-}
22{-# LANGUAGE TypeFamilies #-}
24{-# LANGUAGE TypeFamilies #-}
23{-# LANGUAGE TypeOperators #-}
25{-# LANGUAGE TypeOperators #-}
24{-# LANGUAGE UndecidableInstances #-}
26{-# LANGUAGE UndecidableInstances #-}
@@ -26,191 +28,346 @@
26{-# LANGUAGE ForeignFunctionInterface #-}
28{-# LANGUAGE ForeignFunctionInterface #-}
27{-# LANGUAGE JavaScriptFFI #-}
29{-# LANGUAGE JavaScriptFFI #-}
28#endif
30#endif
31+-- | This is a builder to be used on the client side. It can be run in two modes:
32+--
33+-- 1. in "hydration mode", reusing DOM nodes already in the page (as produced
34+-- by 'Reflex.Dom.Builder.Static.renderStatic')
35+-- 2. in "immediate mode", creating and appending DOM nodes as required
36+--
37+-- In "hydration mode", the preexisting DOM __must contain__ what the builder
38+-- will expect at switchover time (the time at which parity with the static
39+-- renderer is reached, and the time after which the page is "live").
40+--
41+-- For example, displaying the current time as text should be done inside
42+-- 'Reflex.Dom.Prerender.prerender' to ensure that we don't attempt to hydrate the incorrect text.
43+-- The server will prerender a text node with time A, and the client will expect
44+-- a text node with time B. Barring a miracle, time A and time B will not match,
45+-- and hydration will fail.
29module Reflex.Dom.Builder.Immediate
46module Reflex.Dom.Builder.Immediate
30− ( EventTriggerRef (..)
47+ ( HydrationDomBuilderT (..)
31− , ImmediateDomBuilderEnv (..)
48+ , HydrationDomBuilderEnv (..)
49+ , HydrationMode (..)
50+ , HydrationRunnerT (..)
51+ , runHydrationRunnerT
32− , ImmediateDomBuilderT (..)
52+ , ImmediateDomBuilderT
33− , runImmediateDomBuilderT
53+ , runHydrationDomBuilderT
54+ , getHydrationMode
55+ , addHydrationStep
56+ , addHydrationStepWithSetup
57+ , setPreviousNode
58+ , insertAfterPreviousNode
59+ , hydrateComment
34− , askParent
60+ , askParent
35− , askEvents
61+ , askEvents
36− , append
62+ , append
37− , textNodeInternal
63+ , textNodeInternal
64+ , removeSubsequentNodes
38− , deleteBetweenExclusive
65+ , deleteBetweenExclusive
39− , extractBetweenExclusive
66+ , extractBetweenExclusive
40− , deleteUpTo
67+ , deleteUpTo
41− , extractUpTo
68+ , extractUpTo
42− , SupportsImmediateDomBuilder
69+ , SupportsHydrationDomBuilder
43− , collectUpTo
70+ , collectUpTo
44− , collectUpToGivenParent
71+ , collectUpToGivenParent
72+ , EventTriggerRef (..)
45− , EventFilterTriggerRef (..)
73+ , EventFilterTriggerRef (..)
46− , wrap
74+ , wrap
47− , makeElement
75+ , elementInternal
76+ , HydrationDomSpace
77+ , GhcjsDomSpace
48− , GhcjsDomHandler (..)
78+ , GhcjsDomHandler (..)
49− , GhcjsDomHandler1 (..)
79+ , GhcjsDomHandler1 (..)
50− , GhcjsDomEvent (..)
80+ , GhcjsDomEvent (..)
51− , GhcjsDomSpace
52− , GhcjsEventFilter (..)
81+ , GhcjsEventFilter (..)
53− , Pair1 (..)
82+ , Pair1 (..)
54− , Maybe1 (..)
83+ , Maybe1 (..)
55− , GhcjsEventSpec (..)
84+ , GhcjsEventSpec (..)
56− , HasDocument (..)
85+ , HasDocument (..)
57− , ghcjsEventSpec_filters
86+ , ghcjsEventSpec_filters
58− , ghcjsEventSpec_handler
87+ , ghcjsEventSpec_handler
59− , GhcjsEventHandler (..)
88+ , GhcjsEventHandler (..)
60−#ifndef USE_TEMPLATE_HASKELL
61− , phantom2
62−#endif
63− , drawChildUpdate
89+ , drawChildUpdate
64− , ChildReadyState (..)
90+ , ChildReadyState (..)
65− , ChildReadyStateInt (..)
66− , mkHasFocus
91+ , mkHasFocus
67− , insertBefore
92+ , insertBefore
68− , EventType
93+ , EventType
69− , defaultDomEventHandler
94+ , defaultDomEventHandler
70− , defaultDomWindowEventHandler
95+ , defaultDomWindowEventHandler
71− , withIsEvent
96+ , withIsEvent
72− , showEventName
97+ , showEventName
73− , elementOnEventName
98+ , elementOnEventName
74− , windowOnEventName
99+ , windowOnEventName
75− , wrapDomEvent
100+ , wrapDomEvent
76− , subscribeDomEvent
101+ , subscribeDomEvent
77− , wrapDomEventMaybe
102+ , wrapDomEventMaybe
78− , wrapDomEventsMaybe
103+ , wrapDomEventsMaybe
79− , getKeyEvent
104+ , getKeyEvent
80− , getMouseEventCoords
105+ , getMouseEventCoords
81− , getTouchEvent
106+ , getTouchEvent
82− , WindowConfig (..)
107+ , WindowConfig (..)
83− , Window (..)
108+ , Window (..)
84− , wrapWindow
109+ , wrapWindow
85− -- * Internal
110+ -- * Internal
86− , traverseDMapWithKeyWithAdjust'
111+ , traverseDMapWithKeyWithAdjust'
87− , hoistTraverseWithKeyWithAdjust
112+ , hoistTraverseWithKeyWithAdjust
88− , traverseIntMapWithKeyWithAdjust'
113+ , traverseIntMapWithKeyWithAdjust'
89− , hoistTraverseIntMapWithKeyWithAdjust
114+ , hoistTraverseIntMapWithKeyWithAdjust
90− ) where
115+ ) where
91−
92−import Foreign.JavaScript.TH
93−import Reflex.Class as Reflex
94−import Reflex.Dom.Builder.Class
95−import Reflex.Dynamic
96−import Reflex.DynamicWriter (DynamicWriterT)
97−import Reflex.EventWriter (EventWriterT)
98−import Reflex.Host.Class
99−import qualified Reflex.Patch.DMap as PatchDMap
100−import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
101−import Reflex.PerformEvent.Class
102−import Reflex.PostBuild.Base
103−import Reflex.PostBuild.Class
104−import Reflex.TriggerEvent.Base hiding (askEvents)
105−import qualified Reflex.TriggerEvent.Base as TriggerEventT (askEvents)
106−import Reflex.TriggerEvent.Class
107−import Reflex.Query.Base (QueryT)
108
116
109import Control.Concurrent
117import Control.Concurrent
110−import Control.Lens hiding (element, ix)
118+import Control.Exception (bracketOnError)
119+import Control.Lens (Identity(..), imapM_, iforM_, (^.), makeLenses)
111import Control.Monad.Exception
120import Control.Monad.Exception
112import Control.Monad.Primitive
121import Control.Monad.Primitive
113import Control.Monad.Reader
122import Control.Monad.Reader
114import Control.Monad.Ref
123import Control.Monad.Ref
115−import Control.Monad.State.Strict (StateT)
124+import Control.Monad.State.Strict (StateT, mapStateT, get, modify', gets, runStateT)
116−import qualified Control.Monad.State as Lazy (StateT)
117−#ifndef USE_TEMPLATE_HASKELL
118−import Data.Functor.Contravariant (phantom)
119−#endif
120import Data.Bitraversable
125import Data.Bitraversable
121import Data.Default
126import Data.Default
122import Data.Dependent.Map (DMap)
127import Data.Dependent.Map (DMap)
123−import qualified Data.Dependent.Map as DMap
124import Data.Dependent.Sum
128import Data.Dependent.Sum
129+import Data.FastMutableIntMap (PatchIntMap (..))
130+import Data.Foldable (for_, traverse_)
125import Data.Functor.Compose
131import Data.Functor.Compose
126import Data.Functor.Constant
132import Data.Functor.Constant
127import Data.Functor.Misc
133import Data.Functor.Misc
128import Data.Functor.Product
134import Data.Functor.Product
129import Data.IORef
135import Data.IORef
130−import qualified Data.Map as Map
136+import Data.IntMap.Strict (IntMap)
131import Data.Maybe
137import Data.Maybe
132−import Data.Monoid hiding (Product)
138+import Data.Monoid ((<>))
133−import Data.Some (Some)
139+import Data.Some (Some(..))
134−import qualified Data.Some as Some
135import Data.Text (Text)
140import Data.Text (Text)
141+import Foreign.JavaScript.Internal.Utils
142+import Foreign.JavaScript.TH
143+import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
144+import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute, hasAttributeNS)
145+import GHCJS.DOM.EventM (EventM, event, on)
146+import GHCJS.DOM.KeyboardEvent as KeyboardEvent
147+import GHCJS.DOM.MouseEvent
148+import GHCJS.DOM.Node (appendChild_, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
149+import GHCJS.DOM.Types (liftJSM, askJSM, runJSM, JSM, MonadJSM, FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node, TouchEvent, WheelEvent, uncheckedCastTo, ClipboardEvent)
150+import GHCJS.DOM.UIEvent
151+import Language.Javascript.JSaddle (call, eval)
152+import Reflex.Adjustable.Class
153+import Reflex.Class as Reflex
154+import Reflex.Dom.Builder.Class
155+import Reflex.Dynamic
156+import Reflex.Host.Class
157+import Reflex.Patch.DMapWithMove (PatchDMapWithMove(..))
158+import Reflex.Patch.MapWithMove (PatchMapWithMove(..))
159+import Reflex.PerformEvent.Base (PerformEventT)
160+import Reflex.PerformEvent.Class
161+import Reflex.PostBuild.Base (PostBuildT)
162+import Reflex.PostBuild.Class
163+#ifdef PROFILE_REFLEX
164+import Reflex.Profiled
165+#endif
166+import Reflex.Requester.Base
167+import Reflex.Requester.Class
168+import Reflex.Spider (Spider, SpiderHost, Global)
169+import Reflex.TriggerEvent.Base hiding (askEvents)
170+import Reflex.TriggerEvent.Class
171+
172+import qualified Data.Dependent.Map as DMap
173+import qualified Data.FastMutableIntMap as FastMutableIntMap
174+import qualified Data.IntMap.Strict as IntMap
175+import qualified Data.Map as Map
136import qualified Data.Text as T
176import qualified Data.Text as T
137import qualified GHCJS.DOM as DOM
177import qualified GHCJS.DOM as DOM
138−import GHCJS.DOM.RequestAnimationFrameCallback
178+import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
139−import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode)
179+import qualified GHCJS.DOM.DocumentOrShadowRoot as Document
140−import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
141import qualified GHCJS.DOM.Element as Element
180import qualified GHCJS.DOM.Element as Element
142import qualified GHCJS.DOM.Event as Event
181import qualified GHCJS.DOM.Event as Event
143−import qualified GHCJS.DOM.GlobalEventHandlers as Events
144−import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
145−import GHCJS.DOM.EventM (EventM, event, on)
146import qualified GHCJS.DOM.EventM as DOM
182import qualified GHCJS.DOM.EventM as DOM
147import qualified GHCJS.DOM.FileList as FileList
183import qualified GHCJS.DOM.FileList as FileList
184+import qualified GHCJS.DOM.GlobalEventHandlers as Events
148import qualified GHCJS.DOM.HTMLInputElement as Input
185import qualified GHCJS.DOM.HTMLInputElement as Input
149import qualified GHCJS.DOM.HTMLSelectElement as Select
186import qualified GHCJS.DOM.HTMLSelectElement as Select
150import qualified GHCJS.DOM.HTMLTextAreaElement as TextArea
187import qualified GHCJS.DOM.HTMLTextAreaElement as TextArea
151−import GHCJS.DOM.MouseEvent
188+import qualified GHCJS.DOM.Node as Node
189+import qualified GHCJS.DOM.Text as DOM
152import qualified GHCJS.DOM.Touch as Touch
190import qualified GHCJS.DOM.Touch as Touch
153import qualified GHCJS.DOM.TouchEvent as TouchEvent
191import qualified GHCJS.DOM.TouchEvent as TouchEvent
154import qualified GHCJS.DOM.TouchList as TouchList
192import qualified GHCJS.DOM.TouchList as TouchList
155−import GHCJS.DOM.Node (appendChild_, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
156−import qualified GHCJS.DOM.Node as DOM (insertBefore_)
157−import GHCJS.DOM.Types
158− (liftJSM, askJSM, runJSM, JSM, MonadJSM,
159− FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
160− ToDOMString, TouchEvent, WheelEvent, uncheckedCastTo, ClipboardEvent)
161import qualified GHCJS.DOM.Types as DOM
193import qualified GHCJS.DOM.Types as DOM
162−import GHCJS.DOM.UIEvent
163−import GHCJS.DOM.KeyboardEvent as KeyboardEvent
164import qualified GHCJS.DOM.Window as Window
194import qualified GHCJS.DOM.Window as Window
165−import Language.Javascript.JSaddle (call, eval)
195+import qualified Reflex.Patch.DMap as PatchDMap
166−import Data.IntMap.Strict (IntMap)
196+import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
167−import qualified Data.IntMap.Strict as IntMap
197+import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
168−import Data.FastMutableIntMap (PatchIntMap (..))
198+import qualified Reflex.TriggerEvent.Base as TriggerEventT (askEvents)
169−import qualified Data.FastMutableIntMap as FastMutableIntMap
170
199
171−import Reflex.Requester.Base
200+#ifndef USE_TEMPLATE_HASKELL
172−import Reflex.Requester.Class
201+import Data.Functor.Contravariant (phantom)
173−import Foreign.JavaScript.Internal.Utils
202+import Control.Lens (Lens', Getter)
203+#endif
174
204
175#ifndef ghcjs_HOST_OS
205#ifndef ghcjs_HOST_OS
176import GHCJS.DOM.Types (MonadJSM (..))
206import GHCJS.DOM.Types (MonadJSM (..))
177
207
178−instance MonadJSM m => MonadJSM (ImmediateDomBuilderT t m) where
208+instance MonadJSM m => MonadJSM (HydrationRunnerT t m) where
209+ {-# INLINABLE liftJSM' #-}
210+ liftJSM' = lift . liftJSM'
211+
212+instance MonadJSM m => MonadJSM (HydrationDomBuilderT s t m) where
213+ {-# INLINABLE liftJSM' #-}
214+ liftJSM' = lift . liftJSM'
215+
216+instance MonadJSM m => MonadJSM (DomRenderHookT t m) where
217+ {-# INLINABLE liftJSM' #-}
179− liftJSM' = ImmediateDomBuilderT . liftJSM'
218+ liftJSM' = lift . liftJSM'
180#endif
219#endif
181
220
182−data ImmediateDomBuilderEnv t
221+data HydrationDomBuilderEnv t m = HydrationDomBuilderEnv
183− = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
222+ { _hydrationDomBuilderEnv_document :: {-# UNPACK #-} !Document
184− , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
223+ -- ^ Reference to the document
224+ , _hydrationDomBuilderEnv_parent :: !(Either Node (IORef Node))
225+ -- ^ This is in an IORef because in the time up to hydration we can't actually know what the
226+ -- parent is - we populate this reference during the DOM traversal at hydration time
227+ , _hydrationDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word)
185− , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
228+ -- ^ Number of children who still aren't fully rendered
229+ , _hydrationDomBuilderEnv_commitAction :: !(JSM ())
186− , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
230+ -- ^ Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
231+ , _hydrationDomBuilderEnv_hydrationMode :: {-# UNPACK #-} !(IORef HydrationMode)
232+ -- ^ In hydration mode? Should be switched to `HydrationMode_Immediate` after hydration is finished
233+ , _hydrationDomBuilderEnv_switchover :: !(Event t ())
234+ , _hydrationDomBuilderEnv_delayed :: {-# UNPACK #-} !(IORef (HydrationRunnerT t m ()))
187− }
235+ }
188
236
189−newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a }
237+-- | A monad for DomBuilder which just gets the results of children and pushes
238+-- work into an action that is delayed until after postBuild (to match the
239+-- static builder). The action runs in 'HydrationRunnerT', which performs the
240+-- DOM takeover and sets up the events, after which point this monad will
241+-- continue in the vein of 'ImmediateDomBuilderT'.
242+newtype HydrationDomBuilderT s t m a = HydrationDomBuilderT { unHydrationDomBuilderT :: ReaderT (HydrationDomBuilderEnv t m) (DomRenderHookT t m) a }
190 deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
243 deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
191#if MIN_VERSION_base(4,9,1)
244#if MIN_VERSION_base(4,9,1)
192 , MonadAsyncException
245 , MonadAsyncException
193#endif
246#endif
194 )
247 )
195
248
196−instance PrimMonad m => PrimMonad (ImmediateDomBuilderT x m) where
249+instance PrimMonad m => PrimMonad (HydrationDomBuilderT s t m) where
197− type PrimState (ImmediateDomBuilderT x m) = PrimState m
250+ type PrimState (HydrationDomBuilderT s t m) = PrimState m
198 primitive = lift . primitive
251 primitive = lift . primitive
199
252
200−instance MonadTrans (ImmediateDomBuilderT t) where
253+instance MonadTrans (HydrationDomBuilderT s t) where
201− lift = ImmediateDomBuilderT . lift . lift . lift
254+ lift = HydrationDomBuilderT . lift . lift
202
255
203−instance (Reflex t, MonadFix m) => DomRenderHook t (ImmediateDomBuilderT t m) where
256+instance (Reflex t, MonadFix m) => DomRenderHook t (HydrationDomBuilderT s t m) where
204− withRenderHook hook (ImmediateDomBuilderT a) = do
257+ withRenderHook hook = HydrationDomBuilderT . mapReaderT (withRenderHook hook) . unHydrationDomBuilderT
205− e <- ImmediateDomBuilderT ask
258+ requestDomAction = HydrationDomBuilderT . lift . requestDomAction
206− ImmediateDomBuilderT $ lift $ withRequesting $ \rsp -> do
259+ requestDomAction_ = HydrationDomBuilderT . lift . requestDomAction_
207− (x, req) <- lift $ runRequesterT (runReaderT a e) $ runIdentity <$> rsp
208− return (ffor req $ \rm -> hook $ traverseRequesterData (\r -> Identity <$> r) rm, x)
209− requestDomAction = ImmediateDomBuilderT . lift . requestingIdentity
210− requestDomAction_ = ImmediateDomBuilderT . lift . requesting_
211
260
212−{-# INLINABLE runImmediateDomBuilderT #-}
261+-- | The monad which performs the delayed actions to reuse prerendered nodes and set up events.
213−runImmediateDomBuilderT
262+-- State contains reference to the previous node sibling, if any, and the reader contains reference to the parent node.
263+newtype HydrationRunnerT t m a = HydrationRunnerT { unHydrationRunnerT :: StateT HydrationState (ReaderT Node (DomRenderHookT t m)) a }
264+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
265+#if MIN_VERSION_base(4,9,1)
266+ , MonadAsyncException
267+#endif
268+ )
269+
270+data HydrationState = HydrationState
271+ { _hydrationState_previousNode :: !(Maybe Node)
272+ , _hydrationState_failed :: !Bool
273+ }
274+
275+{-# INLINABLE localRunner #-}
276+localRunner :: (MonadJSM m, Monad m) => HydrationRunnerT t m a -> Maybe Node -> Node -> HydrationRunnerT t m a
277+localRunner (HydrationRunnerT m) s parent = do
278+ s0 <- HydrationRunnerT get
279+ (a, s') <- HydrationRunnerT $ lift $ local (\_ -> parent) $ runStateT m (s0 { _hydrationState_previousNode = s })
280+ traverse_ removeSubsequentNodes $ _hydrationState_previousNode s'
281+ HydrationRunnerT $ modify' $ \hs -> hs { _hydrationState_failed = _hydrationState_failed s' }
282+ pure a
283+
284+{-# INLINABLE runHydrationRunnerT #-}
285+runHydrationRunnerT
286+ :: (MonadRef m, Ref m ~ IORef, Monad m, PerformEvent t m, MonadFix m, MonadReflexCreateTrigger t m, MonadJSM m, MonadJSM (Performable m))
287+ => HydrationRunnerT t m a -> Maybe Node -> Node -> Chan [DSum (EventTriggerRef t) TriggerInvocation] -> m a
288+runHydrationRunnerT (HydrationRunnerT m) s parent events = flip runDomRenderHookT events $ flip runReaderT parent $ do
289+ (a, s') <- runStateT m (HydrationState s False)
290+ traverse_ removeSubsequentNodes $ _hydrationState_previousNode s'
291+ when (_hydrationState_failed s') $ liftIO $ putStrLn "reflex-dom warning: hydration failed: the DOM was not as expected at switchover time. This may be due to invalid HTML which the browser has altered upon parsing, some external JS altering the DOM, or the page being served from an outdated cache."
292+ pure a
293+
294+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydrationRunnerT t m) where
295+ {-# INLINABLE newEventWithTrigger #-}
296+ newEventWithTrigger = lift . newEventWithTrigger
297+ {-# INLINABLE newFanEventWithTrigger #-}
298+ newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
299+
300+instance MonadTrans (HydrationRunnerT t) where
301+ {-# INLINABLE lift #-}
302+ lift = HydrationRunnerT . lift . lift . lift
303+
304+instance MonadSample t m => MonadSample t (HydrationRunnerT t m) where
305+ {-# INLINABLE sample #-}
306+ sample = lift . sample
307+
308+instance (Reflex t, MonadFix m) => DomRenderHook t (HydrationRunnerT t m) where
309+ withRenderHook hook = HydrationRunnerT . mapStateT (mapReaderT (withRenderHook hook)) . unHydrationRunnerT
310+ requestDomAction = HydrationRunnerT . lift . lift . requestDomAction
311+ requestDomAction_ = HydrationRunnerT . lift . lift . requestDomAction_
312+
313+-- | Add a hydration step which depends on some computation that should only be
314+-- done *before* the switchover to immediate mode - this is most likely some
315+-- form of 'hold' which we want to remove after hydration is done
316+{-# INLINABLE addHydrationStepWithSetup #-}
317+addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> HydrationRunnerT t m ()) -> HydrationDomBuilderT s t m ()
318+addHydrationStepWithSetup setup f = getHydrationMode >>= \case
319+ HydrationMode_Immediate -> pure ()
320+ HydrationMode_Hydrating -> do
321+ switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
322+ (s, _) <- lift $ runWithReplace setup $ return () <$ switchover
323+ addHydrationStep (f s)
324+
325+-- | Add a hydration step
326+{-# INLINABLE addHydrationStep #-}
327+addHydrationStep :: MonadIO m => HydrationRunnerT t m () -> HydrationDomBuilderT s t m ()
328+addHydrationStep m = do
329+ delayedRef <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_delayed
330+ liftIO $ modifyIORef' delayedRef (>> m)
331+
332+-- | Shared behavior for HydrationDomBuilderT and HydrationRunnerT
333+newtype DomRenderHookT t m a = DomRenderHookT { unDomRenderHookT :: RequesterT t JSM Identity (TriggerEventT t m) a }
334+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
335+#if MIN_VERSION_base(4,9,1)
336+ , MonadAsyncException
337+#endif
338+ )
339+
340+{-# INLINABLE runDomRenderHookT #-}
341+runDomRenderHookT
342+ :: (MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadJSM m, MonadJSM (Performable m), MonadRef m, Ref m ~ IORef)
343+ => DomRenderHookT t m a
344+ -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
345+ -> m a
346+runDomRenderHookT (DomRenderHookT a) events = do
347+ flip runTriggerEventT events $ do
348+ rec (result, req) <- runRequesterT a rsp
349+ rsp <- performEventAsync $ ffor req $ \rm f -> liftJSM $ runInAnimationFrame f $
350+ traverseRequesterData (fmap Identity) rm
351+ return result
352+ where
353+ runInAnimationFrame f x = void . DOM.inAnimationFrame' $ \_ -> do
354+ v <- synchronously x
355+ void . liftIO $ f v
356+
357+instance MonadTrans (DomRenderHookT t) where
358+ {-# INLINABLE lift #-}
359+ lift = DomRenderHookT . lift . lift
360+
361+instance (Reflex t, MonadFix m) => DomRenderHook t (DomRenderHookT t m) where
362+ withRenderHook hook (DomRenderHookT a) = do
363+ DomRenderHookT $ withRequesting $ \rsp -> do
364+ (x, req) <- lift $ runRequesterT a $ runIdentity <$> rsp
365+ return (ffor req $ \rm -> hook $ traverseRequesterData (fmap Identity) rm, x)
366+ requestDomAction = DomRenderHookT . requestingIdentity
367+ requestDomAction_ = DomRenderHookT . requesting_
368+
369+{-# INLINABLE runHydrationDomBuilderT #-}
370+runHydrationDomBuilderT
214 :: ( MonadFix m
371 :: ( MonadFix m
215 , PerformEvent t m
372 , PerformEvent t m
216 , MonadReflexCreateTrigger t m
373 , MonadReflexCreateTrigger t m
@@ -219,64 +376,76 @@ runImmediateDomBuilderT
219 , MonadRef m
376 , MonadRef m
220 , Ref m ~ IORef
377 , Ref m ~ IORef
221 )
378 )
222− => ImmediateDomBuilderT t m a
379+ => HydrationDomBuilderT s t m a
223− -> ImmediateDomBuilderEnv t
380+ -> HydrationDomBuilderEnv t m
224 -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
381 -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
225 -> m a
382 -> m a
226−runImmediateDomBuilderT (ImmediateDomBuilderT a) env eventChan =
383+runHydrationDomBuilderT (HydrationDomBuilderT a) env = runDomRenderHookT (runReaderT a env)
227− flip runTriggerEventT eventChan $ do
228− rec (x, req) <- runRequesterT (runReaderT a env) rsp
229− rsp <- performEventAsync $ ffor req $ \rm f -> liftJSM $ runInAnimationFrame f $
230− traverseRequesterData (\r -> Identity <$> r) rm
231− return x
232− where
233− runInAnimationFrame f x = void . DOM.inAnimationFrame' $ \_ -> do
234− v <- synchronously x
235− void . liftIO $ f v
236
384
237−class Monad m => HasDocument m where
385+instance (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, Monad m) => HasDocument (HydrationDomBuilderT s t m) where
238− askDocument :: m Document
239− default askDocument :: (m ~ f m', MonadTrans f, Monad m', HasDocument m') => m Document
240− askDocument = lift askDocument
241−
242−instance HasDocument m => HasDocument (ReaderT r m)
243−instance HasDocument m => HasDocument (StateT s m)
244−instance HasDocument m => HasDocument (Lazy.StateT s m)
245−instance HasDocument m => HasDocument (EventWriterT t w m)
246−instance HasDocument m => HasDocument (DynamicWriterT t w m)
247−instance HasDocument m => HasDocument (PostBuildT t m)
248−instance HasDocument m => HasDocument (RequesterT t request response m)
249−instance HasDocument m => HasDocument (QueryT t q m)
250−
251−instance Monad m => HasDocument (ImmediateDomBuilderT t m) where
252 {-# INLINABLE askDocument #-}
386 {-# INLINABLE askDocument #-}
253− askDocument = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_document
387+ askDocument = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_document
254
388
255{-# INLINABLE askParent #-}
389{-# INLINABLE askParent #-}
256−askParent :: Monad m => ImmediateDomBuilderT t m Node
390+askParent :: Monad m => HydrationRunnerT t m DOM.Node
257−askParent = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_parent
391+askParent = HydrationRunnerT ask
392+
393+{-# INLINABLE getParent #-}
394+getParent :: MonadIO m => HydrationDomBuilderT s t m DOM.Node
395+getParent = either pure (liftIO . readIORef) =<< HydrationDomBuilderT (asks _hydrationDomBuilderEnv_parent)
258
396
259{-# INLINABLE askEvents #-}
397{-# INLINABLE askEvents #-}
260−askEvents :: Monad m => ImmediateDomBuilderT t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
398+askEvents :: Monad m => HydrationDomBuilderT s t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
261−askEvents = ImmediateDomBuilderT . lift . lift $ TriggerEventT.askEvents
399+askEvents = HydrationDomBuilderT . lift . DomRenderHookT . lift $ TriggerEventT.askEvents
262
400
263−localEnv :: Monad m => (ImmediateDomBuilderEnv t -> ImmediateDomBuilderEnv t) -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m a
401+{-# INLINABLE localEnv #-}
264−localEnv f = ImmediateDomBuilderT . local f . unImmediateDomBuilderT
402+localEnv :: Monad m => (HydrationDomBuilderEnv t m -> HydrationDomBuilderEnv t m) -> HydrationDomBuilderT s t m a -> HydrationDomBuilderT s t m a
403+localEnv f = HydrationDomBuilderT . local (f $!) . unHydrationDomBuilderT
265
404
266{-# INLINABLE append #-}
405{-# INLINABLE append #-}
267−append :: MonadJSM m => DOM.Node -> ImmediateDomBuilderT t m ()
406+append :: MonadJSM m => DOM.Node -> HydrationDomBuilderT s t m ()
268append n = do
407append n = do
269− p <- askParent
408+ p <- getParent
270 liftJSM $ appendChild_ p n
409 liftJSM $ appendChild_ p n
271 return ()
410 return ()
272
411
273−{-# INLINABLE textNodeInternal #-}
412+{-# SPECIALIZE append
274−textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
413+ :: DOM.Node
275−textNodeInternal !t = do
414+ -> HydrationDomBuilderT s Spider HydrationM ()
276− doc <- askDocument
415+ #-}
277− n <- liftJSM $ createTextNode doc t
416+
278− append $ toNode n
417+data HydrationMode
279− return n
418+ = HydrationMode_Hydrating
419+ -- ^ The time from initial load to parity with static builder
420+ | HydrationMode_Immediate
421+ -- ^ After hydration
422+ deriving (Eq, Ord, Show)
423+
424+{-# INLINABLE getPreviousNode #-}
425+getPreviousNode :: Monad m => HydrationRunnerT t m (Maybe DOM.Node)
426+getPreviousNode = HydrationRunnerT $ gets _hydrationState_previousNode
427+
428+{-# INLINABLE setPreviousNode #-}
429+setPreviousNode :: Monad m => Maybe DOM.Node -> HydrationRunnerT t m ()
430+setPreviousNode n = HydrationRunnerT $ modify' (\hs -> hs { _hydrationState_previousNode = n })
431+
432+{-# INLINABLE askUnreadyChildren #-}
433+askUnreadyChildren :: Monad m => HydrationDomBuilderT s t m (IORef Word)
434+askUnreadyChildren = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_unreadyChildren
435+
436+{-# INLINABLE askCommitAction #-}
437+askCommitAction :: Monad m => HydrationDomBuilderT s t m (JSM ())
438+askCommitAction = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_commitAction
439+
440+{-# INLINABLE getHydrationMode #-}
441+getHydrationMode :: MonadIO m => HydrationDomBuilderT s t m HydrationMode
442+getHydrationMode = liftIO . readIORef =<< HydrationDomBuilderT (asks _hydrationDomBuilderEnv_hydrationMode)
443+
444+-- | Remove all nodes after given node
445+removeSubsequentNodes :: (MonadJSM m, IsNode n) => n -> m ()
446+removeSubsequentNodes n = liftJSM $ do
447+ f <- eval ("(function(n) { while (n.nextSibling) { (n.parentNode).removeChild(n.nextSibling); }; })" :: Text)
448+ void $ call f f [n]
280
449
281-- | s and e must both be children of the same node and s must precede e;
450-- | s and e must both be children of the same node and s must precede e;
282-- all nodes between s and e will be removed, but s and e will not be removed
451-- all nodes between s and e will be removed, but s and e will not be removed
@@ -314,7 +483,7 @@ extractUpTo df s e = liftJSM $ do
314 void $ call f f (df, s, e)
483 void $ call f f (df, s, e)
315#endif
484#endif
316
485
317−type SupportsImmediateDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, Adjustable t m, PrimMonad m)
486+type SupportsHydrationDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, Adjustable t m, PrimMonad m, PerformEvent t m, MonadJSM (Performable m))
318
487
319{-# INLINABLE collectUpTo #-}
488{-# INLINABLE collectUpTo #-}
320collectUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m DOM.DocumentFragment
489collectUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m DOM.DocumentFragment
@@ -332,10 +501,15 @@ collectUpToGivenParent currentParent s e = do
332
501
333newtype EventFilterTriggerRef t er (en :: EventTag) = EventFilterTriggerRef (IORef (Maybe (EventTrigger t (er en))))
502newtype EventFilterTriggerRef t er (en :: EventTag) = EventFilterTriggerRef (IORef (Maybe (EventTrigger t (er en))))
334
503
335−{-# INLINABLE wrap #-}
504+-- | This 'wrap' is only partial: it doesn't create the 'EventSelector' itself
505+{-# INLINE wrap #-}
506+wrap
336−wrap :: forall m er t. (Reflex t, MonadFix m, MonadJSM m, MonadReflexCreateTrigger t m) => RawElement GhcjsDomSpace -> RawElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m (Element er GhcjsDomSpace t)
507+ :: forall s m er t. (Reflex t, MonadJSM m, MonadReflexCreateTrigger t m, DomRenderHook t m, EventSpec s ~ GhcjsEventSpec)
508+ => Chan [DSum (EventTriggerRef t) TriggerInvocation]
509+ -> DOM.Element
510+ -> RawElementConfig er t s
511+ -> m (DMap EventName (EventFilterTriggerRef t er))
337−wrap e cfg = do
512+wrap events e cfg = do
338− events <- askEvents
339 forM_ (_rawElementConfig_modifyAttributes cfg) $ \modifyAttrs -> requestDomAction_ $ ffor modifyAttrs $ imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
513 forM_ (_rawElementConfig_modifyAttributes cfg) $ \modifyAttrs -> requestDomAction_ $ ffor modifyAttrs $ imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
340 Nothing -> maybe (removeAttribute e n) (setAttribute e n) mv
514 Nothing -> maybe (removeAttribute e n) (setAttribute e n) mv
341 Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv
515 Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv
@@ -352,46 +526,74 @@ wrap e cfg = do
352 mv <- liftJSM k --TODO: Only do this when the event is subscribed
526 mv <- liftJSM k --TODO: Only do this when the event is subscribed
353 liftIO $ forM_ mv $ \v -> writeChan events [EventTriggerRef triggerRef :=> TriggerInvocation v (return ())]
527 liftIO $ forM_ mv $ \v -> writeChan events [EventTriggerRef triggerRef :=> TriggerInvocation v (return ())]
354 return $ en :=> EventFilterTriggerRef triggerRef
528 return $ en :=> EventFilterTriggerRef triggerRef
355− es <- do
529+ return eventTriggerRefs
356− let h :: GhcjsEventHandler er
530+
357− !h = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
531+{-# SPECIALIZE wrap
358− ctx <- askJSM
532+ :: Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
359− newFanEventWithTrigger $ \(WrapArg en) t ->
533+ -> DOM.Element
534+ -> RawElementConfig er DomTimeline HydrationDomSpace
535+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (DMap EventName (EventFilterTriggerRef DomTimeline er))
536+ #-}
537+
538+{-# SPECIALIZE wrap
539+ :: Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
540+ -> DOM.Element
541+ -> RawElementConfig er DomTimeline GhcjsDomSpace
542+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM (DMap EventName (EventFilterTriggerRef DomTimeline er))
543+ #-}
544+
545+{-# INLINE triggerBody #-}
546+triggerBody
547+ :: forall s er t x. EventSpec s ~ GhcjsEventSpec
548+ => DOM.JSContextRef
549+ -> RawElementConfig er t s
550+ -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
551+ -> DMap EventName (EventFilterTriggerRef t er)
552+ -> DOM.Element
553+ -> WrapArg er EventName x
554+ -> EventTrigger t x
555+ -> IO (IO ())
360− case DMap.lookup en eventTriggerRefs of
556+triggerBody ctx cfg events eventTriggerRefs e (WrapArg en) t = case DMap.lookup en eventTriggerRefs of
361− Just (EventFilterTriggerRef r) -> do
557+ Just (EventFilterTriggerRef r) -> do
362− writeIORef r $ Just t
558+ writeIORef r $ Just t
363− return $ do
559+ return $ do
364− writeIORef r Nothing
560+ writeIORef r Nothing
365− Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
561+ Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
366− evt <- DOM.event
562+ evt <- DOM.event
367− mv <- lift $ unGhcjsEventHandler h (en, GhcjsDomEvent evt)
563+ mv <- lift $ unGhcjsEventHandler handler (en, GhcjsDomEvent evt)
368− case mv of
564+ case mv of
369− Nothing -> return ()
565+ Nothing -> return ()
370− Just v -> liftIO $ do
566+ Just v -> liftIO $ do
371− --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
567+ --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
372− ref <- newIORef $ Just t
568+ ref <- newIORef $ Just t
373− writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
569+ writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
374− return $ Element
570+ where
375− { _element_events = es
571+ -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't
376− , _element_raw = e
572+ -- retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
377− }
573+ handler :: GhcjsEventHandler er
378−
574+ !handler = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg
379−{-# INLINABLE makeElement #-}
575+
380−makeElement :: forall er t m a. (MonadJSM m, MonadFix m, MonadReflexCreateTrigger t m, Adjustable t m) => Text -> ElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element)
576+{-# SPECIALIZE triggerBody
381−makeElement elementTag cfg child = do
577+ :: DOM.JSContextRef
382− doc <- askDocument
578+ -> RawElementConfig er DomTimeline HydrationDomSpace
383− e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
579+ -> Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
384− Nothing -> createElement doc elementTag
580+ -> DMap EventName (EventFilterTriggerRef DomTimeline er)
385− Just ens -> createElementNS doc (Just ens) elementTag
581+ -> DOM.Element
386− ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
582+ -> WrapArg er EventName x
387− Nothing -> lift $ setAttribute e n v
583+ -> EventTrigger DomTimeline x
388− Just ans -> lift $ setAttributeNS e (Just ans) n v
584+ -> IO (IO ())
389− result <- flip localEnv child $ \env -> env
585+ #-}
390− { _immediateDomBuilderEnv_parent = toNode e
586+
391− }
587+{-# SPECIALIZE triggerBody
392− append $ toNode e
588+ :: DOM.JSContextRef
393− wrapped <- wrap e $ extractRawElementConfig cfg
589+ -> RawElementConfig er DomTimeline GhcjsDomSpace
394− return ((wrapped, result), e)
590+ -> Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
591+ -> DMap EventName (EventFilterTriggerRef DomTimeline er)
592+ -> DOM.Element
593+ -> WrapArg er EventName x
594+ -> EventTrigger DomTimeline x
595+ -> IO (IO ())
596+ #-}
395
597
396newtype GhcjsDomHandler a b = GhcjsDomHandler { unGhcjsDomHandler :: a -> JSM b }
598newtype GhcjsDomHandler a b = GhcjsDomHandler { unGhcjsDomHandler :: a -> JSM b }
397
599
@@ -403,9 +605,10 @@ data GhcjsDomSpace
403
605
404instance DomSpace GhcjsDomSpace where
606instance DomSpace GhcjsDomSpace where
405 type EventSpec GhcjsDomSpace = GhcjsEventSpec
607 type EventSpec GhcjsDomSpace = GhcjsEventSpec
608+ type RawDocument GhcjsDomSpace = DOM.Document
406 type RawTextNode GhcjsDomSpace = DOM.Text
609 type RawTextNode GhcjsDomSpace = DOM.Text
610+ type RawCommentNode GhcjsDomSpace = DOM.Comment
407 type RawElement GhcjsDomSpace = DOM.Element
611 type RawElement GhcjsDomSpace = DOM.Element
408− type RawFile GhcjsDomSpace = DOM.File
409 type RawInputElement GhcjsDomSpace = DOM.HTMLInputElement
612 type RawInputElement GhcjsDomSpace = DOM.HTMLInputElement
410 type RawTextAreaElement GhcjsDomSpace = DOM.HTMLTextAreaElement
613 type RawTextAreaElement GhcjsDomSpace = DOM.HTMLTextAreaElement
411 type RawSelectElement GhcjsDomSpace = DOM.HTMLSelectElement
614 type RawSelectElement GhcjsDomSpace = DOM.HTMLSelectElement
@@ -437,15 +640,11 @@ data GhcjsEventSpec er = GhcjsEventSpec
437newtype GhcjsEventHandler er = GhcjsEventHandler { unGhcjsEventHandler :: forall en. (EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)) }
640newtype GhcjsEventHandler er = GhcjsEventHandler { unGhcjsEventHandler :: forall en. (EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)) }
438
641
439#ifndef USE_TEMPLATE_HASKELL
642#ifndef USE_TEMPLATE_HASKELL
440−phantom2 :: (Functor f, Contravariant f) => f a -> f b
441−phantom2 = phantom
442−{-# INLINE phantom2 #-}
443−
444ghcjsEventSpec_filters :: forall er . Lens' (GhcjsEventSpec er) (DMap EventName (GhcjsEventFilter er))
643ghcjsEventSpec_filters :: forall er . Lens' (GhcjsEventSpec er) (DMap EventName (GhcjsEventFilter er))
445ghcjsEventSpec_filters f (GhcjsEventSpec a b) = (\a' -> GhcjsEventSpec a' b) <$> f a
644ghcjsEventSpec_filters f (GhcjsEventSpec a b) = (\a' -> GhcjsEventSpec a' b) <$> f a
446{-# INLINE ghcjsEventSpec_filters #-}
645{-# INLINE ghcjsEventSpec_filters #-}
447ghcjsEventSpec_handler :: forall er en . Getter (GhcjsEventSpec er) ((EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)))
646ghcjsEventSpec_handler :: forall er en . Getter (GhcjsEventSpec er) ((EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)))
448−ghcjsEventSpec_handler f (GhcjsEventSpec _ (GhcjsEventHandler b)) = phantom2 (f b)
647+ghcjsEventSpec_handler f (GhcjsEventSpec _ (GhcjsEventHandler b)) = phantom (f b)
449{-# INLINE ghcjsEventSpec_handler #-}
648{-# INLINE ghcjsEventSpec_handler #-}
450#endif
649#endif
451
650
@@ -458,137 +657,753 @@ instance er ~ EventResult => Default (GhcjsEventSpec er) where
458 runReaderT (defaultDomEventHandler e en) evt
657 runReaderT (defaultDomEventHandler e en) evt
459 }
658 }
460
659
461−instance SupportsImmediateDomBuilder t m => NotReady t (ImmediateDomBuilderT t m) where
660+{-# INLINE makeElement #-}
661+makeElement :: MonadJSM m => Document -> Text -> ElementConfig er t s -> m DOM.Element
662+makeElement doc elementTag cfg = do
663+ e <- uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
664+ Nothing -> createElement doc elementTag
665+ Just ens -> createElementNS doc (Just ens) elementTag
666+ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
667+ Nothing -> setAttribute e n v
668+ Just ans -> setAttributeNS e (Just ans) n v
669+ pure e
670+
671+{-# INLINE elementImmediate #-}
672+elementImmediate
673+ :: ( RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, EventSpec s ~ GhcjsEventSpec
674+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m )
675+ => Text
676+ -> ElementConfig er t s
677+ -> HydrationDomBuilderT s t m a
678+ -> HydrationDomBuilderT s t m (Element er GhcjsDomSpace t, a)
679+elementImmediate elementTag cfg child = do
680+ doc <- askDocument
681+ ctx <- askJSM
682+ events <- askEvents
683+ parent <- getParent
684+ e <- makeElement doc elementTag cfg
685+ appendChild_ parent e
686+ -- Run the child builder with updated parent and previous sibling references
687+ result <- localEnv (\env -> env { _hydrationDomBuilderEnv_parent = Left $ toNode e }) child
688+ let rawCfg = extractRawElementConfig cfg
689+ eventTriggerRefs <- wrap events e rawCfg
690+ es <- newFanEventWithTrigger $ triggerBody ctx rawCfg events eventTriggerRefs e
691+ return (Element es e, result)
692+
693+{-# SPECIALIZE elementImmediate
694+ :: Text
695+ -> ElementConfig er DomTimeline HydrationDomSpace
696+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM a
697+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er GhcjsDomSpace DomTimeline, a)
698+ #-}
699+
700+{-# SPECIALIZE elementImmediate
701+ :: Text
702+ -> ElementConfig er DomTimeline GhcjsDomSpace
703+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM a
704+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM (Element er GhcjsDomSpace DomTimeline, a)
705+ #-}
706+
707+-- For specialisation
708+
709+-- | The Reflex timeline for interacting with the DOM
710+type DomTimeline =
711+#ifdef PROFILE_REFLEX
712+ ProfiledTimeline
713+#endif
714+ Spider
715+
716+-- | The ReflexHost the DOM lives in
717+type DomHost =
718+#ifdef PROFILE_REFLEX
719+ ProfiledM
720+#endif
721+ (SpiderHost Global)
722+
723+type DomCoreWidget x = PostBuildT DomTimeline (WithJSContextSingleton x (PerformEventT DomTimeline DomHost))
724+type HydrationM = DomCoreWidget ()
725+
726+{-# INLINE elementInternal #-}
727+elementInternal
728+ :: (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
729+ => Text
730+ -> ElementConfig er t HydrationDomSpace
731+ -> HydrationDomBuilderT HydrationDomSpace t m a
732+ -> HydrationDomBuilderT HydrationDomSpace t m (Element er HydrationDomSpace t, a)
733+elementInternal elementTag cfg child = getHydrationMode >>= \case
734+ HydrationMode_Immediate -> do
735+ (Element es _, result) <- elementImmediate elementTag cfg child
736+ return (Element es (), result)
737+ HydrationMode_Hydrating -> fst <$> hydrateElement elementTag cfg child
738+
739+{-# SPECIALIZE elementInternal
740+ :: Text
741+ -> ElementConfig er DomTimeline HydrationDomSpace
742+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM a
743+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
744+ #-}
745+
746+{-# INLINE hydrateElement #-}
747+hydrateElement
748+ :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
749+ => Text
750+ -> ElementConfig er t HydrationDomSpace
751+ -> HydrationDomBuilderT HydrationDomSpace t m a
752+ -> HydrationDomBuilderT HydrationDomSpace t m ((Element er HydrationDomSpace t, a), IORef DOM.Element)
753+hydrateElement elementTag cfg child = do
754+ ctx <- askJSM
755+ events <- askEvents
756+ -- Schedule everything for after postBuild, except for getting the result itself
757+ parentRef <- liftIO $ newIORef $ error "Parent not yet initialized"
758+ e' <- liftIO $ newIORef $ error "hydrateElement: Element not yet initialized"
759+ env <- HydrationDomBuilderT ask
760+ childDelayedRef <- liftIO $ newIORef $ pure ()
761+ let env' = env
762+ { _hydrationDomBuilderEnv_parent = Right parentRef
763+ , _hydrationDomBuilderEnv_delayed = childDelayedRef
764+ }
765+ result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
766+ wrapResult <- liftIO newEmptyMVar
767+ let skipAttr = "data-hydration-skip" :: DOM.JSString
768+ ssrAttr = "data-ssr" :: DOM.JSString
769+ shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
770+ shouldSkip e = case cfg ^. namespace of
771+ Nothing -> do
772+ skip <- hasAttribute e skipAttr
773+ ssr <- hasAttribute e ssrAttr
774+ pure $ skip || not ssr
775+ Just ns -> do
776+ skip <- hasAttributeNS e (Just ns) skipAttr
777+ ssr <- hasAttributeNS e (Just ns) ssrAttr
778+ pure $ skip || not ssr
779+ childDom <- liftIO $ readIORef childDelayedRef
780+ let rawCfg = extractRawElementConfig cfg
781+ doc <- askDocument
782+ addHydrationStep $ do
783+ parent <- askParent
784+ lastHydrationNode <- getPreviousNode
785+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
786+ Nothing -> do -- ran out of nodes, create the element
787+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
788+ e <- makeElement doc elementTag cfg
789+ insertAfterPreviousNode e
790+ pure e
791+ Just node -> DOM.castTo DOM.Element node >>= \case
792+ Nothing -> go (Just node) -- this node is not an element, skip
793+ Just e -> shouldSkip e >>= \case
794+ True -> go (Just node) -- this element is explicitly marked for being skipped by hydration
795+ False -> do
796+ t <- Element.getTagName e
797+ -- TODO: check attributes?
798+ if T.toCaseFold elementTag == T.toCaseFold t
799+ then pure e
800+ -- we came to some other statically rendered element, so something has gone wrong
801+ else do
802+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
803+ n <- makeElement doc elementTag cfg
804+ insertAfterPreviousNode n
805+ pure n
806+ e <- go lastHydrationNode
807+ setPreviousNode $ Just $ toNode e
808+ -- Update the parent node used by the children
809+ liftIO $ writeIORef parentRef $ toNode e
810+ liftIO $ writeIORef e' e
811+ -- Setup events, store the result so we can wait on it later
812+ refs <- wrap events e rawCfg
813+ liftIO $ putMVar wrapResult (e, refs)
814+ localRunner childDom Nothing $ toNode e
815+ -- We need the EventSelector to switch to the real event handler after activation
816+ es <- newFanEventWithTrigger $ \(WrapArg en) t -> do
817+ cleanup <- newEmptyMVar
818+ threadId <- forkIO $ do
819+ -- Wait on the data we need from the delayed action
820+ (e, eventTriggerRefs) <- readMVar wrapResult
821+ bracketOnError
822+ -- Run the setup, acquiring the cleanup action
823+ (triggerBody ctx rawCfg events eventTriggerRefs e (WrapArg en) t)
824+ -- Run the cleanup, if we have it - but only when an exception is
825+ -- raised (we might get killed between acquiring the cleanup action
826+ -- from 'triggerBody' and putting it in the MVar)
827+ id
828+ -- Try to put this action into the cleanup MVar
829+ (putMVar cleanup)
830+ pure $ do
831+ tryReadMVar cleanup >>= \case
832+ Nothing -> killThread threadId
833+ Just c -> c
834+ return ((Element es (), result), e')
835+
836+{-# SPECIALIZE hydrateElement
837+ :: Text
838+ -> ElementConfig er DomTimeline HydrationDomSpace
839+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM a
840+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM ((Element er HydrationDomSpace DomTimeline, a), IORef DOM.Element)
841+ #-}
842+
843+{-# INLINE inputElementImmediate #-}
844+inputElementImmediate
845+ :: ( RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, EventSpec s ~ GhcjsEventSpec
846+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
847+ , MonadRef m, Ref m ~ IORef )
848+ => InputElementConfig er t s -> HydrationDomBuilderT s t m (InputElement er GhcjsDomSpace t)
849+inputElementImmediate cfg = do
850+ (e@(Element eventSelector domElement), ()) <- elementImmediate "input" (_inputElementConfig_elementConfig cfg) $ return ()
851+ let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
852+ Input.setValue domInputElement $ cfg ^. inputElementConfig_initialValue
853+ v0 <- Input.getValue domInputElement
854+ let getMyValue = Input.getValue domInputElement
855+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select eventSelector (WrapArg Input)
856+ valueChangedBySetValue <- case _inputElementConfig_setValue cfg of
857+ Nothing -> return never
858+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
859+ Input.setValue domInputElement v'
860+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
861+ v <- holdDyn v0 $ leftmost
862+ [ valueChangedBySetValue
863+ , valueChangedByUI
864+ ]
865+ Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
866+ checkedChangedByUI <- wrapDomEvent domInputElement (`on` Events.click) $ do
867+ Input.getChecked domInputElement
868+ checkedChangedBySetChecked <- case _inputElementConfig_setChecked cfg of
869+ Nothing -> return never
870+ Just eNewchecked -> requestDomAction $ ffor eNewchecked $ \newChecked -> do
871+ oldChecked <- Input.getChecked domInputElement
872+ Input.setChecked domInputElement newChecked
873+ return $ if newChecked /= oldChecked
874+ then Just newChecked
875+ else Nothing
876+ c <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
877+ [ fmapMaybe id checkedChangedBySetChecked
878+ , checkedChangedByUI
879+ ]
880+ hasFocus <- mkHasFocus e
881+ files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
882+ mfiles <- Input.getFiles domInputElement
883+ let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
884+ maybe (return []) getMyFiles mfiles
885+ checked <- holdUniqDyn c
886+ return $ InputElement
887+ { _inputElement_value = v
888+ , _inputElement_checked = checked
889+ , _inputElement_checkedChange = checkedChangedByUI
890+ , _inputElement_input = valueChangedByUI
891+ , _inputElement_hasFocus = hasFocus
892+ , _inputElement_element = e
893+ , _inputElement_raw = domInputElement
894+ , _inputElement_files = files
895+ }
896+
897+{-# INLINE inputElementInternal #-}
898+inputElementInternal
899+ :: ( MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
900+ , MonadRef m, Ref m ~ IORef )
901+ => InputElementConfig er t HydrationDomSpace -> HydrationDomBuilderT HydrationDomSpace t m (InputElement er HydrationDomSpace t)
902+inputElementInternal cfg = getHydrationMode >>= \case
903+ HydrationMode_Immediate -> ffor (inputElementImmediate cfg) $ \result -> result
904+ { _inputElement_element = Element (_element_events $ _inputElement_element result) ()
905+ , _inputElement_raw = ()
906+ }
907+ HydrationMode_Hydrating -> do
908+ ((e, _), domElementRef) <- hydrateElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
909+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
910+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
911+ (focusChange, triggerFocusChange) <- newTriggerEvent
912+ (checkedChangedByUI, triggerCheckedChangedByUI) <- newTriggerEvent
913+ (checkedChangedBySetChecked, triggerCheckedChangedBySetChecked) <- newTriggerEvent
914+ (fileChange, triggerFileChange) <- newTriggerEvent
915+ doc <- askDocument
916+ -- Expected initial value from config
917+ let v0 = _inputElementConfig_initialValue cfg
918+ addHydrationStep $ do
919+ domElement <- liftIO $ readIORef domElementRef
920+ let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
921+ getValue = Input.getValue domInputElement
922+ -- The browser might have messed with the value, or the user could have
923+ -- altered it before activation, so we set it if it isn't what we expect
924+ liftJSM getValue >>= \v0' -> do
925+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
926+ -- Watch for user interaction and trigger event accordingly
927+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Input)
928+ for_ (_inputElementConfig_setValue cfg) $ \eSetValue ->
929+ requestDomAction_ $ ffor eSetValue $ \v' -> do
930+ Input.setValue domInputElement v'
931+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
932+ liftIO $ triggerChangeBySetValue v
933+ let focusChange' = leftmost
934+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
935+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
936+ ]
937+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
938+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
939+ Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
940+ _ <- liftJSM $ domInputElement `on` Events.click $ do
941+ liftIO . triggerCheckedChangedByUI =<< Input.getChecked domInputElement
942+ for_ (_inputElementConfig_setChecked cfg) $ \eNewchecked ->
943+ requestDomAction $ ffor eNewchecked $ \newChecked -> do
944+ oldChecked <- Input.getChecked domInputElement
945+ Input.setChecked domInputElement newChecked
946+ when (newChecked /= oldChecked) $ liftIO $ triggerCheckedChangedBySetChecked newChecked
947+ _ <- liftJSM $ domInputElement `on` Events.change $ do
948+ mfiles <- Input.getFiles domInputElement
949+ let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
950+ liftIO . triggerFileChange =<< maybe (return []) getMyFiles mfiles
951+ return ()
952+ checked' <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
953+ [ checkedChangedBySetChecked
954+ , checkedChangedByUI
955+ ]
956+ checked <- holdUniqDyn checked'
957+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
958+ hasFocus <- holdUniqDyn =<< holdDyn initialFocus focusChange
959+ v <- holdDyn v0 $ leftmost
960+ [ valueChangedBySetValue
961+ , valueChangedByUI
962+ ]
963+ files <- holdDyn mempty fileChange
964+ return $ InputElement
965+ { _inputElement_value = v
966+ , _inputElement_checked = checked
967+ , _inputElement_checkedChange = checkedChangedByUI
968+ , _inputElement_input = valueChangedByUI
969+ , _inputElement_hasFocus = hasFocus
970+ , _inputElement_element = e
971+ , _inputElement_raw = ()
972+ , _inputElement_files = files
973+ }
974+
975+{-# INLINE textAreaElementImmediate #-}
976+textAreaElementImmediate
977+ :: ( RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, EventSpec s ~ GhcjsEventSpec
978+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
979+ , MonadRef m, Ref m ~ IORef )
980+ => TextAreaElementConfig er t s -> HydrationDomBuilderT s t m (TextAreaElement er GhcjsDomSpace t)
981+textAreaElementImmediate cfg = do
982+ (e@(Element eventSelector domElement), _) <- elementImmediate "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
983+ let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
984+ TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
985+ v0 <- TextArea.getValue domTextAreaElement
986+ let getMyValue = TextArea.getValue domTextAreaElement
987+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select eventSelector (WrapArg Input)
988+ valueChangedBySetValue <- case _textAreaElementConfig_setValue cfg of
989+ Nothing -> return never
990+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
991+ TextArea.setValue domTextAreaElement v'
992+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
993+ v <- holdDyn v0 $ leftmost
994+ [ valueChangedBySetValue
995+ , valueChangedByUI
996+ ]
997+ hasFocus <- mkHasFocus e
998+ return $ TextAreaElement
999+ { _textAreaElement_value = v
1000+ , _textAreaElement_input = valueChangedByUI
1001+ , _textAreaElement_hasFocus = hasFocus
1002+ , _textAreaElement_element = e
1003+ , _textAreaElement_raw = domTextAreaElement
1004+ }
1005+
1006+{-# INLINE textAreaElementInternal #-}
1007+textAreaElementInternal
1008+ :: ( MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
1009+ , MonadRef m, Ref m ~ IORef )
1010+ => TextAreaElementConfig er t HydrationDomSpace -> HydrationDomBuilderT HydrationDomSpace t m (TextAreaElement er HydrationDomSpace t)
1011+textAreaElementInternal cfg = getHydrationMode >>= \case
1012+ HydrationMode_Immediate -> ffor (textAreaElementImmediate cfg) $ \result -> result
1013+ { _textAreaElement_element = Element (_element_events $ _textAreaElement_element result) ()
1014+ , _textAreaElement_raw = ()
1015+ }
1016+ HydrationMode_Hydrating -> do
1017+ ((e, _), domElementRef) <- hydrateElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
1018+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
1019+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
1020+ (focusChange, triggerFocusChange) <- newTriggerEvent
1021+ doc <- askDocument
1022+ -- Expected initial value from config
1023+ let v0 = _textAreaElementConfig_initialValue cfg
1024+ addHydrationStep $ do
1025+ domElement <- liftIO $ readIORef domElementRef
1026+ let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
1027+ getValue = TextArea.getValue domTextAreaElement
1028+ -- The browser might have messed with the value, or the user could have
1029+ -- altered it before activation, so we set it if it isn't what we expect
1030+ liftJSM getValue >>= \v0' -> do
1031+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
1032+ -- Watch for user interaction and trigger event accordingly
1033+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Input)
1034+ for_ (_textAreaElementConfig_setValue cfg) $ \eSetValue ->
1035+ requestDomAction_ $ ffor eSetValue $ \v' -> do
1036+ TextArea.setValue domTextAreaElement v'
1037+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
1038+ liftIO $ triggerChangeBySetValue v
1039+ let focusChange' = leftmost
1040+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
1041+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1042+ ]
1043+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
1044+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
1045+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
1046+ hasFocus <- holdUniqDyn =<< holdDyn initialFocus focusChange
1047+ v <- holdDyn v0 $ leftmost
1048+ [ valueChangedBySetValue
1049+ , valueChangedByUI
1050+ ]
1051+ return $ TextAreaElement
1052+ { _textAreaElement_value = v
1053+ , _textAreaElement_input = valueChangedByUI
1054+ , _textAreaElement_hasFocus = hasFocus
1055+ , _textAreaElement_element = e
1056+ , _textAreaElement_raw = ()
1057+ }
1058+
1059+{-# INLINE selectElementImmediate #-}
1060+selectElementImmediate
1061+ :: ( EventSpec s ~ GhcjsEventSpec, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document
1062+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m )
1063+ => SelectElementConfig er t s
1064+ -> HydrationDomBuilderT s t m a
1065+ -> HydrationDomBuilderT s t m (SelectElement er GhcjsDomSpace t, a)
1066+selectElementImmediate cfg child = do
1067+ (e@(Element eventSelector domElement), result) <- elementImmediate "select" (cfg ^. selectElementConfig_elementConfig) child
1068+ let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
1069+ Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
1070+ v0 <- Select.getValue domSelectElement
1071+ let getMyValue = Select.getValue domSelectElement
1072+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select eventSelector (WrapArg Change)
1073+ valueChangedBySetValue <- case _selectElementConfig_setValue cfg of
1074+ Nothing -> return never
1075+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
1076+ Select.setValue domSelectElement v'
1077+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
1078+ v <- holdDyn v0 $ leftmost
1079+ [ valueChangedBySetValue
1080+ , valueChangedByUI
1081+ ]
1082+ hasFocus <- mkHasFocus e
1083+ let wrapped = SelectElement
1084+ { _selectElement_value = v
1085+ , _selectElement_change = valueChangedByUI
1086+ , _selectElement_hasFocus = hasFocus
1087+ , _selectElement_element = e
1088+ , _selectElement_raw = domSelectElement
1089+ }
1090+ return (wrapped, result)
1091+
1092+{-# INLINE selectElementInternal #-}
1093+selectElementInternal
1094+ :: ( MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
1095+ , MonadRef m, Ref m ~ IORef )
1096+ => SelectElementConfig er t HydrationDomSpace
1097+ -> HydrationDomBuilderT HydrationDomSpace t m a
1098+ -> HydrationDomBuilderT HydrationDomSpace t m (SelectElement er HydrationDomSpace t, a)
1099+selectElementInternal cfg child = getHydrationMode >>= \case
1100+ HydrationMode_Immediate -> ffor (selectElementImmediate cfg child) $ \(e, result) -> (e
1101+ { _selectElement_element = Element (_element_events $ _selectElement_element e) ()
1102+ , _selectElement_raw = ()
1103+ }, result)
1104+ HydrationMode_Hydrating -> do
1105+ ((e, result), domElementRef) <- hydrateElement "select" (cfg ^. selectElementConfig_elementConfig) child
1106+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
1107+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
1108+ (focusChange, triggerFocusChange) <- newTriggerEvent
1109+ doc <- askDocument
1110+ -- Expected initial value from config
1111+ let v0 = _selectElementConfig_initialValue cfg
1112+ addHydrationStep $ do
1113+ domElement <- liftIO $ readIORef domElementRef
1114+ let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
1115+ getValue = Select.getValue domSelectElement
1116+ -- The browser might have messed with the value, or the user could have
1117+ -- altered it before activation, so we set it if it isn't what we expect
1118+ liftJSM getValue >>= \v0' -> do
1119+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
1120+ -- Watch for user interaction and trigger event accordingly
1121+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Change)
1122+ for_ (_selectElementConfig_setValue cfg) $ \eSetValue ->
1123+ requestDomAction_ $ ffor eSetValue $ \v' -> do
1124+ Select.setValue domSelectElement v'
1125+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
1126+ liftIO $ triggerChangeBySetValue v
1127+ let focusChange' = leftmost
1128+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
1129+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1130+ ]
1131+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
1132+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
1133+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
1134+ hasFocus <- holdUniqDyn =<< holdDyn initialFocus focusChange
1135+ v <- holdDyn v0 $ leftmost
1136+ [ valueChangedBySetValue
1137+ , valueChangedByUI
1138+ ]
1139+ return $ (,result) $ SelectElement
1140+ { _selectElement_value = v
1141+ , _selectElement_change = valueChangedByUI
1142+ , _selectElement_hasFocus = hasFocus
1143+ , _selectElement_element = e
1144+ , _selectElement_raw = ()
1145+ }
1146+
1147+{-# INLINE textNodeImmediate #-}
1148+textNodeImmediate
1149+ :: (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, MonadJSM m, Reflex t, MonadFix m)
1150+ => TextNodeConfig t -> HydrationDomBuilderT s t m DOM.Text
1151+textNodeImmediate (TextNodeConfig !t mSetContents) = do
1152+ p <- getParent
1153+ doc <- askDocument
1154+ n <- createTextNode doc t
1155+ appendChild_ p n
1156+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1157+ pure n
1158+
1159+{-# SPECIALIZE textNodeImmediate
1160+ :: TextNodeConfig DomTimeline
1161+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM DOM.Text
1162+ #-}
1163+
1164+{-# SPECIALIZE textNodeImmediate
1165+ :: TextNodeConfig DomTimeline
1166+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM DOM.Text
1167+ #-}
1168+
1169+{-# INLINE textNodeInternal #-}
1170+textNodeInternal
1171+ :: (Adjustable t m, MonadHold t m, MonadJSM m, MonadFix m, Reflex t)
1172+ => TextNodeConfig t -> HydrationDomBuilderT HydrationDomSpace t m (TextNode HydrationDomSpace t)
1173+textNodeInternal tc@(TextNodeConfig !t mSetContents) = do
1174+ doc <- askDocument
1175+ getHydrationMode >>= \case
1176+ HydrationMode_Immediate -> void $ textNodeImmediate tc
1177+ HydrationMode_Hydrating -> addHydrationStepWithSetup (maybe (pure $ pure t) (hold t) mSetContents) $ \currentText -> do
1178+ n <- hydrateTextNode doc =<< sample currentText
1179+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1180+ pure $ TextNode ()
1181+
1182+{-# SPECIALIZE textNodeInternal
1183+ :: TextNodeConfig DomTimeline
1184+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (TextNode HydrationDomSpace DomTimeline)
1185+ #-}
1186+
1187+-- | The static builder mashes adjacent text nodes into one node: we check the
1188+-- text content of each node we come to, comparing it to the content we
1189+-- expect. We also have a special case for empty text nodes - we always create
1190+-- the and add them after the previous node reference.
1191+{-# INLINE hydrateTextNode #-}
1192+hydrateTextNode :: MonadJSM m => Document -> Text -> HydrationRunnerT t m DOM.Text
1193+hydrateTextNode doc t@"" = do
1194+ tn <- createTextNode doc t
1195+ insertAfterPreviousNode tn
1196+ pure tn
1197+hydrateTextNode doc t = do
1198+ n <- join $ go <$> askParent <*> getPreviousNode
1199+ setPreviousNode $ Just $ toNode n
1200+ return n
1201+ where
1202+ go parent mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1203+ Nothing -> do
1204+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1205+ n <- createTextNode doc t
1206+ insertAfterPreviousNode n
1207+ pure n
1208+ Just node -> DOM.castTo DOM.Text node >>= \case
1209+ Nothing -> go parent $ Just node
1210+ Just originalNode -> do
1211+ originalText <- Node.getTextContentUnchecked originalNode
1212+ case T.stripPrefix t originalText of
1213+ Just "" -> return originalNode
1214+ Just _ -> do
1215+ -- If we have the right prefix, we split the text node into a node containing the
1216+ -- required text and a subsequent sibling node containing the rest of the text.
1217+ DOM.splitText_ originalNode $ fromIntegral $ T.length t
1218+ return originalNode
1219+ Nothing -> do
1220+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1221+ n <- createTextNode doc t
1222+ insertAfterPreviousNode n
1223+ pure n
1224+
1225+{-# INLINE commentNodeImmediate #-}
1226+commentNodeImmediate
1227+ :: (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, MonadJSM m, Reflex t, MonadFix m)
1228+ => CommentNodeConfig t -> HydrationDomBuilderT s t m DOM.Comment
1229+commentNodeImmediate (CommentNodeConfig !t mSetContents) = do
1230+ p <- getParent
1231+ doc <- askDocument
1232+ n <- createComment doc t
1233+ appendChild_ p n
1234+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1235+ pure n
1236+
1237+{-# INLINE commentNodeInternal #-}
1238+commentNodeInternal
1239+ :: (Ref m ~ IORef, MonadRef m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadJSM (Performable m), MonadJSM m, MonadFix m, Reflex t, Adjustable t m, MonadHold t m, MonadSample t m)
1240+ => CommentNodeConfig t -> HydrationDomBuilderT HydrationDomSpace t m (CommentNode HydrationDomSpace t)
1241+commentNodeInternal tc@(CommentNodeConfig t0 mSetContents) = do
1242+ doc <- askDocument
1243+ getHydrationMode >>= \case
1244+ HydrationMode_Immediate -> void $ commentNodeInternal tc
1245+ HydrationMode_Hydrating -> addHydrationStepWithSetup (maybe (pure $ pure t0) (hold t0) mSetContents) $ \bt -> do
1246+ t <- sample bt
1247+ void $ hydrateComment doc t mSetContents
1248+ pure $ CommentNode ()
1249+
1250+{-# INLINE hydrateComment #-}
1251+hydrateComment :: (MonadJSM m, Reflex t, MonadFix m) => Document -> Text -> Maybe (Event t Text) -> HydrationRunnerT t m DOM.Comment
1252+hydrateComment doc t mSetContents = do
1253+ parent <- askParent
1254+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1255+ Nothing -> do
1256+ c <- createComment doc t
1257+ insertAfterPreviousNode c
1258+ pure c
1259+ Just node -> DOM.castTo DOM.Comment node >>= \case
1260+ Nothing -> go (Just node)
1261+ Just c -> do
1262+ t' <- Node.getTextContentUnchecked c
1263+ if t == t'
1264+ then pure c
1265+ else do
1266+ c' <- createComment doc t
1267+ insertAfterPreviousNode c'
1268+ pure c'
1269+ n <- go =<< getPreviousNode
1270+ setPreviousNode $ Just $ toNode n
1271+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1272+ pure n
1273+
1274+-- | We leave markers in the static builder as comments, and rip these comments
1275+-- out at hydration time, replacing them with empty text nodes.
1276+{-# INLINABLE skipToAndReplaceComment #-}
1277+skipToAndReplaceComment
1278+ :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1279+ => Text
1280+ -> IORef Text
1281+ -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1282+skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1283+ HydrationMode_Immediate -> do
1284+ -- If we're in immediate mode, we don't try to replace an existing comment,
1285+ -- and just return a dummy key
1286+ t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
1287+ append $ toNode t
1288+ textNodeRef <- liftIO $ newIORef t
1289+ keyRef <- liftIO $ newIORef ""
1290+ pure (pure (), textNodeRef, keyRef)
1291+ HydrationMode_Hydrating -> do
1292+ doc <- askDocument
1293+ textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
1294+ keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1295+ let go key0 mLastNode = do
1296+ parent <- askParent
1297+ node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
1298+ DOM.castTo DOM.Comment node >>= \case
1299+ Just comment -> do
1300+ commentText <- Node.getTextContentUnchecked comment
1301+ case T.stripPrefix (prefix <> key0) commentText of
1302+ Just key -> do
1303+ -- Replace the comment with an (invisible) text node
1304+ tn <- createTextNode doc ("" :: Text)
1305+ Node.replaceChild_ parent tn comment
1306+ pure (tn, key)
1307+ Nothing -> do
1308+ go key0 (Just node)
1309+ Nothing -> do
1310+ go key0 (Just node)
1311+ switchComment = do
1312+ key0 <- liftIO $ readIORef key0Ref
1313+ (tn, key) <- go key0 =<< getPreviousNode
1314+ setPreviousNode $ Just $ toNode tn
1315+ liftIO $ do
1316+ writeIORef textNodeRef tn
1317+ writeIORef keyRef key
1318+ pure (switchComment, textNodeRef, keyRef)
1319+
1320+{-# INLINABLE skipToReplaceStart #-}
1321+skipToReplaceStart :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1322+skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef "")
1323+
1324+{-# INLINABLE skipToReplaceEnd #-}
1325+skipToReplaceEnd :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => IORef Text -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text)
1326+skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
1327+
1328+instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
462 notReadyUntil e = do
1329 notReadyUntil e = do
463 eOnce <- headE e
1330 eOnce <- headE e
464− env <- ImmediateDomBuilderT ask
1331+ unreadyChildren <- askUnreadyChildren
465− let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
1332+ commitAction <- askCommitAction
466 liftIO $ modifyIORef' unreadyChildren succ
1333 liftIO $ modifyIORef' unreadyChildren succ
467 let ready = do
1334 let ready = do
468 old <- liftIO $ readIORef unreadyChildren
1335 old <- liftIO $ readIORef unreadyChildren
469 let new = pred old
1336 let new = pred old
470 liftIO $ writeIORef unreadyChildren $! new
1337 liftIO $ writeIORef unreadyChildren $! new
471− when (new == 0) $ _immediateDomBuilderEnv_commitAction env
1338+ when (new == 0) commitAction
472 requestDomAction_ $ ready <$ eOnce
1339 requestDomAction_ $ ready <$ eOnce
473 notReady = do
1340 notReady = do
474− env <- ImmediateDomBuilderT ask
1341+ unreadyChildren <- askUnreadyChildren
475− let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
476 liftIO $ modifyIORef' unreadyChildren succ
1342 liftIO $ modifyIORef' unreadyChildren succ
477
1343
478−instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t m) where
1344+data HydrationDomSpace
479− type DomBuilderSpace (ImmediateDomBuilderT t m) = GhcjsDomSpace
1345+
1346+instance DomSpace HydrationDomSpace where
1347+ type EventSpec HydrationDomSpace = GhcjsEventSpec
1348+ type RawDocument HydrationDomSpace = DOM.Document
1349+ type RawTextNode HydrationDomSpace = ()
1350+ type RawCommentNode HydrationDomSpace = ()
1351+ type RawElement HydrationDomSpace = ()
1352+ type RawInputElement HydrationDomSpace = ()
1353+ type RawTextAreaElement HydrationDomSpace = ()
1354+ type RawSelectElement HydrationDomSpace = ()
1355+ addEventSpecFlags _ en f es = es
1356+ { _ghcjsEventSpec_filters =
1357+ let f' = Just . GhcjsEventFilter . \case
1358+ Nothing -> \evt -> do
1359+ mEventResult <- unGhcjsEventHandler (_ghcjsEventSpec_handler es) (en, evt)
1360+ return (f mEventResult, return mEventResult)
1361+ Just (GhcjsEventFilter oldFilter) -> \evt -> do
1362+ (oldFlags, oldContinuation) <- oldFilter evt
1363+ mEventResult <- oldContinuation
1364+ let newFlags = oldFlags <> f mEventResult
1365+ return (newFlags, return mEventResult)
1366+ in DMap.alter f' en $ _ghcjsEventSpec_filters es
1367+ }
1368+
1369+instance SupportsHydrationDomBuilder t m => DomBuilder t (HydrationDomBuilderT HydrationDomSpace t m) where
1370+ type DomBuilderSpace (HydrationDomBuilderT HydrationDomSpace t m) = HydrationDomSpace
1371+ {-# INLINABLE element #-}
1372+ element = elementInternal
480 {-# INLINABLE textNode #-}
1373 {-# INLINABLE textNode #-}
481− textNode (TextNodeConfig initialContents mSetContents) = do
482− n <- textNodeInternal initialContents
1374+ textNode = textNodeInternal
483− mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1375+ {-# INLINABLE commentNode #-}
484− return $ TextNode n
1376+ commentNode = commentNodeInternal
1377+ {-# INLINABLE inputElement #-}
1378+ inputElement = inputElementInternal
1379+ {-# INLINABLE textAreaElement #-}
1380+ textAreaElement = textAreaElementInternal
1381+ {-# INLINABLE selectElement #-}
1382+ selectElement = selectElementInternal
1383+ placeRawElement () = pure ()
1384+ wrapRawElement () _cfg = pure $ Element (EventSelector $ const never) ()
1385+
1386+instance SupportsHydrationDomBuilder t m => DomBuilder t (HydrationDomBuilderT GhcjsDomSpace t m) where
1387+ type DomBuilderSpace (HydrationDomBuilderT GhcjsDomSpace t m) = GhcjsDomSpace
485 {-# INLINABLE element #-}
1388 {-# INLINABLE element #-}
486− element elementTag cfg child = fst <$> makeElement elementTag cfg child
1389+ element = elementImmediate
1390+ {-# INLINABLE textNode #-}
1391+ textNode = fmap TextNode . textNodeImmediate
1392+ {-# INLINABLE commentNode #-}
1393+ commentNode = fmap CommentNode . commentNodeImmediate
487 {-# INLINABLE inputElement #-}
1394 {-# INLINABLE inputElement #-}
488− inputElement cfg = do
1395+ inputElement = inputElementImmediate
489− ((e, _), domElement) <- makeElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
490− let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
491− Input.setValue domInputElement $ cfg ^. inputElementConfig_initialValue
492− v0 <- Input.getValue domInputElement
493− let getMyValue = Input.getValue domInputElement
494− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
495− valueChangedBySetValue <- case _inputElementConfig_setValue cfg of
496− Nothing -> return never
497− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
498− Input.setValue domInputElement v'
499− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
500− v <- holdDyn v0 $ leftmost
501− [ valueChangedBySetValue
502− , valueChangedByUI
503− ]
504− Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
505− checkedChangedByUI <- wrapDomEvent domInputElement (`on` Events.click) $ do
506− Input.getChecked domInputElement
507− checkedChangedBySetChecked <- case _inputElementConfig_setChecked cfg of
508− Nothing -> return never
509− Just eNewchecked -> requestDomAction $ ffor eNewchecked $ \newChecked -> do
510− oldChecked <- Input.getChecked domInputElement
511− Input.setChecked domInputElement newChecked
512− return $ if newChecked /= oldChecked
513− then Just newChecked
514− else Nothing
515− c <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
516− [ fmapMaybe id checkedChangedBySetChecked
517− , checkedChangedByUI
518− ]
519− let initialFocus = False --TODO: Is this correct?
520− hasFocus <- holdDyn initialFocus $ leftmost
521− [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
522− , True <$ Reflex.select (_element_events e) (WrapArg Focus)
523− ]
524− files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
525− mfiles <- Input.getFiles domInputElement
526− let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
527− maybe (return []) getMyFiles mfiles
528− checked <- holdUniqDyn c
529− return $ InputElement
530− { _inputElement_value = v
531− , _inputElement_checked = checked
532− , _inputElement_checkedChange = checkedChangedByUI
533− , _inputElement_input = valueChangedByUI
534− , _inputElement_hasFocus = hasFocus
535− , _inputElement_element = e
536− , _inputElement_raw = domInputElement
537− , _inputElement_files = files
538− }
539 {-# INLINABLE textAreaElement #-}
1396 {-# INLINABLE textAreaElement #-}
540− textAreaElement cfg = do --TODO
1397+ textAreaElement = textAreaElementImmediate
541− ((e, _), domElement) <- makeElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
542− let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
543− TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
544− v0 <- TextArea.getValue domTextAreaElement
545− let getMyValue = TextArea.getValue domTextAreaElement
546− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
547− valueChangedBySetValue <- case _textAreaElementConfig_setValue cfg of
548− Nothing -> return never
549− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
550− TextArea.setValue domTextAreaElement v'
551− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
552− v <- holdDyn v0 $ leftmost
553− [ valueChangedBySetValue
554− , valueChangedByUI
555− ]
556− hasFocus <- mkHasFocus e
557− return $ TextAreaElement
558− { _textAreaElement_value = v
559− , _textAreaElement_input = valueChangedByUI
560− , _textAreaElement_hasFocus = hasFocus
561− , _textAreaElement_element = e
562− , _textAreaElement_raw = domTextAreaElement
563− }
564 {-# INLINABLE selectElement #-}
1398 {-# INLINABLE selectElement #-}
565− selectElement cfg child = do
1399+ selectElement = selectElementImmediate
566− ((e, result), domElement) <- makeElement "select" (cfg ^. selectElementConfig_elementConfig) child
567− let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
568− Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
569− v0 <- Select.getValue domSelectElement
570− let getMyValue = Select.getValue domSelectElement
571− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Change)
572− valueChangedBySetValue <- case _selectElementConfig_setValue cfg of
573− Nothing -> return never
574− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
575− Select.setValue domSelectElement v'
576− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
577− v <- holdDyn v0 $ leftmost
578− [ valueChangedBySetValue
579− , valueChangedByUI
580− ]
581− hasFocus <- mkHasFocus e
582− let wrapped = SelectElement
583− { _selectElement_value = v
584− , _selectElement_change = valueChangedByUI
585− , _selectElement_hasFocus = hasFocus
586− , _selectElement_element = e
587− , _selectElement_raw = domSelectElement
588− }
589− return (wrapped, result)
590 placeRawElement = append . toNode
1400 placeRawElement = append . toNode
591− wrapRawElement = wrap
1401+ wrapRawElement e rawCfg = do
1402+ events <- askEvents
1403+ ctx <- askJSM
1404+ eventTriggerRefs <- wrap events e rawCfg
1405+ es <- newFanEventWithTrigger $ triggerBody ctx rawCfg events eventTriggerRefs e
1406+ pure $ Element es e
592
1407
593data FragmentState
1408data FragmentState
594 = FragmentState_Unmounted
1409 = FragmentState_Unmounted
@@ -608,21 +1423,21 @@ extractFragment fragment = do
608 extractBetweenExclusive (_immediateDomFragment_document fragment) before after
1423 extractBetweenExclusive (_immediateDomFragment_document fragment) before after
609 liftIO $ writeIORef (_immediateDomFragment_state fragment) FragmentState_Unmounted
1424 liftIO $ writeIORef (_immediateDomFragment_state fragment) FragmentState_Unmounted
610
1425
611−instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomBuilderT t m) where
1426+instance SupportsHydrationDomBuilder t m => MountableDomBuilder t (HydrationDomBuilderT GhcjsDomSpace t m) where
612− type DomFragment (ImmediateDomBuilderT t m) = ImmediateDomFragment
1427+ type DomFragment (HydrationDomBuilderT GhcjsDomSpace t m) = ImmediateDomFragment
613 buildDomFragment w = do
1428 buildDomFragment w = do
614 df <- createDocumentFragment =<< askDocument
1429 df <- createDocumentFragment =<< askDocument
615 result <- flip localEnv w $ \env -> env
1430 result <- flip localEnv w $ \env -> env
616− { _immediateDomBuilderEnv_parent = toNode df
1431+ { _hydrationDomBuilderEnv_parent = Left $ toNode df
617 }
1432 }
618 state <- liftIO $ newIORef FragmentState_Unmounted
1433 state <- liftIO $ newIORef FragmentState_Unmounted
619 return (ImmediateDomFragment df state, result)
1434 return (ImmediateDomFragment df state, result)
620 mountDomFragment fragment setFragment = do
1435 mountDomFragment fragment setFragment = do
621− parent <- askParent
1436+ parent <- getParent
622 extractFragment fragment
1437 extractFragment fragment
623− before <- textNodeInternal ("" :: Text)
1438+ before <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
624 appendChild_ parent $ _immediateDomFragment_document fragment
1439 appendChild_ parent $ _immediateDomFragment_document fragment
625− after <- textNodeInternal ("" :: Text)
1440+ after <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
626 xs <- foldDyn (\new (previous, _) -> (new, Just previous)) (fragment, Nothing) setFragment
1441 xs <- foldDyn (\new (previous, _) -> (new, Just previous)) (fragment, Nothing) setFragment
627 requestDomAction_ $ ffor (updated xs) $ \(childFragment, Just previousFragment) -> do
1442 requestDomAction_ $ ffor (updated xs) $ \(childFragment, Just previousFragment) -> do
628 extractFragment previousFragment
1443 extractFragment previousFragment
@@ -631,12 +1446,19 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB
631 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
1446 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
632 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
1447 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
633
1448
1449+instance (Reflex t, Monad m, Adjustable t m, MonadHold t m, MonadFix m) => Adjustable t (DomRenderHookT t m) where
1450+ runWithReplace a0 a' = DomRenderHookT $ runWithReplace (unDomRenderHookT a0) (fmapCheap unDomRenderHookT a')
1451+ traverseIntMapWithKeyWithAdjust f m = DomRenderHookT . traverseIntMapWithKeyWithAdjust (\k -> unDomRenderHookT . f k) m
1452+ traverseDMapWithKeyWithAdjust f m = DomRenderHookT . traverseDMapWithKeyWithAdjust (\k -> unDomRenderHookT . f k) m
1453+ traverseDMapWithKeyWithAdjustWithMove f m = DomRenderHookT . traverseDMapWithKeyWithAdjustWithMove (\k -> unDomRenderHookT . f k) m
1454+
634−instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
1455+instance (Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => Adjustable t (HydrationDomBuilderT s t m) where
635 {-# INLINABLE runWithReplace #-}
1456 {-# INLINABLE runWithReplace #-}
636 runWithReplace a0 a' = do
1457 runWithReplace a0 a' = do
637− initialEnv <- ImmediateDomBuilderT ask
1458+ initialEnv <- HydrationDomBuilderT ask
638− before <- textNodeInternal ("" :: Text)
1459+ let hydrating = _hydrationDomBuilderEnv_hydrationMode initialEnv
1460+ (hydrateStart, before, beforeKey) <- skipToReplaceStart
639− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
1461+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
640 haveEverBeenReady <- liftIO $ newIORef False
1462 haveEverBeenReady <- liftIO $ newIORef False
641 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
1463 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
642 let myCommitAction = do
1464 let myCommitAction = do
@@ -647,79 +1469,117 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
647 old <- liftIO $ readIORef parentUnreadyChildren
1469 old <- liftIO $ readIORef parentUnreadyChildren
648 let new = pred old
1470 let new = pred old
649 liftIO $ writeIORef parentUnreadyChildren $! new
1471 liftIO $ writeIORef parentUnreadyChildren $! new
650− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
1472+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
651 -- We draw 'after' in this roundabout way to avoid using MonadFix
1473 -- We draw 'after' in this roundabout way to avoid using MonadFix
652 doc <- askDocument
1474 doc <- askDocument
653− after <- createTextNode doc ("" :: Text)
1475+ parent <- getParent
1476+ (hydrateEnd, after) <- skipToReplaceEnd beforeKey
654 let drawInitialChild = do
1477 let drawInitialChild = do
1478+ h <- liftIO $ readIORef hydrating
1479+ p' <- case h of
1480+ HydrationMode_Hydrating -> pure parent
1481+ HydrationMode_Immediate -> toNode <$> createDocumentFragment doc
655 unreadyChildren <- liftIO $ newIORef 0
1482 unreadyChildren <- liftIO $ newIORef 0
656− let f = do
1483+ let a0' = case h of
657− result <- a0
1484+ HydrationMode_Hydrating -> a0
658− append $ toNode after
1485+ HydrationMode_Immediate -> do
659− return result
1486+ a <- a0
1487+ insertBefore p' =<< liftIO (readIORef after)
1488+ pure a
1489+ delayed <- case h of
1490+ HydrationMode_Hydrating -> liftIO $ newIORef $ pure ()
1491+ HydrationMode_Immediate -> pure $ _hydrationDomBuilderEnv_delayed initialEnv
660− result <- runReaderT (unImmediateDomBuilderT f) $ initialEnv
1492+ result <- runReaderT (unHydrationDomBuilderT a0') initialEnv
661− { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
1493+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
662− , _immediateDomBuilderEnv_commitAction = myCommitAction
1494+ , _hydrationDomBuilderEnv_commitAction = myCommitAction
1495+ , _hydrationDomBuilderEnv_parent = Left p'
1496+ , _hydrationDomBuilderEnv_delayed = delayed
663 }
1497 }
1498+ dom <- case h of
1499+ HydrationMode_Hydrating -> liftIO $ readIORef delayed
1500+ HydrationMode_Immediate -> pure $ pure ()
664 liftIO $ readIORef unreadyChildren >>= \case
1501 liftIO $ readIORef unreadyChildren >>= \case
665 0 -> writeIORef haveEverBeenReady True
1502 0 -> writeIORef haveEverBeenReady True
666 _ -> modifyIORef' parentUnreadyChildren succ
1503 _ -> modifyIORef' parentUnreadyChildren succ
667− return result
1504+ return (dom, result)
668 a'' <- numberOccurrences a'
1505 a'' <- numberOccurrences a'
669− (result0, child') <- ImmediateDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do
1506+ ((hydrate0, result0), child') <- HydrationDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do
1507+ h <- liftIO $ readIORef hydrating
1508+ p' <- case h of
1509+ HydrationMode_Hydrating -> pure parent
670− df <- createDocumentFragment doc
1510+ HydrationMode_Immediate -> toNode <$> createDocumentFragment doc
671 unreadyChildren <- liftIO $ newIORef 0
1511 unreadyChildren <- liftIO $ newIORef 0
672 let commitAction = do
1512 let commitAction = do
673 c <- liftIO $ readIORef currentCohort
1513 c <- liftIO $ readIORef currentCohort
674 when (c <= cohortId) $ do -- If a newer cohort has already been committed, just ignore this
1514 when (c <= cohortId) $ do -- If a newer cohort has already been committed, just ignore this
1515+ !before' <- liftIO $ readIORef before
1516+ !after' <- liftIO $ readIORef after
675− deleteBetweenExclusive before after
1517+ deleteBetweenExclusive before' after'
676− insertBefore df after
1518+ insertBefore p' after'
677 liftIO $ writeIORef currentCohort cohortId
1519 liftIO $ writeIORef currentCohort cohortId
678 myCommitAction
1520 myCommitAction
1521+ delayed <- case h of
1522+ HydrationMode_Hydrating -> liftIO $ newIORef $ pure ()
1523+ HydrationMode_Immediate -> pure $ _hydrationDomBuilderEnv_delayed initialEnv
679− result <- runReaderT (unImmediateDomBuilderT child) $ initialEnv
1524+ result <- runReaderT (unHydrationDomBuilderT child) $ initialEnv
680− { _immediateDomBuilderEnv_parent = toNode df
1525+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
681− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
1526+ , _hydrationDomBuilderEnv_commitAction = case h of
682− , _immediateDomBuilderEnv_commitAction = commitAction
1527+ HydrationMode_Hydrating -> myCommitAction
1528+ HydrationMode_Immediate -> commitAction
1529+ , _hydrationDomBuilderEnv_parent = Left p'
1530+ , _hydrationDomBuilderEnv_delayed = delayed
683− }
1531+ }
1532+ dom <- case h of
1533+ HydrationMode_Hydrating -> liftIO $ readIORef delayed
1534+ HydrationMode_Immediate -> pure $ pure ()
684 uc <- liftIO $ readIORef unreadyChildren
1535 uc <- liftIO $ readIORef unreadyChildren
685 let commitActionToRunNow = if uc == 0
1536 let commitActionToRunNow = if uc == 0
686− then Just commitAction
1537+ then Just $ commitAction
687 else Nothing -- A child will run it when unreadyChildren is decremented to 0
1538 else Nothing -- A child will run it when unreadyChildren is decremented to 0
1539+ actions = case h of
1540+ HydrationMode_Hydrating -> Left dom
688− return (commitActionToRunNow, result)
1541+ HydrationMode_Immediate -> Right commitActionToRunNow
1542+ return (actions, result)
1543+ let (hydrate', commitAction) = fanEither $ fmap fst child'
1544+ addHydrationStepWithSetup (hold hydrate0 hydrate') $ \contents -> do
1545+ hydrateStart
1546+ join $ sample contents
1547+ hydrateEnd
689− requestDomAction_ $ fmapMaybe fst child'
1548+ requestDomAction_ $ fmapMaybe id commitAction
690 return (result0, snd <$> child')
1549 return (result0, snd <$> child')
1550+
691 {-# INLINABLE traverseIntMapWithKeyWithAdjust #-}
1551 {-# INLINABLE traverseIntMapWithKeyWithAdjust #-}
692 traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjust'
1552 traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjust'
693 {-# INLINABLE traverseDMapWithKeyWithAdjust #-}
1553 {-# INLINABLE traverseDMapWithKeyWithAdjust #-}
694 traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjust'
1554 traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjust'
695 {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-}
1555 {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-}
696 traverseDMapWithKeyWithAdjustWithMove = do
1556 traverseDMapWithKeyWithAdjustWithMove = do
697− let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do
1557+ let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose (TraverseChild t m (Some k)) v')) old = do
698− let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState k))) a)
1558+ let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState (Some k)))) a)
699 new k = PatchDMapWithMove.nodeInfoMapFromM $ \case
1559 new k = PatchDMapWithMove.nodeInfoMapFromM $ \case
700− PatchDMapWithMove.From_Insert (Compose (_, _, sRef, _)) -> do
1560+ PatchDMapWithMove.From_Insert (Compose (TraverseChild (Left _hydration) _)) -> return PatchDMapWithMove.From_Delete
701− readIORef sRef >>= \case
1561+ PatchDMapWithMove.From_Insert (Compose (TraverseChild (Right immediate) _)) -> do
1562+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
702 ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete
1563 ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete
703 ChildReadyState_Unready _ -> do
1564 ChildReadyState_Unready _ -> do
704− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
1565+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ Some k
705− return $ PatchDMapWithMove.From_Insert $ Constant sRef
1566+ return $ PatchDMapWithMove.From_Insert $ Constant (_traverseChildImmediate_childReadyState immediate)
706 PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete
1567 PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete
707 PatchDMapWithMove.From_Move fromKey -> return $ PatchDMapWithMove.From_Move fromKey
1568 PatchDMapWithMove.From_Move fromKey -> return $ PatchDMapWithMove.From_Move fromKey
708− deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState k))) (ComposeMaybe k) a -> IO (Constant () a)
1569+ deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState (Some k)))) (ComposeMaybe k) a -> IO (Constant () a)
709 deleteOrMove _ (Pair (Constant sRef) (ComposeMaybe mToKey)) = do
1570 deleteOrMove _ (Pair (Constant sRef) (ComposeMaybe mToKey)) = do
710− writeIORef sRef $ ChildReadyState_Unready $ Some.This <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases
1571+ writeIORef sRef $ ChildReadyState_Unready $ Some <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases
711 return $ Constant ()
1572 return $ Constant ()
712 p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p
1573 p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p
713 _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old
1574 _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old
714 return $ applyAlways p' old
1575 return $ applyAlways p' old
715− hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness $ \placeholders lastPlaceholderRef (p_ :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) -> do
1576+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness $ \placeholders lastPlaceholder (p_ :: PatchDMapWithMove k (Compose (TraverseChild t m (Some k)) v')) -> do
716 let p = unPatchDMapWithMove p_
1577 let p = unPatchDMapWithMove p_
717 phsBefore <- liftIO $ readIORef placeholders
1578 phsBefore <- liftIO $ readIORef placeholders
718− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
719− let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a)
1579+ let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a)
720 collectIfMoved k e = do
1580 collectIfMoved k e = do
721− let mThisPlaceholder = Map.lookup (Some.This k) phsBefore -- Will be Nothing if this element wasn't present before
1581+ let mThisPlaceholder = Map.lookup (Some k) phsBefore -- Will be Nothing if this element wasn't present before
722− nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsBefore
1582+ nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some k) phsBefore
723 case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of
1583 case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of
724 False -> do
1584 False -> do
725 mapM_ (`deleteUpTo` nextPlaceholder) mThisPlaceholder
1585 mapM_ (`deleteUpTo` nextPlaceholder) mThisPlaceholder
@@ -727,13 +1587,22 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
727 True -> do
1587 True -> do
728 Constant <$> mapM (`collectUpTo` nextPlaceholder) mThisPlaceholder
1588 Constant <$> mapM (`collectUpTo` nextPlaceholder) mThisPlaceholder
729 collected <- DMap.traverseWithKey collectIfMoved p
1589 collected <- DMap.traverseWithKey collectIfMoved p
730− let !phsAfter = fromMaybe phsBefore $ apply (weakenPatchDMapWithMoveWith (\(Compose (_, ph, _, _)) -> ph) p_) phsBefore --TODO: Don't recompute this
1590+ let !phsAfter = fromMaybe phsBefore $ apply filtered phsBefore
1591+ weakened :: PatchMapWithMove (Some k) (Either (TraverseChildHydration t m) (TraverseChildImmediate (Some k)))
1592+ weakened = weakenPatchDMapWithMoveWith (_traverseChild_mode . getCompose) p_
1593+ filtered :: PatchMapWithMove (Some k) DOM.Text
1594+ filtered = PatchMapWithMove $ flip Map.mapMaybe (unPatchMapWithMove weakened) $ \(PatchMapWithMove.NodeInfo from to) -> flip PatchMapWithMove.NodeInfo to <$> case from of
1595+ PatchMapWithMove.From_Insert (Left _hydration) -> Nothing
1596+ PatchMapWithMove.From_Insert (Right immediate) -> Just $ PatchMapWithMove.From_Insert $ _traverseChildImmediate_placeholder immediate
1597+ PatchMapWithMove.From_Delete -> Just $ PatchMapWithMove.From_Delete
1598+ PatchMapWithMove.From_Move k -> Just $ PatchMapWithMove.From_Move k
731− let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant () a)
1599+ let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> JSM (Constant () a)
732 placeFragment k e = do
1600 placeFragment k e = do
733− let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsAfter
1601+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some k) phsAfter
734 case PatchDMapWithMove._nodeInfo_from e of
1602 case PatchDMapWithMove._nodeInfo_from e of
735− PatchDMapWithMove.From_Insert (Compose (df, _, _, _)) -> do
1603+ PatchDMapWithMove.From_Insert (Compose (TraverseChild x _)) -> case x of
1604+ Left _ -> pure ()
736− df `insertBefore` nextPlaceholder
1605+ Right immediate -> _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
737 PatchDMapWithMove.From_Delete -> do
1606 PatchDMapWithMove.From_Delete -> do
738 return ()
1607 return ()
739 PatchDMapWithMove.From_Move fromKey -> do
1608 PatchDMapWithMove.From_Move fromKey -> do
@@ -744,105 +1613,166 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
744 liftIO $ writeIORef placeholders $! phsAfter
1613 liftIO $ writeIORef placeholders $! phsAfter
745
1614
746{-# INLINABLE traverseDMapWithKeyWithAdjust' #-}
1615{-# INLINABLE traverseDMapWithKeyWithAdjust' #-}
1616+traverseDMapWithKeyWithAdjust'
747−traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, DMap.GCompare k) => (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> ImmediateDomBuilderT t m (DMap k v', Event t (PatchDMap k v'))
1617+ :: forall s t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadJSM m, PrimMonad m, DMap.GCompare k, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1618+ => (forall a. k a -> v a -> HydrationDomBuilderT s t m (v' a))
1619+ -> DMap k v
1620+ -> Event t (PatchDMap k v)
1621+ -> HydrationDomBuilderT s t m (DMap k v', Event t (PatchDMap k v'))
748traverseDMapWithKeyWithAdjust' = do
1622traverseDMapWithKeyWithAdjust' = do
749− let updateChildUnreadiness (p :: PatchDMap k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do
1623+ let updateChildUnreadiness (p :: PatchDMap k (Compose (TraverseChild t m (Some k)) v')) old = do
750− let new :: forall a. k a -> ComposeMaybe (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState k))) a)
1624+ let new :: forall a. k a -> ComposeMaybe (Compose (TraverseChild t m (Some k)) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState (Some k)))) a)
751 new k (ComposeMaybe m) = ComposeMaybe <$> case m of
1625 new k (ComposeMaybe m) = ComposeMaybe <$> case m of
752 Nothing -> return Nothing
1626 Nothing -> return Nothing
753− Just (Compose (_, _, sRef, _)) -> do
1627+ Just (Compose (TraverseChild (Left _hydration) _)) -> pure Nothing
754− readIORef sRef >>= \case
1628+ Just (Compose (TraverseChild (Right immediate) _)) -> do
1629+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
755 ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
1630 ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
756 ChildReadyState_Unready _ -> do
1631 ChildReadyState_Unready _ -> do
757− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
1632+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ Some k
758− return $ Just $ Constant sRef
1633+ return $ Just $ Constant (_traverseChildImmediate_childReadyState immediate)
759 delete _ (Constant sRef) = do
1634 delete _ (Constant sRef) = do
760 writeIORef sRef $ ChildReadyState_Unready Nothing
1635 writeIORef sRef $ ChildReadyState_Unready Nothing
761 return $ Constant ()
1636 return $ Constant ()
762 p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p
1637 p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p
763 _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old
1638 _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old
764 return $ applyAlways p' old
1639 return $ applyAlways p' old
765− hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchDMap p) -> do
1640+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholder (PatchDMap patch) -> do
766 phs <- liftIO $ readIORef placeholders
1641 phs <- liftIO $ readIORef placeholders
767− forM_ (DMap.toList p) $ \(k :=> ComposeMaybe mv) -> do
1642+ forM_ (DMap.toList patch) $ \(k :=> ComposeMaybe mv) -> do
768− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
769− let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phs
1643+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some k) phs
1644+ -- Delete old node
1645+ forM_ (Map.lookup (Some k) phs) $ \thisPlaceholder -> do
770− forM_ (Map.lookup (Some.This k) phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
1646+ thisPlaceholder `deleteUpTo` nextPlaceholder
1647+ -- Insert new node
1648+ forM_ mv $ \(Compose (TraverseChild e _)) -> case e of
1649+ Left _hydration -> pure ()
1650+ Right immediate -> do
771− forM_ mv $ \(Compose (df, _, _, _)) -> df `insertBefore` nextPlaceholder
1651+ _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
1652+ let weakened :: PatchMap (Some k) (Either (TraverseChildHydration t m) (TraverseChildImmediate (Some k)))
1653+ weakened = weakenPatchDMapWith (_traverseChild_mode . getCompose) $ PatchDMap patch
1654+ filtered :: PatchMap (Some k) DOM.Text
1655+ filtered = PatchMap $ flip Map.mapMaybe (unPatchMap weakened) $ \case
1656+ Nothing -> Just Nothing -- deletion
1657+ Just (Left _) -> Nothing
1658+ Just (Right immediate) -> Just $ Just $ _traverseChildImmediate_placeholder immediate
772− liftIO $ writeIORef placeholders $! fromMaybe phs $ apply (weakenPatchDMapWith (\(Compose (_, ph, _, _)) -> ph) $ PatchDMap p) phs
1659+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply filtered phs
773
1660
774{-# INLINABLE traverseIntMapWithKeyWithAdjust' #-}
1661{-# INLINABLE traverseIntMapWithKeyWithAdjust' #-}
775−traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m) => (IntMap.Key -> v -> ImmediateDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> ImmediateDomBuilderT t m (IntMap v', Event t (PatchIntMap v'))
1662+traverseIntMapWithKeyWithAdjust'
1663+ :: forall s t m v v'. (Adjustable t m, MonadJSM m, MonadFix m, PrimMonad m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1664+ => (IntMap.Key -> v -> HydrationDomBuilderT s t m v')
1665+ -> IntMap v
1666+ -> Event t (PatchIntMap v)
1667+ -> HydrationDomBuilderT s t m (IntMap v', Event t (PatchIntMap v'))
776traverseIntMapWithKeyWithAdjust' = do
1668traverseIntMapWithKeyWithAdjust' = do
777− let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) old = do
1669+ let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (TraverseChild t m Int v')) old = do
778− let new :: IntMap.Key -> Maybe (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IO (Maybe (IORef ChildReadyStateInt))
1670+ let new :: IntMap.Key -> Maybe (TraverseChild t m Int v') -> IO (Maybe (IORef (ChildReadyState Int)))
779 new k m = case m of
1671 new k m = case m of
780 Nothing -> return Nothing
1672 Nothing -> return Nothing
781− Just (_, _, sRef, _) -> do
1673+ Just (TraverseChild (Left _hydration) _) -> pure Nothing
1674+ Just (TraverseChild (Right immediate) _) -> do
1675+ let sRef = _traverseChildImmediate_childReadyState immediate
782 readIORef sRef >>= \case
1676 readIORef sRef >>= \case
783− ChildReadyStateInt_Ready -> return Nothing -- Delete this child, since it's ready
1677+ ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
784− ChildReadyStateInt_Unready _ -> do
1678+ ChildReadyState_Unready _ -> do
785− writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
1679+ writeIORef sRef $ ChildReadyState_Unready $ Just k
786 return $ Just sRef
1680 return $ Just sRef
787 delete _ sRef = do
1681 delete _ sRef = do
788− writeIORef sRef $ ChildReadyStateInt_Unready Nothing
1682+ writeIORef sRef $ ChildReadyState_Unready Nothing
789 return ()
1683 return ()
790 p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner
1684 p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner
791 _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old
1685 _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old
792 return $ applyAlways p' old
1686 return $ applyAlways p' old
793− hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchIntMap p) -> do
1687+ hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholder (PatchIntMap p) -> do
794 phs <- liftIO $ readIORef placeholders
1688 phs <- liftIO $ readIORef placeholders
795 forM_ (IntMap.toList p) $ \(k, mv) -> do
1689 forM_ (IntMap.toList p) $ \(k, mv) -> do
796− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
797 let nextPlaceholder = maybe lastPlaceholder snd $ IntMap.lookupGT k phs
1690 let nextPlaceholder = maybe lastPlaceholder snd $ IntMap.lookupGT k phs
1691+ -- Delete old node
798 forM_ (IntMap.lookup k phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
1692 forM_ (IntMap.lookup k phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
1693+ -- Insert new node
1694+ forM_ mv $ \(TraverseChild e _) -> case e of
1695+ Left _hydration -> pure ()
1696+ Right immediate -> do
799− forM_ mv $ \(df, _, _, _) -> df `insertBefore` nextPlaceholder
1697+ _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
1698+ let filtered :: PatchIntMap DOM.Text
1699+ filtered = PatchIntMap $ flip IntMap.mapMaybe p $ \case
1700+ Nothing -> Just Nothing -- deletion
1701+ Just tc
1702+ | Right immediate <- _traverseChild_mode tc -> Just $ Just $ _traverseChildImmediate_placeholder immediate
1703+ | otherwise -> Nothing
800− liftIO $ writeIORef placeholders $! fromMaybe phs $ apply ((\(_, ph, _, _) -> ph) <$> PatchIntMap p) phs
1704+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply filtered phs
1705+
1706+{-# SPECIALIZE traverseIntMapWithKeyWithAdjust'
1707+ :: (IntMap.Key -> v -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM v')
1708+ -> IntMap v
1709+ -> Event DomTimeline (PatchIntMap v)
1710+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM (IntMap v', Event DomTimeline (PatchIntMap v'))
1711+ #-}
801
1712
802−#if MIN_VERSION_base(4,9,0)
1713+{-# SPECIALIZE traverseIntMapWithKeyWithAdjust'
803−data ChildReadyState k
1714+ :: (IntMap.Key -> v -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM v')
804−#else
1715+ -> IntMap v
805−data ChildReadyState (k :: * -> *)
1716+ -> Event DomTimeline (PatchIntMap v)
806−#endif
1717+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (IntMap v', Event DomTimeline (PatchIntMap v'))
1718+ #-}
1719+
1720+data ChildReadyState a
807 = ChildReadyState_Ready
1721 = ChildReadyState_Ready
808− | ChildReadyState_Unready !(Maybe (Some k))
1722+ | ChildReadyState_Unready !(Maybe a)
809 deriving (Show, Read, Eq, Ord)
1723 deriving (Show, Read, Eq, Ord)
810
1724
811−data ChildReadyStateInt
1725+insertAfterPreviousNode :: (Monad m, MonadJSM m) => DOM.IsNode node => node -> HydrationRunnerT t m ()
812− = ChildReadyStateInt_Ready
1726+insertAfterPreviousNode node = do
813− | ChildReadyStateInt_Unready !(Maybe Int)
1727+ parent <- askParent
814− deriving (Show, Read, Eq, Ord)
1728+ nextNode <- maybe (Node.getFirstChild parent) Node.getNextSibling =<< getPreviousNode
1729+ Node.insertBefore_ parent node nextNode
1730+ setPreviousNode $ Just $ toNode node
815
1731
816−{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
1732+{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
817−hoistTraverseIntMapWithKeyWithAdjust :: forall v v' t m p.
1733+hoistTraverseWithKeyWithAdjust
1734+ ::
818 ( Adjustable t m
1735 ( Adjustable t m
1736+ , MonadHold t m
1737+ , DMap.GCompare k
819 , MonadIO m
1738 , MonadIO m
820 , MonadJSM m
1739 , MonadJSM m
821− , MonadFix m
822 , PrimMonad m
1740 , PrimMonad m
823− , Monoid (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
1741+ , MonadFix m
824− , Functor p
1742+ , Patch (p k v)
1743+ , Patch (p k (Constant Int))
1744+ , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int)
1745+ , Patch (p k (Compose (TraverseChild t m (Some k)) v'))
1746+ , PatchTarget (p k (Compose (TraverseChild t m (Some k)) v')) ~ DMap k (Compose (TraverseChild t m (Some k)) v')
1747+ , Monoid (p k (Compose (TraverseChild t m (Some k)) v'))
1748+ , RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document
825 )
1749 )
826− => ( (IntMap.Key -> v -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
1750+ => (forall vv vv'.
827− -> IntMap v
1751+ (forall a. k a -> vv a -> DomRenderHookT t m (vv' a))
1752+ -> DMap k vv
828− -> Event t (p v)
1753+ -> Event t (p k vv)
829− -> RequesterT t JSM Identity (TriggerEventT t m) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event t (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')))
1754+ -> DomRenderHookT t m (DMap k vv', Event t (p k vv')))
830− ) -- ^ The base monad's traversal
1755+ -- ^ The base monad's traversal
1756+ -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv')
1757+ -- ^ A way of mapping over the patch type
1758+ -> (p k (Compose (TraverseChild t m (Some k)) v') -> DMap k (Constant (IORef (ChildReadyState (Some k)))) -> IO (DMap k (Constant (IORef (ChildReadyState (Some k))))))
831− -> (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
1759+ -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
832− -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ()) -- ^ Apply a patch to the DOM
1760+ -> (IORef (Map.Map (Some k) DOM.Text) -> DOM.Text -> p k (Compose (TraverseChild t m (Some k)) v') -> JSM ())
833− -> (IntMap.Key -> v -> ImmediateDomBuilderT t m v')
1761+ -- ^ Apply a patch to the DOM
834− -> IntMap v
1762+ -> (forall a. k a -> v a -> HydrationDomBuilderT s t m (v' a))
1763+ -> DMap k v
835− -> Event t (p v)
1764+ -> Event t (p k v)
836− -> ImmediateDomBuilderT t m (IntMap v', Event t (p v'))
1765+ -> HydrationDomBuilderT s t m (DMap k v', Event t (p k v'))
837−hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
1766+hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
838− initialEnv <- ImmediateDomBuilderT ask
1767+ doc <- askDocument
1768+ initialEnv <- HydrationDomBuilderT ask
839− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
1769+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
840− pendingChange :: IORef (IntMap (IORef ChildReadyStateInt), p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) <- liftIO $ newIORef mempty
1770+ pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState (Some k)))), p k (Compose (TraverseChild t m (Some k)) v')) <- liftIO $ newIORef mempty
841 haveEverBeenReady <- liftIO $ newIORef False
1771 haveEverBeenReady <- liftIO $ newIORef False
842− placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
1772+ placeholders <- liftIO $ newIORef Map.empty
843− lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1773+ lastPlaceholder <- createTextNode doc ("" :: Text)
844 let applyDomUpdate p = do
1774 let applyDomUpdate p = do
845− applyDomUpdate_ placeholders lastPlaceholderRef p
1775+ applyDomUpdate_ placeholders lastPlaceholder p
846 markSelfReady
1776 markSelfReady
847 liftIO $ writeIORef pendingChange $! mempty
1777 liftIO $ writeIORef pendingChange $! mempty
848 markSelfReady = do
1778 markSelfReady = do
@@ -853,86 +1783,105 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_
853 old <- liftIO $ readIORef parentUnreadyChildren
1783 old <- liftIO $ readIORef parentUnreadyChildren
854 let new = pred old
1784 let new = pred old
855 liftIO $ writeIORef parentUnreadyChildren $! new
1785 liftIO $ writeIORef parentUnreadyChildren $! new
856− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
1786+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
857− markChildReady :: IORef ChildReadyStateInt -> JSM ()
1787+ markChildReady :: IORef (ChildReadyState (Some k)) -> JSM ()
858 markChildReady childReadyState = do
1788 markChildReady childReadyState = do
859 liftIO (readIORef childReadyState) >>= \case
1789 liftIO (readIORef childReadyState) >>= \case
860− ChildReadyStateInt_Ready -> return ()
1790+ ChildReadyState_Ready -> return ()
861− ChildReadyStateInt_Unready countedAt -> do
1791+ ChildReadyState_Unready countedAt -> do
862− liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
1792+ liftIO $ writeIORef childReadyState ChildReadyState_Ready
863 case countedAt of
1793 case countedAt of
864 Nothing -> return ()
1794 Nothing -> return ()
865− Just k -> do -- This child has been counted as unready, so we need to remove it from the unready set
1795+ Just (Some k) -> do -- This child has been counted as unready, so we need to remove it from the unready set
866 (oldUnready, p) <- liftIO $ readIORef pendingChange
1796 (oldUnready, p) <- liftIO $ readIORef pendingChange
867− when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
1797+ when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
868− let newUnready = IntMap.delete k oldUnready
1798+ let newUnready = DMap.delete k oldUnready
869 liftIO $ writeIORef pendingChange (newUnready, p)
1799 liftIO $ writeIORef pendingChange (newUnready, p)
870− when (IntMap.null newUnready) $ do
1800+ when (DMap.null newUnready) $ do
871 applyDomUpdate p
1801 applyDomUpdate p
1802+ (children0 :: DMap k (Compose (TraverseChild t m (Some k)) v'), children' :: Event t (p k (Compose (TraverseChild t m (Some k)) v')))
872− (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
1803+ <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
873− let processChild k (_, _, sRef, _) = do
1804+ let processChild k (Compose (TraverseChild e _)) = case e of
874− readIORef sRef >>= \case
1805+ Left _ -> pure $ ComposeMaybe Nothing
875− ChildReadyStateInt_Ready -> return Nothing
1806+ Right immediate -> ComposeMaybe <$> do
876− ChildReadyStateInt_Unready _ -> do
1807+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
877− writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
1808+ ChildReadyState_Ready -> return Nothing
878− return $ Just sRef
1809+ ChildReadyState_Unready _ -> do
1810+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ Some k
1811+ return $ Just $ Constant (_traverseChildImmediate_childReadyState immediate)
879− initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
1812+ initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
880− liftIO $ if IntMap.null initialUnready
1813+ liftIO $ if DMap.null initialUnready
881 then writeIORef haveEverBeenReady True
1814 then writeIORef haveEverBeenReady True
882 else do
1815 else do
883 modifyIORef' parentUnreadyChildren succ
1816 modifyIORef' parentUnreadyChildren succ
884 writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
1817 writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
885− let result0 = IntMap.map (\(_, _, _, v) -> v) children0
1818+ getHydrationMode >>= \case
886− placeholders0 = fmap (\(_, ph, _, _) -> ph) children0
1819+ HydrationMode_Hydrating -> addHydrationStepWithSetup (holdIncremental children0 children') $ \children -> do
887− result' = ffor children' $ fmap $ \(_, _, _, r) -> r
1820+ dm :: DMap k (Compose (TraverseChild t m (Some k)) v') <- sample $ currentIncremental children
888− liftIO $ writeIORef placeholders $! placeholders0
1821+ phs <- traverse id $ weakenDMapWith (either _traverseChildHydration_delayed (pure . _traverseChildImmediate_placeholder) . _traverseChild_mode . getCompose) dm
889− _ <- IntMap.traverseWithKey (\_ (df, _, _, _) -> void $ append $ toNode df) children0
1822+ liftIO $ writeIORef placeholders $! phs
890− liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1823+ insertAfterPreviousNode lastPlaceholder
1824+ HydrationMode_Immediate -> do
1825+ let activate i = do
1826+ append $ toNode $ _traverseChildImmediate_fragment i
1827+ pure $ _traverseChildImmediate_placeholder i
1828+ phs <- traverse id $ weakenDMapWith (either (error "impossible") activate . _traverseChild_mode . getCompose) children0
1829+ liftIO $ writeIORef placeholders $! phs
1830+ append $ toNode lastPlaceholder
891 requestDomAction_ $ ffor children' $ \p -> do
1831 requestDomAction_ $ ffor children' $ \p -> do
892 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
1832 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
893 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
1833 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
894 let !newP = p <> oldP
1834 let !newP = p <> oldP
895 liftIO $ writeIORef pendingChange (newUnready, newP)
1835 liftIO $ writeIORef pendingChange (newUnready, newP)
896− when (IntMap.null newUnready) $ do
1836+ when (DMap.null newUnready) $ do
897 applyDomUpdate newP
1837 applyDomUpdate newP
1838+ let result0 = DMap.map (_traverseChild_result . getCompose) children0
1839+ result' = ffor children' $ mapPatch $ _traverseChild_result . getCompose
898 return (result0, result')
1840 return (result0, result')
899
1841
900−{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
1842+{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
901−hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
1843+hoistTraverseIntMapWithKeyWithAdjust ::
902 ( Adjustable t m
1844 ( Adjustable t m
903 , MonadHold t m
1845 , MonadHold t m
904− , DMap.GCompare k
905− , MonadIO m
906 , MonadJSM m
1846 , MonadJSM m
907− , PrimMonad m
908 , MonadFix m
1847 , MonadFix m
909− , Patch (p k v)
1848+ , PrimMonad m
910− , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int)
1849+ , Monoid (p (TraverseChild t m Int v'))
911− , Monoid (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v'))
1850+ , Functor p
912− , Patch (p k (Constant Int))
1851+ , PatchTarget (p (HydrationRunnerT t m ())) ~ IntMap (HydrationRunnerT t m ())
1852+ , PatchTarget (p (TraverseChild t m Int v')) ~ IntMap (TraverseChild t m Int v')
1853+ , Patch (p (HydrationRunnerT t m ()))
1854+ , Patch (p (TraverseChild t m Int v'))
1855+ , RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document
913 )
1856 )
914− => (forall vv vv'.
1857+ => ((IntMap.Key -> v -> DomRenderHookT t m (TraverseChild t m Int v'))
915− (forall a. k a -> vv a -> RequesterT t JSM Identity (TriggerEventT t m) (vv' a))
1858+ -> IntMap v
916− -> DMap k vv
917− -> Event t (p k vv)
1859+ -> Event t (p v)
918− -> RequesterT t JSM Identity (TriggerEventT t m) (DMap k vv', Event t (p k vv'))
1860+ -> DomRenderHookT t m (IntMap (TraverseChild t m Int v'), Event t (p (TraverseChild t m Int v'))))
919− ) -- ^ The base monad's traversal
1861+ -- ^ The base monad's traversal
920− -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv') -- ^ A way of mapping over the patch type
1862+ -> (p (TraverseChild t m Int v')
1863+ -> IntMap (IORef (ChildReadyState Int))
1864+ -> IO (IntMap (IORef (ChildReadyState Int))))
921− -> (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> DMap k (Constant (IORef (ChildReadyState k))) -> IO (DMap k (Constant (IORef (ChildReadyState k))))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
1865+ -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
922− -> (IORef (Map.Map (Some.Some k) DOM.Text) -> IORef DOM.Text -> p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> JSM ()) -- ^ Apply a patch to the DOM
1866+ -> (IORef (IntMap DOM.Text)
923− -> (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a))
1867+ -> DOM.Text
924− -> DMap k v
1868+ -> p (TraverseChild t m Int v')
1869+ -> JSM ())
1870+ -- ^ Apply a patch to the DOM
1871+ -> (IntMap.Key -> v -> HydrationDomBuilderT s t m v')
1872+ -> IntMap v
925− -> Event t (p k v)
1873+ -> Event t (p v)
926− -> ImmediateDomBuilderT t m (DMap k v', Event t (p k v'))
1874+ -> HydrationDomBuilderT s t m (IntMap v', Event t (p v'))
927−hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ (f :: forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) (dm0 :: DMap k v) dm' = do
1875+hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
928− initialEnv <- ImmediateDomBuilderT ask
1876+ doc <- askDocument
1877+ initialEnv <- HydrationDomBuilderT ask
929− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
1878+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
930− pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState k))), p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) <- liftIO $ newIORef mempty
1879+ pendingChange :: IORef (IntMap (IORef (ChildReadyState Int)), p (TraverseChild t m Int v')) <- liftIO $ newIORef mempty
931 haveEverBeenReady <- liftIO $ newIORef False
1880 haveEverBeenReady <- liftIO $ newIORef False
932− placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
1881+ placeholders <- liftIO $ newIORef IntMap.empty
933− lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1882+ lastPlaceholder <- createTextNode doc ("" :: Text)
934 let applyDomUpdate p = do
1883 let applyDomUpdate p = do
935− applyDomUpdate_ placeholders lastPlaceholderRef p
1884+ applyDomUpdate_ placeholders lastPlaceholder p
936 markSelfReady
1885 markSelfReady
937 liftIO $ writeIORef pendingChange $! mempty
1886 liftIO $ writeIORef pendingChange $! mempty
938 markSelfReady = do
1887 markSelfReady = do
@@ -943,8 +1892,8 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda
943 old <- liftIO $ readIORef parentUnreadyChildren
1892 old <- liftIO $ readIORef parentUnreadyChildren
944 let new = pred old
1893 let new = pred old
945 liftIO $ writeIORef parentUnreadyChildren $! new
1894 liftIO $ writeIORef parentUnreadyChildren $! new
946− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
1895+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
947− markChildReady :: IORef (ChildReadyState k) -> JSM ()
1896+ markChildReady :: IORef (ChildReadyState Int) -> JSM ()
948 markChildReady childReadyState = do
1897 markChildReady childReadyState = do
949 liftIO (readIORef childReadyState) >>= \case
1898 liftIO (readIORef childReadyState) >>= \case
950 ChildReadyState_Ready -> return ()
1899 ChildReadyState_Ready -> return ()
@@ -952,82 +1901,191 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda
952 liftIO $ writeIORef childReadyState ChildReadyState_Ready
1901 liftIO $ writeIORef childReadyState ChildReadyState_Ready
953 case countedAt of
1902 case countedAt of
954 Nothing -> return ()
1903 Nothing -> return ()
955− Just (Some.This k) -> do -- This child has been counted as unready, so we need to remove it from the unready set
1904+ Just k -> do -- This child has been counted as unready, so we need to remove it from the unready set
956 (oldUnready, p) <- liftIO $ readIORef pendingChange
1905 (oldUnready, p) <- liftIO $ readIORef pendingChange
957− when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
1906+ when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
958− let newUnready = DMap.delete k oldUnready
1907+ let newUnready = IntMap.delete k oldUnready
959 liftIO $ writeIORef pendingChange (newUnready, p)
1908 liftIO $ writeIORef pendingChange (newUnready, p)
960− when (DMap.null newUnready) $ do
1909+ when (IntMap.null newUnready) $ do
961 applyDomUpdate p
1910 applyDomUpdate p
1911+ (children0 :: IntMap (TraverseChild t m Int v'), children' :: Event t (p (TraverseChild t m Int v')))
962− (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
1912+ <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
963− let processChild k (Compose (_, _, sRef, _)) = ComposeMaybe <$> do
1913+ let processChild k (TraverseChild e _) = case e of
964− readIORef sRef >>= \case
1914+ Left _ -> pure Nothing
1915+ Right immediate -> do
1916+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
965− ChildReadyState_Ready -> return Nothing
1917+ ChildReadyState_Ready -> return Nothing
966− ChildReadyState_Unready _ -> do
1918+ ChildReadyState_Unready _ -> do
967− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
1919+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just k
968− return $ Just $ Constant sRef
1920+ return $ Just (_traverseChildImmediate_childReadyState immediate)
969− initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
1921+ initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
970− liftIO $ if DMap.null initialUnready
1922+ liftIO $ if IntMap.null initialUnready
971 then writeIORef haveEverBeenReady True
1923 then writeIORef haveEverBeenReady True
972 else do
1924 else do
973 modifyIORef' parentUnreadyChildren succ
1925 modifyIORef' parentUnreadyChildren succ
974 writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
1926 writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
975− let result0 = DMap.map (\(Compose (_, _, _, v)) -> v) children0
1927+ getHydrationMode >>= \case
976− placeholders0 = weakenDMapWith (\(Compose (_, ph, _, _)) -> ph) children0
1928+ HydrationMode_Hydrating -> addHydrationStepWithSetup (holdIncremental children0 children') $ \children -> do
977− result' = ffor children' $ mapPatch $ \(Compose (_, _, _, r)) -> r
1929+ dm :: IntMap (TraverseChild t m Int v') <- sample $ currentIncremental children
978− liftIO $ writeIORef placeholders $! placeholders0
1930+ phs <- traverse (either _traverseChildHydration_delayed (pure . _traverseChildImmediate_placeholder) . _traverseChild_mode) dm
979− _ <- DMap.traverseWithKey (\_ (Compose (df, _, _, _)) -> Constant () <$ append (toNode df)) children0
1931+ liftIO $ writeIORef placeholders $! phs
980− liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1932+ insertAfterPreviousNode lastPlaceholder
1933+ HydrationMode_Immediate -> do
1934+ let activate i = do
1935+ append $ toNode $ _traverseChildImmediate_fragment i
1936+ pure $ _traverseChildImmediate_placeholder i
1937+ phs <- traverse (either (error "impossible") activate . _traverseChild_mode) children0
1938+ liftIO $ writeIORef placeholders $! phs
1939+ append $ toNode lastPlaceholder
981 requestDomAction_ $ ffor children' $ \p -> do
1940 requestDomAction_ $ ffor children' $ \p -> do
982 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
1941 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
983 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
1942 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
984 let !newP = p <> oldP
1943 let !newP = p <> oldP
985 liftIO $ writeIORef pendingChange (newUnready, newP)
1944 liftIO $ writeIORef pendingChange (newUnready, newP)
986− when (DMap.null newUnready) $ do
1945+ when (IntMap.null newUnready) $ do
987 applyDomUpdate newP
1946 applyDomUpdate newP
1947+ let result0 = IntMap.map _traverseChild_result children0
1948+ result' = ffor children' $ fmap $ _traverseChild_result
988 return (result0, result')
1949 return (result0, result')
989
1950
1951+{-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust
1952+ :: ((IntMap.Key -> v -> DomRenderHookT DomTimeline HydrationM (TraverseChild DomTimeline HydrationM Int v'))
1953+ -> IntMap v
1954+ -> Event DomTimeline (PatchIntMap v)
1955+ -> DomRenderHookT DomTimeline HydrationM (IntMap (TraverseChild DomTimeline HydrationM Int v'), Event DomTimeline (PatchIntMap (TraverseChild DomTimeline HydrationM Int v'))))
1956+ -> (PatchIntMap (TraverseChild DomTimeline HydrationM Int v')
1957+ -> IntMap (IORef (ChildReadyState Int))
1958+ -> IO (IntMap (IORef (ChildReadyState Int))))
1959+ -> (IORef (IntMap DOM.Text)
1960+ -> DOM.Text
1961+ -> PatchIntMap (TraverseChild DomTimeline HydrationM Int v')
1962+ -> JSM ())
1963+ -> (IntMap.Key -> v -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM v')
1964+ -> IntMap v
1965+ -> Event DomTimeline (PatchIntMap v)
1966+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (IntMap v', Event DomTimeline (PatchIntMap v'))
1967+ #-}
1968+
1969+{-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust
1970+ :: ((IntMap.Key -> v -> DomRenderHookT DomTimeline HydrationM (TraverseChild DomTimeline HydrationM Int v'))
1971+ -> IntMap v
1972+ -> Event DomTimeline (PatchIntMap v)
1973+ -> DomRenderHookT DomTimeline HydrationM (IntMap (TraverseChild DomTimeline HydrationM Int v'), Event DomTimeline (PatchIntMap (TraverseChild DomTimeline HydrationM Int v'))))
1974+ -> (PatchIntMap (TraverseChild DomTimeline HydrationM Int v')
1975+ -> IntMap (IORef (ChildReadyState Int))
1976+ -> IO (IntMap (IORef (ChildReadyState Int))))
1977+ -> (IORef (IntMap DOM.Text)
1978+ -> DOM.Text
1979+ -> PatchIntMap (TraverseChild DomTimeline HydrationM Int v')
1980+ -> JSM ())
1981+ -> (IntMap.Key -> v -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM v')
1982+ -> IntMap v
1983+ -> Event DomTimeline (PatchIntMap v)
1984+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM (IntMap v', Event DomTimeline (PatchIntMap v'))
1985+ #-}
1986+
1987+data TraverseChildImmediate k = TraverseChildImmediate
1988+ { _traverseChildImmediate_fragment :: {-# UNPACK #-} !DOM.DocumentFragment
1989+ -- ^ Child is appended to this fragment
1990+ , _traverseChildImmediate_placeholder :: {-# UNPACK #-} !DOM.Text
1991+ -- ^ Placeholder reference
1992+ , _traverseChildImmediate_childReadyState :: {-# UNPACK #-} !(IORef (ChildReadyState k))
1993+ }
1994+
1995+newtype TraverseChildHydration t m = TraverseChildHydration
1996+ { _traverseChildHydration_delayed :: HydrationRunnerT t m DOM.Text
1997+ -- ^ Action to run at switchover, returns the placeholder
1998+ }
1999+
2000+data TraverseChild t m k a = TraverseChild
2001+ { _traverseChild_mode :: !(Either (TraverseChildHydration t m) (TraverseChildImmediate k))
2002+ , _traverseChild_result :: !a
2003+ } deriving Functor
2004+
990{-# INLINABLE drawChildUpdate #-}
2005{-# INLINABLE drawChildUpdate #-}
991−drawChildUpdate :: (MonadIO m, MonadJSM m)
2006+drawChildUpdate :: (MonadJSM m, Reflex t)
992− => ImmediateDomBuilderEnv t
2007+ => HydrationDomBuilderEnv t m
993 -> (IORef (ChildReadyState k) -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
2008 -> (IORef (ChildReadyState k) -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
994− -> ImmediateDomBuilderT t m (v' a)
2009+ -> HydrationDomBuilderT s t m (f a)
995− -> RequesterT t JSM Identity (TriggerEventT t m) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a)
2010+ -> DomRenderHookT t m (Compose (TraverseChild t m k) f a)
996drawChildUpdate initialEnv markReady child = do
2011drawChildUpdate initialEnv markReady child = do
997− childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing
2012+ let doc = _hydrationDomBuilderEnv_document initialEnv
998 unreadyChildren <- liftIO $ newIORef 0
2013 unreadyChildren <- liftIO $ newIORef 0
2014+ liftIO (readIORef $ _hydrationDomBuilderEnv_hydrationMode initialEnv) >>= \case
2015+ HydrationMode_Hydrating -> do
2016+ childDelayedRef <- liftIO $ newIORef $ pure ()
2017+ result <- runReaderT (unHydrationDomBuilderT child) initialEnv
2018+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
2019+ , _hydrationDomBuilderEnv_delayed = childDelayedRef
2020+ }
2021+ childDelayed <- liftIO $ readIORef childDelayedRef
2022+ return $ Compose $ TraverseChild
2023+ { _traverseChild_result = result
2024+ , _traverseChild_mode = Left TraverseChildHydration
2025+ { _traverseChildHydration_delayed = do
2026+ placeholder <- createTextNode doc ("" :: Text)
2027+ insertAfterPreviousNode placeholder
2028+ childDelayed
2029+ pure placeholder
2030+ }
2031+ }
2032+ HydrationMode_Immediate -> do
2033+ childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing
999− df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
2034+ df <- createDocumentFragment doc
1000− (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
2035+ placeholder <- createTextNode doc ("" :: Text)
1001− { _immediateDomBuilderEnv_parent = toNode df
2036+ Node.appendChild_ df placeholder
1002− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
2037+ result <- runReaderT (unHydrationDomBuilderT child) initialEnv
2038+ { _hydrationDomBuilderEnv_parent = Left $ toNode df
2039+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1003− , _immediateDomBuilderEnv_commitAction = markReady childReadyState
2040+ , _hydrationDomBuilderEnv_commitAction = markReady childReadyState
1004− }
2041+ }
1005− u <- liftIO $ readIORef unreadyChildren
2042+ u <- liftIO $ readIORef unreadyChildren
1006− when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
2043+ when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
1007− return $ Compose (df, placeholder, childReadyState, result)
2044+ return $ Compose $ TraverseChild
2045+ { _traverseChild_result = result
2046+ , _traverseChild_mode = Right TraverseChildImmediate
2047+ { _traverseChildImmediate_fragment = df
2048+ , _traverseChildImmediate_placeholder = placeholder
2049+ , _traverseChildImmediate_childReadyState = childReadyState
2050+ }
2051+ }
1008
2052
1009−{-# INLINABLE drawChildUpdateInt #-}
2053+{-# SPECIALIZE drawChildUpdate
1010−drawChildUpdateInt :: (MonadIO m, MonadJSM m)
2054+ :: HydrationDomBuilderEnv DomTimeline HydrationM
1011− => ImmediateDomBuilderEnv t
2055+ -> (IORef (ChildReadyState Int) -> JSM ())
1012− -> (IORef ChildReadyStateInt -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
2056+ -> HydrationDomBuilderT s DomTimeline HydrationM (Identity a)
1013− -> ImmediateDomBuilderT t m v'
2057+ -> DomRenderHookT DomTimeline HydrationM (Compose (TraverseChild DomTimeline HydrationM Int) Identity a)
1014− -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')
2058+ #-}
1015−drawChildUpdateInt initialEnv markReady child = do
2059+
1016− childReadyState <- liftIO $ newIORef $ ChildReadyStateInt_Unready Nothing
2060+{-# SPECIALIZE drawChildUpdate
1017− unreadyChildren <- liftIO $ newIORef 0
2061+ :: HydrationDomBuilderEnv DomTimeline HydrationM
1018− df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
2062+ -> (IORef (ChildReadyState (Some k)) -> JSM ())
1019− (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
2063+ -> HydrationDomBuilderT s DomTimeline HydrationM (f a)
1020− { _immediateDomBuilderEnv_parent = toNode df
2064+ -> DomRenderHookT DomTimeline HydrationM (Compose (TraverseChild DomTimeline HydrationM (Some k)) f a)
1021− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
2065+ #-}
1022− , _immediateDomBuilderEnv_commitAction = markReady childReadyState
1023− }
1024− u <- liftIO $ readIORef unreadyChildren
1025− when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
1026− return (df, placeholder, childReadyState, result)
1027
2066
2067+{-# INLINABLE drawChildUpdateInt #-}
2068+drawChildUpdateInt :: (MonadIO m, MonadJSM m, Reflex t)
2069+ => HydrationDomBuilderEnv t m
2070+ -> (IORef (ChildReadyState k) -> JSM ())
2071+ -> HydrationDomBuilderT s t m v
2072+ -> DomRenderHookT t m (TraverseChild t m k v)
2073+drawChildUpdateInt env mark m = fmap runIdentity . getCompose <$> drawChildUpdate env mark (Identity <$> m)
2074+
2075+{-# SPECIALIZE drawChildUpdateInt
2076+ :: HydrationDomBuilderEnv DomTimeline HydrationM
2077+ -> (IORef (ChildReadyState k) -> JSM ())
2078+ -> HydrationDomBuilderT s DomTimeline HydrationM v
2079+ -> DomRenderHookT DomTimeline HydrationM (TraverseChild DomTimeline HydrationM k v)
2080+ #-}
2081+
2082+{-# INLINE mkHasFocus #-}
2083+mkHasFocus
2084+ :: (HasDocument m, MonadJSM m, IsNode (RawElement d), MonadHold t m, Reflex t, DOM.IsDocumentOrShadowRoot (RawDocument (DomBuilderSpace m)))
1028−mkHasFocus :: (MonadHold t m, Reflex t) => Element er d t -> m (Dynamic t Bool)
2085+ => Element er d t -> m (Dynamic t Bool)
1029mkHasFocus e = do
2086mkHasFocus e = do
1030− let initialFocus = False --TODO: Actually get the initial focus of the element
2087+ doc <- askDocument
2088+ initialFocus <- Node.isSameNode (toNode $ _element_raw e) . fmap toNode =<< Document.getActiveElement doc
1031 holdDyn initialFocus $ leftmost
2089 holdDyn initialFocus $ leftmost
1032 [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
2090 [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
1033 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
2091 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
@@ -1036,39 +2094,49 @@ mkHasFocus e = do
1036insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
2094insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1037insertBefore new existing = do
2095insertBefore new existing = do
1038 p <- getParentNodeUnchecked existing
2096 p <- getParentNodeUnchecked existing
1039− DOM.insertBefore_ p new (Just existing) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
2097+ Node.insertBefore_ p new (Just existing) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
1040
2098
2099+type ImmediateDomBuilderT = HydrationDomBuilderT GhcjsDomSpace
2100+
1041−instance PerformEvent t m => PerformEvent t (ImmediateDomBuilderT t m) where
2101+instance PerformEvent t m => PerformEvent t (HydrationDomBuilderT s t m) where
1042− type Performable (ImmediateDomBuilderT t m) = Performable m
2102+ type Performable (HydrationDomBuilderT s t m) = Performable m
1043 {-# INLINABLE performEvent_ #-}
2103 {-# INLINABLE performEvent_ #-}
1044 performEvent_ e = lift $ performEvent_ e
2104 performEvent_ e = lift $ performEvent_ e
1045 {-# INLINABLE performEvent #-}
2105 {-# INLINABLE performEvent #-}
1046 performEvent e = lift $ performEvent e
2106 performEvent e = lift $ performEvent e
1047
2107
1048−instance PostBuild t m => PostBuild t (ImmediateDomBuilderT t m) where
2108+instance PostBuild t m => PostBuild t (HydrationDomBuilderT s t m) where
1049 {-# INLINABLE getPostBuild #-}
2109 {-# INLINABLE getPostBuild #-}
1050 getPostBuild = lift getPostBuild
2110 getPostBuild = lift getPostBuild
1051
2111
1052−instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (ImmediateDomBuilderT t m) where
2112+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydrationDomBuilderT s t m) where
1053 {-# INLINABLE newEventWithTrigger #-}
2113 {-# INLINABLE newEventWithTrigger #-}
1054 newEventWithTrigger = lift . newEventWithTrigger
2114 newEventWithTrigger = lift . newEventWithTrigger
1055 {-# INLINABLE newFanEventWithTrigger #-}
2115 {-# INLINABLE newFanEventWithTrigger #-}
1056 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
2116 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
1057
2117
1058−instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (ImmediateDomBuilderT t m) where
2118+instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (HydrationDomBuilderT s t m) where
2119+ {-# INLINABLE newTriggerEvent #-}
2120+ newTriggerEvent = HydrationDomBuilderT . lift $ newTriggerEvent
2121+ {-# INLINABLE newTriggerEventWithOnComplete #-}
2122+ newTriggerEventWithOnComplete = HydrationDomBuilderT . lift $ newTriggerEventWithOnComplete
2123+ {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
2124+ newEventWithLazyTriggerWithOnComplete f = HydrationDomBuilderT . lift $ newEventWithLazyTriggerWithOnComplete f
2125+
2126+instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (DomRenderHookT t m) where
1059 {-# INLINABLE newTriggerEvent #-}
2127 {-# INLINABLE newTriggerEvent #-}
1060− newTriggerEvent = ImmediateDomBuilderT . lift . lift $ newTriggerEvent
2128+ newTriggerEvent = DomRenderHookT . lift $ newTriggerEvent
1061 {-# INLINABLE newTriggerEventWithOnComplete #-}
2129 {-# INLINABLE newTriggerEventWithOnComplete #-}
1062− newTriggerEventWithOnComplete = ImmediateDomBuilderT . lift . lift $ newTriggerEventWithOnComplete
2130+ newTriggerEventWithOnComplete = DomRenderHookT . lift $ newTriggerEventWithOnComplete
1063 {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
2131 {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
1064− newEventWithLazyTriggerWithOnComplete f = ImmediateDomBuilderT . lift . lift $ newEventWithLazyTriggerWithOnComplete f
2132+ newEventWithLazyTriggerWithOnComplete f = DomRenderHookT . lift $ newEventWithLazyTriggerWithOnComplete f
1065
2133
1066−instance HasJSContext m => HasJSContext (ImmediateDomBuilderT t m) where
2134+instance HasJSContext m => HasJSContext (HydrationDomBuilderT s t m) where
1067− type JSContextPhantom (ImmediateDomBuilderT t m) = JSContextPhantom m
2135+ type JSContextPhantom (HydrationDomBuilderT s t m) = JSContextPhantom m
1068 askJSContext = lift askJSContext
2136 askJSContext = lift askJSContext
1069
2137
1070−instance MonadRef m => MonadRef (ImmediateDomBuilderT t m) where
2138+instance MonadRef m => MonadRef (HydrationDomBuilderT s t m) where
1071− type Ref (ImmediateDomBuilderT t m) = Ref m
2139+ type Ref (HydrationDomBuilderT s t m) = Ref m
1072 {-# INLINABLE newRef #-}
2140 {-# INLINABLE newRef #-}
1073 newRef = lift . newRef
2141 newRef = lift . newRef
1074 {-# INLINABLE readRef #-}
2142 {-# INLINABLE readRef #-}
@@ -1076,12 +2144,12 @@ instance MonadRef m => MonadRef (ImmediateDomBuilderT t m) where
1076 {-# INLINABLE writeRef #-}
2144 {-# INLINABLE writeRef #-}
1077 writeRef r = lift . writeRef r
2145 writeRef r = lift . writeRef r
1078
2146
1079−instance MonadAtomicRef m => MonadAtomicRef (ImmediateDomBuilderT t m) where
2147+instance MonadAtomicRef m => MonadAtomicRef (HydrationDomBuilderT s t m) where
1080 {-# INLINABLE atomicModifyRef #-}
2148 {-# INLINABLE atomicModifyRef #-}
1081 atomicModifyRef r = lift . atomicModifyRef r
2149 atomicModifyRef r = lift . atomicModifyRef r
1082
2150
1083−instance (HasJS x m, ReflexHost t) => HasJS x (ImmediateDomBuilderT t m) where
2151+instance (HasJS x m, ReflexHost t) => HasJS x (HydrationDomBuilderT s t m) where
1084− type JSX (ImmediateDomBuilderT t m) = JSX m
2152+ type JSX (HydrationDomBuilderT s t m) = JSX m
1085 liftJS = lift . liftJS
2153 liftJS = lift . liftJS
1086
2154
1087type family EventType en where
2155type family EventType en where
@@ -1539,11 +2607,11 @@ getTouchEvent = do
1539 , _touchEventResult_touches = touches
2607 , _touchEventResult_touches = touches
1540 }
2608 }
1541
2609
1542−instance MonadSample t m => MonadSample t (ImmediateDomBuilderT t m) where
2610+instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
1543 {-# INLINABLE sample #-}
2611 {-# INLINABLE sample #-}
1544 sample = lift . sample
2612 sample = lift . sample
1545
2613
1546−instance MonadHold t m => MonadHold t (ImmediateDomBuilderT t m) where
2614+instance MonadHold t m => MonadHold t (HydrationDomBuilderT s t m) where
1547 {-# INLINABLE hold #-}
2615 {-# INLINABLE hold #-}
1548 hold v0 v' = lift $ hold v0 v'
2616 hold v0 v' = lift $ hold v0 v'
1549 {-# INLINABLE holdDyn #-}
2617 {-# INLINABLE holdDyn #-}
@@ -1565,7 +2633,7 @@ data Window t = Window
1565 , _window_raw :: DOM.Window
2633 , _window_raw :: DOM.Window
1566 }
2634 }
1567
2635
1568−wrapWindow :: (MonadJSM m, MonadReflexCreateTrigger t m) => DOM.Window -> WindowConfig t -> ImmediateDomBuilderT t m (Window t)
2636+wrapWindow :: (MonadJSM m, MonadReflexCreateTrigger t m) => DOM.Window -> WindowConfig t -> HydrationDomBuilderT GhcjsDomSpace t m (Window t)
1569wrapWindow wv _ = do
2637wrapWindow wv _ = do
1570 events <- wrapDomEventsMaybe wv (defaultDomWindowEventHandler wv) windowOnEventName
2638 events <- wrapDomEventsMaybe wv (defaultDomWindowEventHandler wv) windowOnEventName
1571 return $ Window
2639 return $ Window
reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs+6
@@ -80,6 +80,7 @@ instance Adjustable t m => Adjustable t (InputDisabledT m) where
80 runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a')
80 runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a')
81 traverseDMapWithKeyWithAdjust f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
81 traverseDMapWithKeyWithAdjust f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
82 traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
82 traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
83+ traverseIntMapWithKeyWithAdjust f m0 m' = InputDisabledT $ traverseIntMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce m0) (coerceEvent m')
83
84
84instance NotReady t m => NotReady t (InputDisabledT m) where
85instance NotReady t m => NotReady t (InputDisabledT m) where
85 notReadyUntil = lift . notReadyUntil
86 notReadyUntil = lift . notReadyUntil
@@ -108,3 +109,8 @@ instance HasJSContext m => HasJSContext (InputDisabledT m) where
108instance HasJS js m => HasJS js (InputDisabledT m) where
109instance HasJS js m => HasJS js (InputDisabledT m) where
109 type JSX (InputDisabledT m) = JSX m
110 type JSX (InputDisabledT m) = JSX m
110 liftJS = lift . liftJS
111 liftJS = lift . liftJS
112+
113+instance DomRenderHook t m => DomRenderHook t (InputDisabledT m) where
114+ withRenderHook f = InputDisabledT . withRenderHook f . runInputDisabledT
115+ requestDomAction = InputDisabledT . requestDomAction
116+ requestDomAction_ = InputDisabledT . requestDomAction_
reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+79−6
@@ -15,6 +15,7 @@
15{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
16module Reflex.Dom.Builder.Static where
16module Reflex.Dom.Builder.Static where
17
17
18+import Data.IORef (IORef)
18import Blaze.ByteString.Builder.Html.Utf8
19import Blaze.ByteString.Builder.Html.Utf8
19import Control.Lens hiding (element)
20import Control.Lens hiding (element)
20import Control.Monad.Exception
21import Control.Monad.Exception
@@ -32,23 +33,29 @@ import qualified Data.Dependent.Map as DMap
32import Data.Dependent.Sum (DSum (..))
33import Data.Dependent.Sum (DSum (..))
33import Data.Functor.Compose
34import Data.Functor.Compose
34import Data.Functor.Constant
35import Data.Functor.Constant
36+import Data.IntMap (IntMap)
37+import qualified Data.IntMap as IntMap
35import qualified Data.Map as Map
38import qualified Data.Map as Map
36import Data.Map.Misc (applyMap)
39import Data.Map.Misc (applyMap)
37−import Data.Monoid
40+import Data.Monoid ((<>))
38import qualified Data.Set as Set
41import qualified Data.Set as Set
39import Data.Text (Text)
42import Data.Text (Text)
43+import qualified Data.Text as T
40import Data.Text.Encoding
44import Data.Text.Encoding
41import Data.Tuple
45import Data.Tuple
42import GHC.Generics
46import GHC.Generics
47+import Reflex.Adjustable.Class
43import Reflex.Class
48import Reflex.Class
49+import Reflex.Dom.Main (DomHost, DomTimeline, runDomHost)
44import Reflex.Dom.Builder.Class
50import Reflex.Dom.Builder.Class
45import Reflex.Dynamic
51import Reflex.Dynamic
46import Reflex.Host.Class
52import Reflex.Host.Class
47import Reflex.PerformEvent.Base
53import Reflex.PerformEvent.Base
48import Reflex.PerformEvent.Class
54import Reflex.PerformEvent.Class
49import Reflex.PostBuild.Base
55import Reflex.PostBuild.Base
50−import Reflex.Spider
56+import Reflex.PostBuild.Class
51import Reflex.TriggerEvent.Class
57import Reflex.TriggerEvent.Class
58+import System.Random (randomRIO)
52
59
53data StaticDomBuilderEnv t = StaticDomBuilderEnv
60data StaticDomBuilderEnv t = StaticDomBuilderEnv
54 { _staticDomBuilderEnv_shouldEscape :: Bool
61 { _staticDomBuilderEnv_shouldEscape :: Bool
@@ -76,6 +83,10 @@ runStaticDomBuilderT (StaticDomBuilderT a) e = do
76 (result, a') <- runStateT (runReaderT a e) []
83 (result, a') <- runStateT (runReaderT a e) []
77 return (result, mconcat $ reverse a')
84 return (result, mconcat $ reverse a')
78
85
86+instance PostBuild t m => PostBuild t (StaticDomBuilderT t m) where
87+ {-# INLINABLE getPostBuild #-}
88+ getPostBuild = lift getPostBuild
89+
79instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (StaticDomBuilderT t m) where
90instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (StaticDomBuilderT t m) where
80 {-# INLINABLE newEventWithTrigger #-}
91 {-# INLINABLE newEventWithTrigger #-}
81 newEventWithTrigger = lift . newEventWithTrigger
92 newEventWithTrigger = lift . newEventWithTrigger
@@ -138,29 +149,82 @@ instance Default (StaticEventSpec er)
138
149
139instance DomSpace StaticDomSpace where
150instance DomSpace StaticDomSpace where
140 type EventSpec StaticDomSpace = StaticEventSpec
151 type EventSpec StaticDomSpace = StaticEventSpec
152+ type RawDocument StaticDomSpace = ()
141 type RawTextNode StaticDomSpace = ()
153 type RawTextNode StaticDomSpace = ()
154+ type RawCommentNode StaticDomSpace = ()
142 type RawElement StaticDomSpace = ()
155 type RawElement StaticDomSpace = ()
143− type RawFile StaticDomSpace = ()
144 type RawInputElement StaticDomSpace = ()
156 type RawInputElement StaticDomSpace = ()
145 type RawTextAreaElement StaticDomSpace = ()
157 type RawTextAreaElement StaticDomSpace = ()
146 type RawSelectElement StaticDomSpace = ()
158 type RawSelectElement StaticDomSpace = ()
147 addEventSpecFlags _ _ _ _ = StaticEventSpec
159 addEventSpecFlags _ _ _ _ = StaticEventSpec
148
160
161+instance (SupportsStaticDomBuilder t m, Monad m) => HasDocument (StaticDomBuilderT t m) where
162+ askDocument = pure ()
163+
149−instance (Reflex t, Adjustable t m, MonadHold t m) => Adjustable t (StaticDomBuilderT t m) where
164+instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m) => Adjustable t (StaticDomBuilderT t m) where
150 runWithReplace a0 a' = do
165 runWithReplace a0 a' = do
151 e <- StaticDomBuilderT ask
166 e <- StaticDomBuilderT ask
167+ key <- replaceStart
152 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
168 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
153 o <- hold (snd result0) $ fmapCheap snd result'
169 o <- hold (snd result0) $ fmapCheap snd result'
154 StaticDomBuilderT $ modify $ (:) $ join o
170 StaticDomBuilderT $ modify $ (:) $ join o
171+ replaceEnd key
155 return (fst result0, fmapCheap fst result')
172 return (fst result0, fmapCheap fst result')
173+ traverseIntMapWithKeyWithAdjust = hoistIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust
156 traverseDMapWithKeyWithAdjust = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap
174 traverseDMapWithKeyWithAdjust = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap
157 traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove
175 traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove
158
176
177+-- TODO remove this?
178+replaceStart :: (DomBuilder t m, MonadIO m) => m Text
179+replaceStart = do
180+ str <- liftIO $ replicateM 8 $ randomRIO ('a', 'z')
181+ let key = "-" <> T.pack str
182+ _ <- commentNode $ def { _commentNodeConfig_initialContents = "replace-start" <> key }
183+ pure key
184+
185+replaceEnd :: DomBuilder t m => Text -> m ()
186+replaceEnd key = void $ commentNode $ def { _commentNodeConfig_initialContents = "replace-end" <> key }
187+
188+hoistIntMapWithKeyWithAdjust :: forall t m p a b.
189+ ( Adjustable t m
190+ , MonadHold t m
191+ , Patch (p a)
192+ , Functor p
193+ , Patch (p (Behavior t Builder))
194+ , PatchTarget (p (Behavior t Builder)) ~ IntMap (Behavior t Builder)
195+ , Ref m ~ IORef, MonadIO m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m -- TODO remove
196+ )
197+ => (forall x. (IntMap.Key -> a -> m x)
198+ -> IntMap a
199+ -> Event t (p a)
200+ -> m (IntMap x, Event t (p x))
201+ ) -- ^ The base monad's traversal
202+ -> (IntMap.Key -> a -> StaticDomBuilderT t m b)
203+ -> IntMap a
204+ -> Event t (p a)
205+ -> StaticDomBuilderT t m (IntMap b, Event t (p b))
206+hoistIntMapWithKeyWithAdjust base f im0 im' = do
207+ e <- StaticDomBuilderT ask
208+ (children0, children') <- lift $ base (\k v -> runStaticDomBuilderT (f k v) e) im0 im'
209+ let result0 = IntMap.map fst children0
210+ result' = (fmap . fmap) fst children'
211+ outputs0 :: IntMap (Behavior t Builder)
212+ outputs0 = IntMap.map snd children0
213+ outputs' :: Event t (p (Behavior t Builder))
214+ outputs' = (fmap . fmap) snd children'
215+ outputs <- holdIncremental outputs0 outputs'
216+ StaticDomBuilderT $ modify $ (:) $ pull $ do
217+ os <- sample $ currentIncremental outputs
218+ fmap mconcat $ forM (IntMap.toList os) $ \(_, o) -> do
219+ sample o
220+ return (result0, result')
221+
159hoistDMapWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
222hoistDMapWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
160 ( Adjustable t m
223 ( Adjustable t m
161 , MonadHold t m
224 , MonadHold t m
162 , PatchTarget (p k (Constant (Behavior t Builder))) ~ DMap k (Constant (Behavior t Builder))
225 , PatchTarget (p k (Constant (Behavior t Builder))) ~ DMap k (Constant (Behavior t Builder))
163 , Patch (p k (Constant (Behavior t Builder)))
226 , Patch (p k (Constant (Behavior t Builder)))
227+ , Ref m ~ IORef, MonadIO m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m -- TODO remove
164 )
228 )
165 => (forall vv vv'.
229 => (forall vv vv'.
166 (forall a. k a -> vv a -> m (vv' a))
230 (forall a. k a -> vv a -> m (vv' a))
@@ -205,6 +269,15 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
205 Nothing -> return (pure (escape initialContents))
269 Nothing -> return (pure (escape initialContents))
206 Just setContents -> hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
270 Just setContents -> hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
207 return $ TextNode ()
271 return $ TextNode ()
272+ {-# INLINABLE commentNode #-}
273+ commentNode (CommentNodeConfig initialContents mSetContents) = StaticDomBuilderT $ do
274+ --TODO: Do not escape quotation marks; see https://stackoverflow.com/questions/25612166/what-characters-must-be-escaped-in-html-5
275+ shouldEscape <- asks _staticDomBuilderEnv_shouldEscape
276+ let escape = if shouldEscape then fromHtmlEscapedText else byteString . encodeUtf8
277+ modify . (:) =<< (\c -> "<!--" <> c <> "-->") <$> case mSetContents of
278+ Nothing -> return (pure (escape initialContents))
279+ Just setContents -> hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
280+ return $ CommentNode ()
208 {-# INLINABLE element #-}
281 {-# INLINABLE element #-}
209 element elementTag cfg child = do
282 element elementTag cfg child = do
210 -- https://www.w3.org/TR/html-markup/syntax.html#syntax-elements
283 -- https://www.w3.org/TR/html-markup/syntax.html#syntax-elements
@@ -283,12 +356,12 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
283 wrapRawElement () _ = return $ Element (EventSelector $ const never) ()
356 wrapRawElement () _ = return $ Element (EventSelector $ const never) ()
284
357
285--TODO: Make this more abstract --TODO: Put the WithWebView underneath PerformEventT - I think this would perform better
358--TODO: Make this more abstract --TODO: Put the WithWebView underneath PerformEventT - I think this would perform better
286−type StaticWidget x = PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global)))
359+type StaticWidget x = PostBuildT DomTimeline (StaticDomBuilderT DomTimeline (PerformEventT DomTimeline DomHost))
287
360
288{-# INLINE renderStatic #-}
361{-# INLINE renderStatic #-}
289renderStatic :: StaticWidget x a -> IO (a, ByteString)
362renderStatic :: StaticWidget x a -> IO (a, ByteString)
290renderStatic w = do
363renderStatic w = do
291− runSpiderHost $ do
364+ runDomHost $ do
292 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
365 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
293 let env0 = StaticDomBuilderEnv True Nothing
366 let env0 = StaticDomBuilderEnv True Nothing
294 ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild) env0
367 ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild) env0
reflex-dom-core/src/Reflex/Dom/Core.hs+6−1
@@ -1,17 +1,22 @@
1+-- Disable haddocks on this module due to a bug on haddocks when selectively
2+-- reexporting on ghc > 8.2.
3+-- https://github.com/haskell/haddock/issues/979
4+{-# OPTIONS_HADDOCK hide, prune, ignore-exports #-}
1module Reflex.Dom.Core (module X) where
5module Reflex.Dom.Core (module X) where
2
6
3import Reflex as X hiding (askEvents)
7import Reflex as X hiding (askEvents)
4import Reflex.Dom.Builder.Class as X
8import Reflex.Dom.Builder.Class as X
9+import Reflex.Dom.Builder.Hydratable as X
5import Reflex.Dom.Builder.Immediate as X
10import Reflex.Dom.Builder.Immediate as X
6import Reflex.Dom.Builder.InputDisabled as X
11import Reflex.Dom.Builder.InputDisabled as X
7import Reflex.Dom.Builder.Static as X
12import Reflex.Dom.Builder.Static as X
8import Reflex.Dom.Class as X
13import Reflex.Dom.Class as X
9import Reflex.Dom.Location as X
14import Reflex.Dom.Location as X
10import Reflex.Dom.Main as X
15import Reflex.Dom.Main as X
11−import Reflex.Dom.Modals.Base as X
12import Reflex.Dom.Modals.Class as X
16import Reflex.Dom.Modals.Class as X
13import Reflex.Dom.Old as X
17import Reflex.Dom.Old as X
14import Reflex.Dom.Prerender as X
18import Reflex.Dom.Prerender as X
15import Reflex.Dom.WebSocket as X
19import Reflex.Dom.WebSocket as X
16import Reflex.Dom.Widget as X
20import Reflex.Dom.Widget as X
17import Reflex.Dom.Xhr as X
21import Reflex.Dom.Xhr as X
22+import Reflex.Dom.Xhr.FormData as X
reflex-dom-core/src/Reflex/Dom/Location.hs+39
@@ -12,6 +12,7 @@ module Reflex.Dom.Location
12 , getLocationProtocol
12 , getLocationProtocol
13 , getLocationUrl
13 , getLocationUrl
14 , manageHistory
14 , manageHistory
15+ , manageHistory'
15 , HistoryCommand (..)
16 , HistoryCommand (..)
16 , HistoryStateUpdate (..)
17 , HistoryStateUpdate (..)
17 , HistoryItem (..)
18 , HistoryItem (..)
@@ -23,8 +24,11 @@ import Reflex.Dom.Builder.Immediate (wrapDomEvent)
23
24
24import Control.Lens ((^.))
25import Control.Lens ((^.))
25import Control.Monad ((>=>))
26import Control.Monad ((>=>))
27+import Control.Monad.Fix (MonadFix)
28+import Data.Align (align)
26import Data.Monoid
29import Data.Monoid
27import Data.Text (Text)
30import Data.Text (Text)
31+import Data.These (These(..))
28import qualified GHCJS.DOM as DOM
32import qualified GHCJS.DOM as DOM
29import qualified GHCJS.DOM.EventM as DOM
33import qualified GHCJS.DOM.EventM as DOM
30import qualified GHCJS.DOM.Location as Location
34import qualified GHCJS.DOM.Location as Location
@@ -159,3 +163,38 @@ manageHistory runCmd = do
159 <*> getLocationUri location
163 <*> getLocationUri location
160 holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
164 holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
161--TODO: Handle title setting better
165--TODO: Handle title setting better
166+
167+manageHistory'
168+ :: (MonadFix m, MonadJSM m, TriggerEvent t m, MonadHold t m, PerformEvent t m, MonadJSM (Performable m))
169+ => Event t ()
170+ -- ^ Don't do anything until this event has fired
171+ -> Event t HistoryCommand
172+ -> m (Dynamic t HistoryItem)
173+manageHistory' switchover runCmd = do
174+ window <- DOM.currentWindowUnchecked
175+ location <- Window.getLocation window
176+ history <- Window.getHistory window
177+ let getCurrentHistoryItem = HistoryItem
178+ <$> History.getState history
179+ <*> getLocationUri location
180+ item0 <- liftJSM getCurrentHistoryItem
181+ itemSetExternal' <- wrapDomEvent window (`DOM.on` DOM.popState) $ do
182+ e <- DOM.event
183+ HistoryItem
184+ <$> (SerializedScriptValue <$> PopStateEvent.getState e)
185+ <*> getLocationUri location
186+ let f :: (Bool, Maybe a) -> These a () -> (Maybe (Bool, Maybe a), Maybe a)
187+ f (switched, acc) = \case
188+ This change
189+ | switched -> (Nothing, Just change)
190+ | otherwise -> (Just (switched, Just change), Nothing)
191+ That () -> (Just (True, Nothing), acc)
192+ These change () -> (Just (True, Nothing), Just change)
193+ -- Accumulate the events before switchover
194+ (_, cmd') <- mapAccumMaybeB f (False, Nothing) $ align (leftmost [Left <$> runCmd, Right <$> itemSetExternal']) switchover
195+ let (itemSetInternal', itemSetExternal) = fanEither cmd'
196+ itemSetInternal <- performEvent $ ffor itemSetInternal' $ \cmd -> liftJSM $ do
197+ runHistoryCommand history cmd
198+ getCurrentHistoryItem
199+ holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
200+--TODO: Handle title setting better
reflex-dom-core/src/Reflex/Dom/Main.hs+167−36
@@ -17,6 +17,8 @@ module Reflex.Dom.Main where
17
17
18import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
18import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
19
19
20+import Reflex.Adjustable.Class
21+import Reflex.Class
20import Reflex.Dom.Builder.Immediate
22import Reflex.Dom.Builder.Immediate
21import Reflex.Dom.Class
23import Reflex.Dom.Class
22import Reflex.Host.Class
24import Reflex.Host.Class
@@ -24,8 +26,10 @@ import Reflex.PerformEvent.Base
24import Reflex.PostBuild.Base
26import Reflex.PostBuild.Base
25import Reflex.Spider (Global, Spider, SpiderHost, runSpiderHost)
27import Reflex.Spider (Global, Spider, SpiderHost, runSpiderHost)
26import Reflex.TriggerEvent.Base
28import Reflex.TriggerEvent.Base
27−
29+import Reflex.TriggerEvent.Class
28−import Reflex.Dom.Specializations () -- For SPECIALIZATION pragmas, which are always re-exported
30+#ifdef PROFILE_REFLEX
31+import Reflex.Profiled
32+#endif
29
33
30import Control.Concurrent
34import Control.Concurrent
31import Control.Lens
35import Control.Lens
@@ -34,6 +38,7 @@ import Control.Monad.Reader hiding (forM, forM_, mapM, mapM_, sequence, sequence
34import Control.Monad.Ref
38import Control.Monad.Ref
35import Data.ByteString (ByteString)
39import Data.ByteString (ByteString)
36import Data.Dependent.Sum (DSum (..))
40import Data.Dependent.Sum (DSum (..))
41+import Data.Foldable (for_)
37import Data.IORef
42import Data.IORef
38import Data.Maybe
43import Data.Maybe
39import Data.Monoid ((<>))
44import Data.Monoid ((<>))
@@ -52,6 +57,104 @@ import qualified GHCJS.DOM.Types as DOM
52import Reflex.Profiled
57import Reflex.Profiled
53#endif
58#endif
54
59
60+{-# INLINE mainHydrationWidgetWithHead #-}
61+mainHydrationWidgetWithHead :: (forall x. HydrationWidget x ()) -> (forall x. HydrationWidget x ()) -> JSM ()
62+mainHydrationWidgetWithHead = mainHydrationWidgetWithHead'
63+
64+{-# INLINABLE mainHydrationWidgetWithHead' #-}
65+-- | Warning: `mainHydrationWidgetWithHead'` is provided only as performance tweak. It is expected to disappear in future releases.
66+mainHydrationWidgetWithHead' :: HydrationWidget () () -> HydrationWidget () () -> JSM ()
67+mainHydrationWidgetWithHead' = mainHydrationWidgetWithSwitchoverAction' (pure ())
68+
69+{-# INLINE mainHydrationWidgetWithSwitchoverAction #-}
70+mainHydrationWidgetWithSwitchoverAction :: JSM () -> (forall x. HydrationWidget x ()) -> (forall x. HydrationWidget x ()) -> JSM ()
71+mainHydrationWidgetWithSwitchoverAction = mainHydrationWidgetWithSwitchoverAction'
72+
73+{-# INLINABLE mainHydrationWidgetWithSwitchoverAction' #-}
74+-- | Warning: `mainHydrationWidgetWithSwitchoverAction'` is provided only as performance tweak. It is expected to disappear in future releases.
75+mainHydrationWidgetWithSwitchoverAction' :: JSM () -> HydrationWidget () () -> HydrationWidget () () -> JSM ()
76+mainHydrationWidgetWithSwitchoverAction' switchoverAction head' body = do
77+ runHydrationWidgetWithHeadAndBody switchoverAction $ \appendHead appendBody -> do
78+ appendHead head'
79+ appendBody body
80+
81+{-# INLINABLE attachHydrationWidget #-}
82+attachHydrationWidget
83+ :: JSM ()
84+ -> JSContextSingleton ()
85+ -> ( Event DomTimeline ()
86+ -> IORef HydrationMode
87+ -> Maybe (IORef [(Node, HydrationRunnerT DomTimeline (DomCoreWidget ()) ())])
88+ -> EventChannel
89+ -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))
90+ )
91+ -> IO (a, FireCommand DomTimeline DomHost)
92+attachHydrationWidget switchoverAction jsSing w = do
93+ hydrationMode <- liftIO $ newIORef HydrationMode_Hydrating
94+ rootNodesRef <- liftIO $ newIORef []
95+ events <- newChan
96+ runDomHost $ flip runTriggerEventT events $ mdo
97+ (syncEvent, fireSync) <- newTriggerEvent
98+ ((result, postBuildTriggerRef), fc@(FireCommand fire)) <- lift $ hostPerformEventT $ do
99+ a <- w syncEvent hydrationMode (Just rootNodesRef) events
100+ _ <- runWithReplace (return ()) $ delayedAction <$ syncEvent
101+ pure a
102+ mPostBuildTrigger <- readRef postBuildTriggerRef
103+ lift $ forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
104+ liftIO $ fireSync ()
105+ rootNodes <- liftIO $ readIORef rootNodesRef
106+ let delayedAction = do
107+ for_ (reverse rootNodes) $ \(rootNode, runner) -> do
108+ let hydrate = runHydrationRunnerT runner Nothing rootNode events
109+ void $ runWithJSContextSingleton (runPostBuildT hydrate never) jsSing
110+ liftIO $ writeIORef hydrationMode HydrationMode_Immediate
111+ runWithJSContextSingleton (DOM.liftJSM switchoverAction) jsSing
112+ pure (result, fc)
113+
114+type HydrationWidget x a = HydrationDomBuilderT HydrationDomSpace DomTimeline (DomCoreWidget x) a
115+
116+-- | A widget that isn't attached to any particular part of the DOM hierarchy
117+type FloatingWidget x = TriggerEventT DomTimeline (DomCoreWidget x)
118+
119+type DomCoreWidget x = PostBuildT DomTimeline (WithJSContextSingleton x (PerformEventT DomTimeline DomHost))
120+
121+{-# INLINABLE runHydrationWidgetWithHeadAndBody #-}
122+runHydrationWidgetWithHeadAndBody
123+ :: JSM ()
124+ -> ( (forall c. HydrationWidget () c -> FloatingWidget () c) -- "Append to head" --TODO: test invoking this more than once
125+ -> (forall c. HydrationWidget () c -> FloatingWidget () c) -- "Append to body" --TODO: test invoking this more than once
126+ -> FloatingWidget () ()
127+ )
128+ -> JSM ()
129+runHydrationWidgetWithHeadAndBody switchoverAction app = withJSContextSingletonMono $ \jsSing -> do
130+ globalDoc <- currentDocumentUnchecked
131+ headElement <- getHeadUnchecked globalDoc
132+ bodyElement <- getBodyUnchecked globalDoc
133+ (events, fc) <- liftIO . attachHydrationWidget switchoverAction jsSing $ \switchover hydrationMode hydrationResult events -> do
134+ (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
135+ let hydrateDom :: DOM.Node -> HydrationWidget () c -> FloatingWidget () c
136+ hydrateDom n w = do
137+ delayed <- liftIO $ newIORef $ pure ()
138+ unreadyChildren <- liftIO $ newIORef 0
139+ lift $ do
140+ let builderEnv = HydrationDomBuilderEnv
141+ { _hydrationDomBuilderEnv_document = globalDoc
142+ , _hydrationDomBuilderEnv_parent = Left $ toNode n
143+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
144+ , _hydrationDomBuilderEnv_commitAction = pure ()
145+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
146+ , _hydrationDomBuilderEnv_switchover = switchover
147+ , _hydrationDomBuilderEnv_delayed = delayed
148+ }
149+ a <- runHydrationDomBuilderT w builderEnv events
150+ forM_ hydrationResult $ \hr -> do
151+ res <- liftIO $ readIORef delayed
152+ liftIO $ modifyIORef' hr ((n, res) :)
153+ pure a
154+ runWithJSContextSingleton (runPostBuildT (runTriggerEventT (app (hydrateDom $ toNode headElement) (hydrateDom $ toNode bodyElement)) events) postBuild) jsSing
155+ return (events, postBuildTriggerRef)
156+ liftIO $ processAsyncEvents events fc
157+
55{-# INLINE mainWidget #-}
158{-# INLINE mainWidget #-}
56mainWidget :: (forall x. Widget x ()) -> JSM ()
159mainWidget :: (forall x. Widget x ()) -> JSM ()
57mainWidget = mainWidget'
160mainWidget = mainWidget'
@@ -103,40 +206,54 @@ runDomHost = runSpiderHost
103 . runProfiledM
206 . runProfiledM
104#endif
207#endif
105
208
106−type Widget x = PostBuildT DomTimeline (ImmediateDomBuilderT DomTimeline (WithJSContextSingleton x (PerformEventT DomTimeline DomHost))) --TODO: Make this more abstract --TODO: Put the WithJSContext underneath PerformEventT - I think this would perform better because it could avoid fmapping over every performEvent
209+type Widget x = ImmediateDomBuilderT DomTimeline (DomCoreWidget x)
107
210
108{-# INLINABLE attachWidget #-}
211{-# INLINABLE attachWidget #-}
109attachWidget :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> JSM a
212attachWidget :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> JSM a
110attachWidget rootElement wv w = fst <$> attachWidget' rootElement wv w
213attachWidget rootElement wv w = fst <$> attachWidget' rootElement wv w
111
214
112−-- | Warning: `mainWidgetWithHead'` is provided only as performance tweak. It is expected to disappear in future releases.
215+{-# INLINABLE runImmediateWidgetWithHeadAndBody #-}
113−mainWidgetWithHead' :: (a -> Widget () b, b -> Widget () a) -> JSM ()
216+runImmediateWidgetWithHeadAndBody
217+ :: ( (forall c. Widget () c -> FloatingWidget () c) -- "Append to head"
218+ -> (forall c. Widget () c -> FloatingWidget () c) -- "Append to body"
219+ -> FloatingWidget () ()
220+ )
221+ -> JSM ()
114−mainWidgetWithHead' widgets = withJSContextSingletonMono $ \jsSing -> do
222+runImmediateWidgetWithHeadAndBody app = withJSContextSingletonMono $ \jsSing -> do
115− doc <- currentDocumentUnchecked
223+ globalDoc <- currentDocumentUnchecked
116− headElement <- getHeadUnchecked doc
224+ headElement <- getHeadUnchecked globalDoc
117− headFragment <- createDocumentFragment doc
118− bodyElement <- getBodyUnchecked doc
225+ bodyElement <- getBodyUnchecked globalDoc
226+ headFragment <- createDocumentFragment globalDoc
119− bodyFragment <- createDocumentFragment doc
227+ bodyFragment <- createDocumentFragment globalDoc
120− (events, fc) <- liftIO . attachWidget'' $ \events -> do
228+ (events, fc) <- liftIO . attachImmediateWidget $ \hydrationMode events -> do
121− let (headWidget, bodyWidget) = widgets
122 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
229 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
123− let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT DomTimeline DomHost c
230+ let go :: forall c. DOM.DocumentFragment -> Widget () c -> FloatingWidget () c
124− go w df = do
231+ go df w = do
125 unreadyChildren <- liftIO $ newIORef 0
232 unreadyChildren <- liftIO $ newIORef 0
126− let builderEnv = ImmediateDomBuilderEnv
233+ delayed <- liftIO $ newIORef $ pure ()
127− { _immediateDomBuilderEnv_document = toDocument doc
234+ let builderEnv = HydrationDomBuilderEnv
128− , _immediateDomBuilderEnv_parent = toNode df
235+ { _hydrationDomBuilderEnv_document = globalDoc
129− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
236+ , _hydrationDomBuilderEnv_parent = Left $ toNode df
130− , _immediateDomBuilderEnv_commitAction = return () --TODO
237+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
238+ , _hydrationDomBuilderEnv_commitAction = pure () --TODO: possibly `replaceElementContents n f`
239+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
240+ , _hydrationDomBuilderEnv_switchover = never
241+ , _hydrationDomBuilderEnv_delayed = delayed
131 }
242 }
132− runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
243+ lift $ runHydrationDomBuilderT w builderEnv events
133− rec b <- go (headWidget a) headFragment
244+ runWithJSContextSingleton (runPostBuildT (runTriggerEventT (app (go headFragment) (go bodyFragment)) events) postBuild) jsSing
134− a <- go (bodyWidget b) bodyFragment
135 return (events, postBuildTriggerRef)
245 return (events, postBuildTriggerRef)
136 replaceElementContents headElement headFragment
246 replaceElementContents headElement headFragment
137 replaceElementContents bodyElement bodyFragment
247 replaceElementContents bodyElement bodyFragment
138 liftIO $ processAsyncEvents events fc
248 liftIO $ processAsyncEvents events fc
139
249
250+-- | Warning: `mainWidgetWithHead'` is provided only as performance tweak. It is expected to disappear in future releases.
251+mainWidgetWithHead' :: (a -> Widget () b, b -> Widget () a) -> JSM ()
252+mainWidgetWithHead' (h, b) = runImmediateWidgetWithHeadAndBody $ \appendHead appendBody -> do
253+ rec hOut <- appendHead $ h bOut
254+ bOut <- appendBody $ b hOut
255+ pure ()
256+
140replaceElementContents :: DOM.IsElement e => e -> DOM.DocumentFragment -> JSM ()
257replaceElementContents :: DOM.IsElement e => e -> DOM.DocumentFragment -> JSM ()
141replaceElementContents e df = do
258replaceElementContents e df = do
142 setInnerHTML e ("" :: String)
259 setInnerHTML e ("" :: String)
@@ -148,16 +265,20 @@ attachWidget' :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> J
148attachWidget' rootElement jsSing w = do
265attachWidget' rootElement jsSing w = do
149 doc <- getOwnerDocumentUnchecked rootElement
266 doc <- getOwnerDocumentUnchecked rootElement
150 df <- createDocumentFragment doc
267 df <- createDocumentFragment doc
151− ((a, events), fc) <- liftIO . attachWidget'' $ \events -> do
268+ ((a, events), fc) <- liftIO . attachImmediateWidget $ \hydrationMode events -> do
152 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
269 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
153 unreadyChildren <- liftIO $ newIORef 0
270 unreadyChildren <- liftIO $ newIORef 0
154− let builderEnv = ImmediateDomBuilderEnv
271+ delayed <- liftIO $ newIORef $ pure ()
155− { _immediateDomBuilderEnv_document = toDocument doc
272+ let builderEnv = HydrationDomBuilderEnv
156− , _immediateDomBuilderEnv_parent = toNode df
273+ { _hydrationDomBuilderEnv_document = toDocument doc
157− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
274+ , _hydrationDomBuilderEnv_parent = Left $ toNode df
158− , _immediateDomBuilderEnv_commitAction = return () --TODO
275+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
276+ , _hydrationDomBuilderEnv_commitAction = return () --TODO
277+ , _hydrationDomBuilderEnv_switchover = never
278+ , _hydrationDomBuilderEnv_delayed = delayed
279+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
159 }
280 }
160− a <- runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
281+ a <- runWithJSContextSingleton (runPostBuildT (runHydrationDomBuilderT w builderEnv events) postBuild) jsSing
161 return ((a, events), postBuildTriggerRef)
282 return ((a, events), postBuildTriggerRef)
162 replaceElementContents rootElement df
283 replaceElementContents rootElement df
163 liftIO $ processAsyncEvents events fc
284 liftIO $ processAsyncEvents events fc
@@ -165,12 +286,18 @@ attachWidget' rootElement jsSing w = do
165
286
166type EventChannel = Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
287type EventChannel = Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
167
288
168−{-# INLINABLE attachWidget'' #-}
289+{-# INLINABLE attachImmediateWidget #-}
290+attachImmediateWidget
291+ :: ( IORef HydrationMode
292+ -> EventChannel
169−attachWidget'' :: (EventChannel -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))) -> IO (a, FireCommand DomTimeline DomHost)
293+ -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))
170−attachWidget'' w = do
294+ )
295+ -> IO (a, FireCommand DomTimeline DomHost)
296+attachImmediateWidget w = do
297+ hydrationMode <- liftIO $ newIORef HydrationMode_Immediate
171 events <- newChan
298 events <- newChan
172 runDomHost $ do
299 runDomHost $ do
173− ((result, postBuildTriggerRef), fc@(FireCommand fire)) <- hostPerformEventT $ w events
300+ ((result, postBuildTriggerRef), fc@(FireCommand fire)) <- hostPerformEventT $ w hydrationMode events
174 mPostBuildTrigger <- readRef postBuildTriggerRef
301 mPostBuildTrigger <- readRef postBuildTriggerRef
175 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
302 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
176 return (result, fc)
303 return (result, fc)
@@ -186,7 +313,6 @@ processAsyncEvents events (FireCommand fire) = void $ forkIO $ forever $ do
186 liftIO $ forM_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb
313 liftIO $ forM_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb
187 return ()
314 return ()
188
315
189−
190-- | Run a reflex-dom application inside of an existing DOM element with the given ID
316-- | Run a reflex-dom application inside of an existing DOM element with the given ID
191mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> JSM ()
317mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> JSM ()
192mainWidgetInElementById eid w = withJSContextSingleton $ \jsSing -> do
318mainWidgetInElementById eid w = withJSContextSingleton $ \jsSing -> do
@@ -208,8 +334,13 @@ runApp' app = withJSContextSingleton $ \jsSing -> do
208 body <- getBodyUnchecked doc
334 body <- getBodyUnchecked doc
209 win <- getDefaultViewUnchecked doc
335 win <- getDefaultViewUnchecked doc
210 rec o <- attachWidget body jsSing $ do
336 rec o <- attachWidget body jsSing $ do
211− w <- lift $ wrapWindow win $ _appOutput_windowConfig o
337+ w <- wrapWindow win $ _appOutput_windowConfig o
212 app $ AppInput
338 app $ AppInput
213 { _appInput_window = w
339 { _appInput_window = w
214 }
340 }
215 return ()
341 return ()
342+
343+{-# DEPRECATED attachWidget'' "Use 'attachImmediateWidget . const' instead" #-}
344+{-# INLINABLE attachWidget'' #-}
345+attachWidget'' :: (EventChannel -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))) -> IO (a, FireCommand DomTimeline DomHost)
346+attachWidget'' = attachImmediateWidget . const
reflex-dom-core/src/Reflex/Dom/Modals/Base.hsdeleted−199
@@ -1,199 +0,0 @@
1−{-
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE OverloadedStrings #-}
6−{-# LANGUAGE MultiParamTypeClasses #-}
7−{-# LANGUAGE RankNTypes #-}
8−{-# LANGUAGE RecursiveDo #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
10−{-# LANGUAGE TypeFamilies #-}
11−{-# LANGUAGE UndecidableInstances #-}
12−-}
13−module Reflex.Dom.Modals.Base
14−where
15−{-
16− ( ModalsT (..)
17− , ModalLayerConfig (..)
18− , withModalLayer
19− )where
20−
21−import Control.Lens hiding (element)
22−import Control.Monad.Fix
23−import Control.Monad.Primitive
24−import Control.Monad.Ref
25−import Control.Monad.Trans
26−import Data.Coerce
27−import Data.Default
28−import Data.Dependent.Map (DMap, DSum (..), GCompare)
29−import qualified Data.Dependent.Map as DMap
30−import Data.Functor.Compose
31−import Data.Proxy
32−import Data.Semigroup
33−import Data.Text (Text)
34−import qualified Data.Text as T
35−import Foreign.JavaScript.TH
36−import Language.Javascript.JSaddle (MonadJSM)
37−
38−import Data.Functor.Misc
39−import Reflex
40−import Reflex.Dom.Builder.Class
41−import Reflex.Dom.Builder.Immediate
42−import Reflex.Dom.Class
43−import Reflex.Dom.Modals.Class
44−import Reflex.Host.Class
45−
46−instance (Reflex t, PrimMonad m) => ModalOpener t (ModalsT t m) where
47− requestingModal = ModalsT . requesting . fmap Compose
48−
49−newtype ModalsT t m a = ModalsT { unModalsT :: RequesterT t (Compose (ModalsT t m) (Event t)) Maybe m a }
50− deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
51−
52−instance Wrapped (ModalsT t m a) where
53− type Unwrapped (ModalsT t m a) = RequesterT t (Compose (ModalsT t m) (Event t)) Maybe m a
54− _Wrapped' = iso coerce coerce
55−
56−instance ModalsT t m a ~ x => Rewrapped (ModalsT t m a) x
57−
58−instance MonadTrans (ModalsT t) where
59− lift = ModalsT . lift
60−
61−instance (Reflex t, PostBuild t m) => PostBuild t (ModalsT t m) where
62− getPostBuild = view _Unwrapped getPostBuild
63−
64−instance MonadSample t m => MonadSample t (ModalsT t m) where
65− {-# INLINABLE sample #-}
66− sample = lift . sample
67−
68−instance MonadHold t m => MonadHold t (ModalsT t m) where
69− {-# INLINABLE hold #-}
70− hold v0 = lift . hold v0
71− {-# INLINABLE holdDyn #-}
72− holdDyn v0 = lift . holdDyn v0
73− {-# INLINABLE holdIncremental #-}
74− holdIncremental v0 = lift . holdIncremental v0
75−
76−instance PerformEvent t m => PerformEvent t (ModalsT t m) where
77− type Performable (ModalsT t m) = Performable m
78− {-# INLINABLE performEvent_ #-}
79− performEvent_ = lift . performEvent_
80− {-# INLINABLE performEvent #-}
81− performEvent = lift . performEvent
82−
83−instance (ReflexHost t, MonadReflexCreateTrigger t m) => MonadReflexCreateTrigger t (ModalsT t m) where
84− {-# INLINABLE newEventWithTrigger #-}
85− newEventWithTrigger = ModalsT . lift . newEventWithTrigger
86− {-# INLINABLE newFanEventWithTrigger #-}
87− newFanEventWithTrigger f = ModalsT $ lift $ newFanEventWithTrigger f
88−
89−instance TriggerEvent t m => TriggerEvent t (ModalsT t m) where
90− {-# INLINABLE newTriggerEvent #-}
91− newTriggerEvent = lift newTriggerEvent
92− {-# INLINABLE newTriggerEventWithOnComplete #-}
93− newTriggerEventWithOnComplete = lift newTriggerEventWithOnComplete
94− newEventWithLazyTriggerWithOnComplete = lift . newEventWithLazyTriggerWithOnComplete
95−
96−instance MonadRef m => MonadRef (ModalsT t m) where
97− type Ref (ModalsT t m) = Ref m
98− {-# INLINABLE newRef #-}
99− newRef = lift . newRef
100− {-# INLINABLE readRef #-}
101− readRef = lift . readRef
102− {-# INLINABLE writeRef #-}
103− writeRef r = lift . writeRef r
104−
105−instance MonadAtomicRef m => MonadAtomicRef (ModalsT t m) where
106− {-# INLINABLE atomicModifyRef #-}
107− atomicModifyRef r = lift . atomicModifyRef r
108−
109−instance (Adjustable t m, MonadHold t m, MonadFix m) => Adjustable t (ModalsT t m) where
110− runWithReplace a0 a' = ModalsT $ runWithReplace (unModalsT a0) (fmapCheap unModalsT a')
111− traverseDMapWithKeyWithAdjust f dm0 dm' = ModalsT $ traverseDMapWithKeyWithAdjust (coerce f) dm0 dm'
112− traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = ModalsT $ traverseDMapWithKeyWithAdjustWithMove (coerce f) dm0 dm'
113−
114−instance (DomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m) => DomBuilder t (ModalsT t m) where
115− type DomBuilderSpace (ModalsT t m) = DomBuilderSpace m
116− element = coerce (element :: Text -> ElementConfig er t (DomBuilderSpace m) -> Unwrapped (ModalsT t m a) -> Unwrapped (ModalsT t m (Element er (DomBuilderSpace m) t, a)))
117− inputElement = coerce (inputElement :: InputElementConfig er t (DomBuilderSpace m) -> Unwrapped (ModalsT t m (InputElement er (DomBuilderSpace m) t)))
118− textAreaElement = coerce (textAreaElement :: TextAreaElementConfig er t (DomBuilderSpace m) -> Unwrapped (ModalsT t m (TextAreaElement er (DomBuilderSpace m) t)))
119− selectElement = coerce (selectElement :: SelectElementConfig er t (DomBuilderSpace m) -> Unwrapped (ModalsT t m a) -> Unwrapped (ModalsT t m (SelectElement er (DomBuilderSpace m) t, a)))
120− wrapRawElement e = (view _Unwrapped :: forall a. Unwrapped (ModalsT t m a) -> ModalsT t m a) . wrapRawElement e
121−
122−instance HasDocument m => HasDocument (ModalsT t m)
123−instance HasJSContext m => HasJSContext (ModalsT t m) where
124− type JSContextPhantom (ModalsT t m) = JSContextPhantom m
125− askJSContext = ModalsT askJSContext
126−#ifndef ghcjs_HOST_OS
127−instance MonadJSM m => MonadJSM (ModalsT t m)
128−#endif
129−
130−data ModalLayerConfig t m = ModalLayerConfig
131− { _modalLayerConfig_backdropElementConfig :: ElementConfig EventResult t (DomBuilderSpace m)
132− , _modalLayerConfig_bodyElementConfig :: ElementConfig EventResult t (DomBuilderSpace m)
133− }
134−
135−instance (DomBuilder t m) => Default (ModalLayerConfig t m) where
136− def = ModalLayerConfig
137− { _modalLayerConfig_backdropElementConfig = (def :: ElementConfig EventResult t (DomBuilderSpace m))
138− & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (const stopPropagation)
139− & initialAttributes .~ ("style" =: T.intercalate ";"
140− [ "position:absolute"
141− , "left:0", "right:0", "top:0", "bottom:0"
142− , "background-color:rgba(0,0,0,0.1)"
143− , "display:flex", "justify-content:center", "align-items:center"
144− ])
145− , _modalLayerConfig_bodyElementConfig = (def :: ElementConfig EventResult t (DomBuilderSpace m))
146− & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (const stopPropagation)
147− & initialAttributes .~ ("style" =: "background-color:white;opacity:1;padding:1em")
148− }
149−
150−instance (MonadQuery t q m, Monad m) => MonadQuery t q (ModalsT t m) where
151− tellQueryIncremental = lift . tellQueryIncremental
152− askQueryResult = lift askQueryResult
153− queryIncremental = lift . queryIncremental
154−
155−withModalLayer :: forall t m a. (Reflex t, MonadFix m, DomBuilder t m, MonadHold t m)
156− => ModalLayerConfig t m
157− -> ModalsT t m a
158− -> m a
159−withModalLayer cfg (ModalsT a) = do
160− rec (result, requests) <- do
161− runRequesterT a modalDone
162− modalDone <- modalInner requests cfg
163− return result
164−
165−modalInner :: forall t m k. (GCompare k, MonadHold t m, MonadFix m, DomBuilder t m)
166− => Event t (DMap k (Compose (ModalsT t m) (Event t)))
167− -> ModalLayerConfig t m
168− -> m (Event t (DMap k Maybe))
169−modalInner rqs' cfg = do
170− rec let rqs = fmap toInsertionsDMap rqs'
171− dones = fmap toDeletionsDMap modalDone
172− (m0, mpatch) <- traverseDMapWithKeyWithAdjust (\_ v -> Compose <$> modalBody cfg v) DMap.empty (rqs <> dones)
173− modal <- fmap (merge . mapKeyValuePairsMonotonic wrapArgs) . incrementalToDynamic <$> holdIncremental m0 mpatch
174− let modalDone = fmap (mapKeyValuePairsMonotonic unwrapArgs) $ switch $ current modal
175− return modalDone
176− where
177− wrapArgs (k :=> v) = WrapArg k :=> getCompose v
178− unwrapArgs :: DSum (WrapArg Maybe k) Identity -> DSum k Maybe
179− unwrapArgs (WrapArg k :=> Identity v) = k :=> v
180− toInsertionsDMap :: DMap k v -> PatchDMap k v
181− toInsertionsDMap = coerce . DMap.map (ComposeMaybe . Just)
182− toDeletionsDMap :: forall k' v v'. DMap k' v -> PatchDMap k' v'
183− toDeletionsDMap = coerce . DMap.map (\(_ :: v a) -> ComposeMaybe (Nothing :: Maybe (v' a)))
184−
185−modalBody :: forall t m a. (MonadHold t m, MonadFix m, DomBuilder t m)
186− => ModalLayerConfig t m
187− -> Compose (ModalsT t m) (Event t) a
188− -> m (Event t (Maybe a))
189−modalBody cfg v = do
190− (overlay, complete) <- element "div" (_modalLayerConfig_backdropElementConfig cfg) $ do
191− fmap snd $ element "div" (_modalLayerConfig_bodyElementConfig cfg) $ do
192− rec (rsps, rqs') <- runRequesterT (unModalsT (getCompose v)) innerDone
193− innerDone <- modalInner rqs' cfg
194− return rsps
195− return $ leftmost
196− [ Just <$> complete
197− , Nothing <$ domEvent Click overlay
198− ]
199−-}
reflex-dom-core/src/Reflex/Dom/Old.hs−2
@@ -1,6 +1,5 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE ConstraintKinds #-}
3−{-# LANGUAGE ExplicitForAll #-}
4{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
@@ -103,7 +102,6 @@ elConfig_attributes f (ElConfig a b) = (\b' -> ElConfig a b') <$> f b
103{-# INLINE elConfig_attributes #-}
102{-# INLINE elConfig_attributes #-}
104#endif
103#endif
105
104
106−--TODO: HasDocument is still not accounted for
107type MonadWidgetConstraints t m =
105type MonadWidgetConstraints t m =
108 ( DomBuilder t m
106 ( DomBuilder t m
109 , DomBuilderSpace m ~ GhcjsDomSpace
107 , DomBuilderSpace m ~ GhcjsDomSpace
reflex-dom-core/src/Reflex/Dom/Prerender.hs+290−49
@@ -1,82 +1,323 @@
1{-# LANGUAGE ConstraintKinds #-}
1{-# LANGUAGE ConstraintKinds #-}
2−{-# LANGUAGE EmptyDataDecls #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE EmptyCase #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FunctionalDependencies #-}
6{-# LANGUAGE FunctionalDependencies #-}
6−{-# LANGUAGE MultiParamTypeClasses #-}
7+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8+{-# LANGUAGE LambdaCase #-}
9+{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE Rank2Types #-}
10{-# LANGUAGE Rank2Types #-}
11+{-# LANGUAGE RecursiveDo #-}
8{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE TypeFamilies #-}
10−{-# LANGUAGE TypeSynonymInstances #-}
11{-# LANGUAGE UndecidableInstances #-}
14{-# LANGUAGE UndecidableInstances #-}
15+{-# LANGUAGE UndecidableSuperClasses #-}
16+
17+-- | Render the first widget on the server, and the second on the client.
12module Reflex.Dom.Prerender
18module Reflex.Dom.Prerender
13 ( Prerender (..)
19 ( Prerender (..)
14− , prerender
20+ , prerender_
21+ , PrerenderClientConstraint
22+ , PrerenderBaseConstraints
15 ) where
23 ) where
16
24
25+import Control.Monad.Primitive (PrimMonad(..))
17import Control.Monad.Reader
26import Control.Monad.Reader
18−import Data.Constraint
27+import Control.Monad.Ref (MonadRef(..))
28+import Data.IORef (IORef, newIORef)
29+import Data.Semigroup (Semigroup)
30+import Data.Text (Text)
31+import Data.Void
19import Foreign.JavaScript.TH
32import Foreign.JavaScript.TH
20import GHCJS.DOM.Types (MonadJSM)
33import GHCJS.DOM.Types (MonadJSM)
21−import Reflex
34+import Reflex hiding (askEvents)
35+import Reflex.Dom.Builder.Class
22−import Reflex.Dom.Builder.InputDisabled
36+import Reflex.Dom.Builder.Hydratable
23import Reflex.Dom.Builder.Immediate
37import Reflex.Dom.Builder.Immediate
38+import Reflex.Dom.Builder.InputDisabled
24import Reflex.Dom.Builder.Static
39import Reflex.Dom.Builder.Static
25import Reflex.Host.Class
40import Reflex.Host.Class
41+import Data.IntMap.Strict (IntMap)
42+import qualified Data.IntMap.Strict as IntMap
26
43
44+import qualified GHCJS.DOM.Document as Document
45+import qualified GHCJS.DOM.Node as Node
46+import qualified GHCJS.DOM.Types as DOM
47+
27−type PrerenderClientConstraint js m =
48+type PrerenderClientConstraint js t m =
28− ( HasJS js m
49+ ( DomBuilder t m
29− , HasJS js (Performable m)
50+ , DomBuilderSpace m ~ GhcjsDomSpace
30− , MonadJSM m
51+ , DomRenderHook t m
52+ , HasDocument m
53+ , TriggerEvent t m
54+ , PrerenderBaseConstraints js t m
55+ )
56+
57+type PrerenderBaseConstraints js t m =
31− , MonadJSM (Performable m)
58+ ( HasJSContext (Performable m)
32 , HasJSContext m
59 , HasJSContext m
33− , HasJSContext (Performable m)
34 , MonadFix m
60 , MonadFix m
61+ , MonadHold t m
62+ , MonadJSM (Performable m)
63+ , MonadJSM m
64+ , MonadRef (Performable m)
65+ , MonadRef m
66+ , MonadReflexCreateTrigger t m
67+ , MonadSample t (Performable m)
68+ , PerformEvent t m
69+ , PostBuild t m
70+ , PrimMonad m
71+ , Ref (Performable m) ~ IORef
72+ , Ref m ~ IORef
73+ , HasJS js m
35− , MonadFix (Performable m)
74+ , HasJS js (Performable m)
36 )
75 )
37
76
38−class Prerender js m | m -> js where
77+-- | Render the first widget on the server, and the second on the client. The
39− prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m))
78+-- hydration builder will run *both* widgets.
79+prerender_
80+ :: (Functor m, Reflex t, Prerender js t m)
81+ => m () -> Client m () -> m ()
82+prerender_ server client = void $ prerender server client
83+
84+class (PrerenderClientConstraint js t (Client m), Client (Client m) ~ Client m, Prerender js t (Client m)) => Prerender js t m | m -> t js where
85+ -- | Monad in which the client widget is built
86+ type Client m :: * -> *
87+ -- | Render the first widget on the server, and the second on the client. The
88+ -- hydration builder will run *both* widgets, updating the result dynamic at
89+ -- switchover time.
90+ prerender :: m a -> Client m a -> m (Dynamic t a)
91+
92+instance (ReflexHost t, Adjustable t m, PrerenderBaseConstraints js t m) => Prerender js t (HydrationDomBuilderT GhcjsDomSpace t m) where
93+ type Client (HydrationDomBuilderT GhcjsDomSpace t m) = HydrationDomBuilderT GhcjsDomSpace t m
94+ prerender _ client = pure <$> client
95+
96+instance (Adjustable t m, PrerenderBaseConstraints js t m, ReflexHost t) => Prerender js t (HydrationDomBuilderT HydrationDomSpace t m) where
97+ -- | PostBuildT is needed here because we delay running the client builder
98+ -- until after switchover, at which point the postBuild of @m@ has already fired
99+ type Client (HydrationDomBuilderT HydrationDomSpace t m) = PostBuildT t (HydrationDomBuilderT GhcjsDomSpace t m)
100+ -- | Runs the server widget up until switchover, then replaces it with the
101+ -- client widget.
102+ prerender server client = do
103+ env <- HydrationDomBuilderT ask
104+ events <- askEvents
105+ doc <- askDocument
106+ serverDf <- Document.createDocumentFragment doc -- server dom should not be mounted in the window's doc in hydration
107+ df <- Document.createDocumentFragment doc
108+ unreadyChildren <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_unreadyChildren
109+ immediateMode <- liftIO $ newIORef HydrationMode_Immediate
110+ delayed <- liftIO $ newIORef $ pure ()
111+ let clientEnv = env
112+ { _hydrationDomBuilderEnv_parent = Left $ DOM.toNode df
113+ , _hydrationDomBuilderEnv_hydrationMode = immediateMode
114+ }
115+ serverEnv = HydrationDomBuilderEnv
116+ { _hydrationDomBuilderEnv_document = doc
117+ , _hydrationDomBuilderEnv_parent = Left $ DOM.toNode serverDf
118+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
119+ , _hydrationDomBuilderEnv_commitAction = pure ()
120+ , _hydrationDomBuilderEnv_delayed = delayed
121+ , _hydrationDomBuilderEnv_hydrationMode = immediateMode
122+ , _hydrationDomBuilderEnv_switchover = never
123+ }
124+ a0 <- lift $ runHydrationDomBuilderT server serverEnv events
125+ (a', trigger) <- newTriggerEvent
126+ getHydrationMode >>= \case
127+ HydrationMode_Immediate -> do
128+ liftIO . trigger <=< lift $ runHydrationDomBuilderT (runPostBuildT client $ void a') clientEnv events
129+ append $ DOM.toNode df
130+ HydrationMode_Hydrating -> addHydrationStep $ do
131+ liftIO . trigger <=< lift $ runHydrationDomBuilderT (runPostBuildT client $ void a') clientEnv events
132+ insertBefore df =<< deleteToPrerenderEnd doc
133+ holdDyn a0 a'
134+
135+newtype UnrunnableT js t m a = UnrunnableT (ReaderT Void m a)
136+ deriving (Functor, Applicative, Monad, MonadTrans)
137+
138+unrunnable :: UnrunnableT js t m a
139+unrunnable = UnrunnableT $ ReaderT $ \case {}
140+
141+instance (Reflex t, Monad m) => DomBuilder t (UnrunnableT js t m) where
142+ type DomBuilderSpace (UnrunnableT js t m) = GhcjsDomSpace
143+ textNode _ = unrunnable
144+ commentNode _ = unrunnable
145+ element _ _ _ = unrunnable
146+ inputElement _ = unrunnable
147+ textAreaElement _ = unrunnable
148+ selectElement _ _ = unrunnable
149+ placeRawElement _ = unrunnable
150+ wrapRawElement _ _ = unrunnable
151+instance (Reflex t, Monad m) => NotReady t (UnrunnableT js t m) where
152+ notReadyUntil _ = unrunnable
153+ notReady = unrunnable
154+instance (Reflex t, Monad m) => Adjustable t (UnrunnableT js t m) where
155+ runWithReplace _ _ = unrunnable
156+ traverseIntMapWithKeyWithAdjust _ _ _ = unrunnable
157+ traverseDMapWithKeyWithAdjust _ _ _ = unrunnable
158+ traverseDMapWithKeyWithAdjustWithMove _ _ _ = unrunnable
159+instance (Reflex t, Monad m) => PerformEvent t (UnrunnableT js t m) where
160+ type Performable (UnrunnableT js t m) = UnrunnableT js t m
161+ performEvent _ = unrunnable
162+ performEvent_ _ = unrunnable
163+instance Monad m => MonadRef (UnrunnableT js t m) where
164+ type Ref (UnrunnableT js t m) = Ref IO
165+ newRef _ = unrunnable
166+ readRef _ = unrunnable
167+ writeRef _ _ = unrunnable
168+instance Monad m => HasDocument (UnrunnableT js t m) where
169+ askDocument = unrunnable
170+instance Monad m => HasJSContext (UnrunnableT js t m) where
171+ type JSContextPhantom (UnrunnableT js t m) = ()
172+ askJSContext = unrunnable
173+instance Monad m => HasJS JS' (UnrunnableT js t m) where
174+ type JSX (UnrunnableT js t m) = UnrunnableT js t m
175+ liftJS _ = unrunnable
176+instance Monad m => MonadJS JS' (UnrunnableT js t m) where
177+ runJS _ _ = unrunnable
178+ forkJS _ = unrunnable
179+ mkJSUndefined = unrunnable
180+ isJSNull _ = unrunnable
181+ isJSUndefined _ = unrunnable
182+ fromJSBool _ = unrunnable
183+ fromJSString _ = unrunnable
184+ fromJSArray _ = unrunnable
185+ fromJSUint8Array _ = unrunnable
186+ fromJSNumber _ = unrunnable
187+ withJSBool _ _ = unrunnable
188+ withJSString _ _ = unrunnable
189+ withJSNumber _ _ = unrunnable
190+ withJSArray _ _ = unrunnable
191+ withJSUint8Array _ _ = unrunnable
192+ mkJSFun _ = unrunnable
193+ freeJSFun _ = unrunnable
194+ setJSProp _ _ _ = unrunnable
195+ getJSProp _ _ = unrunnable
196+ withJSNode _ _ = unrunnable
197+instance Monad m => TriggerEvent t (UnrunnableT js t m) where
198+ newTriggerEvent = unrunnable
199+ newTriggerEventWithOnComplete = unrunnable
200+ newEventWithLazyTriggerWithOnComplete _ = unrunnable
201+instance Monad m => MonadReflexCreateTrigger t (UnrunnableT js t m) where
202+ newEventWithTrigger _ = unrunnable
203+ newFanEventWithTrigger _ = unrunnable
204+instance Monad m => MonadFix (UnrunnableT js t m) where
205+ mfix _ = unrunnable
206+instance Monad m => MonadHold t (UnrunnableT js t m) where
207+ hold _ _ = unrunnable
208+ holdDyn _ _ = unrunnable
209+ holdIncremental _ _ = unrunnable
210+ buildDynamic _ _ = unrunnable
211+ headE _ = unrunnable
212+instance Monad m => MonadSample t (UnrunnableT js t m) where
213+ sample _ = unrunnable
214+instance Monad m => MonadIO (UnrunnableT js t m) where
215+ liftIO _ = unrunnable
216+#ifndef ghcjs_HOST_OS
217+instance Monad m => MonadJSM (UnrunnableT js t m) where
218+ liftJSM' _ = unrunnable
219+#endif
220+instance (Reflex t, Monad m) => PostBuild t (UnrunnableT js t m) where
221+ getPostBuild = unrunnable
222+instance Monad m => PrimMonad (UnrunnableT js t m) where
223+ type PrimState (UnrunnableT js t m) = PrimState IO
224+ primitive _ = unrunnable
225+instance (Reflex t, Monad m) => DomRenderHook t (UnrunnableT js t m) where
226+ withRenderHook _ _ = unrunnable
227+ requestDomAction _ = unrunnable
228+ requestDomAction_ _ = unrunnable
229+instance (Reflex t, Monad m) => Prerender JS' t (UnrunnableT js t m) where
230+ type Client (UnrunnableT js t m) = UnrunnableT js t m
231+ prerender _ _ = unrunnable
232+
233+instance (SupportsStaticDomBuilder t m) => Prerender JS' t (StaticDomBuilderT t m) where
234+ type Client (StaticDomBuilderT t m) = UnrunnableT JS' t m
235+ prerender server _ = do
236+ _ <- commentNode $ CommentNodeConfig startMarker Nothing
237+ a <- server
238+ _ <- commentNode $ CommentNodeConfig endMarker Nothing
239+ pure $ pure a
40
240
41−-- | Draw one widget when prerendering (e.g. server-side) and another when the
241+instance (Prerender js t m, Monad m) => Prerender js t (ReaderT r m) where
42−-- widget is fully instantiated. In a given execution of this function, there
242+ type Client (ReaderT r m) = ReaderT r (Client m)
43−-- will be exactly one invocation of exactly one of the arguments.
243+ prerender server client = do
44−prerender :: forall js m a. Prerender js m => m a -> (PrerenderClientConstraint js m => m a) -> m a
244+ r <- ask
45−prerender server client = case prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)) of
245+ lift $ prerender (runReaderT server r) (runReaderT client r)
46− Nothing -> server
47− Just Dict -> client
48
246
49−instance ( HasJS js m
247+instance (Prerender js t m, Monad m, Reflex t, MonadFix m, Monoid w) => Prerender js t (DynamicWriterT t w m) where
50− , HasJS js (Performable m)
248+ type Client (DynamicWriterT t w m) = DynamicWriterT t w (Client m)
51− , HasJSContext m
249+ prerender server client = do
52− , HasJSContext (Performable m)
250+ x <- lift $ prerender (runDynamicWriterT server) (runDynamicWriterT client)
53− , MonadJSM m
251+ let (a, w') = splitDynPure x
54− , MonadJSM (Performable m)
252+ w = join w'
55− , MonadFix m
253+ tellDyn w
56− , MonadFix (Performable m)
254+ pure a
57− , ReflexHost t
58− ) => Prerender js (ImmediateDomBuilderT t m) where
59− prerenderClientDict = Just Dict
60
255
61−data NoJavaScript -- This type should never have a HasJS instance
256+instance (Prerender js t m, Monad m, Reflex t, Semigroup w) => Prerender js t (EventWriterT t w m) where
257+ type Client (EventWriterT t w m) = EventWriterT t w (Client m)
258+ prerender server client = do
259+ x <- lift $ prerender (runEventWriterT server) (runEventWriterT client)
260+ let (a, w') = splitDynPure x
261+ w = switch $ current w'
262+ tellEvent w
263+ pure a
62
264
63−instance js ~ NoJavaScript => Prerender js (StaticDomBuilderT t m) where
265+instance (Prerender js t m, MonadFix m, Reflex t) => Prerender js t (RequesterT t request response m) where
64− prerenderClientDict = Nothing
266+ type Client (RequesterT t request response m) = RequesterT t request response (Client m)
267+ prerender server client = mdo
268+ let fannedResponses = fanInt responses
269+ withFannedResponses :: forall m' a. Monad m' => RequesterT t request response m' a -> Int -> m' (a, Event t (IntMap (RequesterData request)))
270+ withFannedResponses w selector = do
271+ (x, e) <- runRequesterT w (selectInt fannedResponses selector)
272+ pure (x, fmapCheap (IntMap.singleton selector) e)
273+ (result, requestsDyn) <- fmap splitDynPure $ lift $ prerender (withFannedResponses server 0) (withFannedResponses client 1)
274+ responses <- fmap (fmapCheap unMultiEntry) $ requesting' $ fmapCheap multiEntry $ switchPromptlyDyn requestsDyn
275+ return result
65
276
66−instance (Prerender js m, ReflexHost t) => Prerender js (PostBuildT t m) where
277+instance (Prerender js t m, Monad m, Reflex t, MonadFix m, Group q, Additive q, Query q, Eq q) => Prerender js t (QueryT t q m) where
67− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
278+ type Client (QueryT t q m) = QueryT t q (Client m)
279+ prerender server client = mdo
280+ result <- queryDyn query
281+ x <- lift $ prerender (runQueryT server result) (runQueryT client result)
282+ let (a, inc) = splitDynPure x
283+ query = incrementalToDynamic =<< inc -- Can we avoid the incrementalToDynamic?
284+ pure a
68
285
69−instance Prerender js m => Prerender js (DynamicWriterT t w m) where
286+instance (Prerender js t m, Monad m) => Prerender js t (InputDisabledT m) where
70− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
287+ type Client (InputDisabledT m) = InputDisabledT (Client m)
288+ prerender (InputDisabledT server) (InputDisabledT client) = InputDisabledT $ prerender server client
71
289
72−instance Prerender js m => Prerender js (ReaderT w m) where
290+instance (Prerender js t m, Monad m) => Prerender js t (HydratableT m) where
73− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
291+ type Client (HydratableT m) = HydratableT (Client m)
292+ prerender (HydratableT server) (HydratableT client) = HydratableT $ prerender server client
74
293
75−instance Prerender js m => Prerender js (RequesterT t request response m) where
294+instance (Prerender js t m, Monad m, ReflexHost t) => Prerender js t (PostBuildT t m) where
76− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
295+ type Client (PostBuildT t m) = PostBuildT t (Client m)
296+ prerender server client = PostBuildT $ do
297+ pb <- ask
298+ lift $ prerender (runPostBuildT server pb) (runPostBuildT client pb)
77
299
78−instance Prerender js m => Prerender js (QueryT t q m) where
300+startMarker, endMarker :: Text
79− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
301+startMarker = "prerender/start"
302+endMarker = "prerender/end"
80
303
81−instance Prerender js m => Prerender js (InputDisabledT m) where
304+deleteToPrerenderEnd :: (MonadIO m, MonadJSM m, Reflex t, MonadFix m) => DOM.Document -> HydrationRunnerT t m DOM.Comment
82− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
305+deleteToPrerenderEnd doc = do
306+ startNode <- hydrateComment doc startMarker Nothing
307+ let go (n :: Int) lastNode = Node.getNextSibling lastNode >>= \case
308+ Nothing -> do
309+ c <- Document.createComment doc endMarker
310+ insertAfterPreviousNode c
311+ pure c
312+ Just node -> DOM.castTo DOM.Comment node >>= \case
313+ Nothing -> go n node
314+ Just c -> Node.getTextContentUnchecked c >>= \case
315+ t | t == startMarker -> go (succ n) node
316+ | t == endMarker -> case n of
317+ 0 -> pure c
318+ _ -> go (pred n) node
319+ | otherwise -> go n node
320+ endNode <- go 0 $ DOM.toNode startNode
321+ deleteBetweenExclusive startNode endNode
322+ setPreviousNode $ Just $ DOM.toNode endNode
323+ pure endNode
reflex-dom-core/src/Reflex/Dom/Specializations.hsdeleted−62
@@ -1,62 +0,0 @@
1−{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE GADTs #-}
3−{-# LANGUAGE Rank2Types #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−module Reflex.Dom.Specializations where
6−
7−import Data.Text (Text)
8−import Reflex.Spider
9−import Reflex.Class
10−import Foreign.JavaScript.TH
11−import Reflex.PerformEvent.Base
12−import Reflex.PostBuild.Base
13−import Reflex.Requester.Base
14−import Reflex.TriggerEvent.Base
15−import Reflex.Dom.Builder.Class
16−import Reflex.Dom.Builder.Immediate
17−import Data.Functor.Identity
18−import Data.Functor.Compose
19−import Data.IORef
20−import GHCJS.DOM.Types (JSM)
21−import Data.IntMap.Strict (IntMap)
22−import qualified Data.IntMap.Strict as IntMap
23−import Data.FastMutableIntMap
24−
25−import qualified GHCJS.DOM.Element as DOM
26−import qualified GHCJS.DOM.Types as DOM
27−
28−{-# SPECIALIZE makeElement :: forall er a. Text -> ElementConfig er (SpiderTimeline Global) GhcjsDomSpace -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) a -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) ((Element er GhcjsDomSpace (SpiderTimeline Global), a), DOM.Element) #-}
29−
30−{-# SPECIALIZE wrap :: forall er. RawElement GhcjsDomSpace -> RawElementConfig er (SpiderTimeline Global) GhcjsDomSpace -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (Element er GhcjsDomSpace (SpiderTimeline Global)) #-}
31−
32−{-# SPECIALIZE mapIntMapWithAdjustImpl :: forall v v'.
33− ( (IntMap.Key -> (Event (SpiderTimeline Global) (), v) -> (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) v')
34− -> IntMap (Event (SpiderTimeline Global) (), v)
35− -> Event (SpiderTimeline Global) (PatchIntMap (Event (SpiderTimeline Global) (), v))
36− -> (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v'))
37− )
38− -> (IntMap.Key -> v -> PostBuildT (SpiderTimeline Global) (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) v')
39− -> IntMap v
40− -> Event (SpiderTimeline Global) (PatchIntMap v)
41− -> PostBuildT (SpiderTimeline Global) (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v'))
42− #-}
43−
44−{-# SPECIALIZE append :: DOM.Node -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) () #-}
45−
46−{-# SPECIALIZE drawChildUpdate :: forall k v' a. ImmediateDomBuilderEnv (SpiderTimeline Global) -> (IORef (ChildReadyState k) -> JSM ()) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (v' a) -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a) #-}
47−
48−{-# SPECIALIZE traverseIntMapWithKeyWithAdjust' :: forall v v'. (IntMap.Key -> v -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) v') -> IntMap v -> Event (SpiderTimeline Global) (PatchIntMap v) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v')) #-}
49−
50−{-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust :: forall v v'.
51− ( (IntMap.Key -> v -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
52− -> IntMap v
53− -> Event (SpiderTimeline Global) (PatchIntMap v)
54− -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event (SpiderTimeline Global) (PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')))
55− )
56− -> (PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt)))
57− -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ())
58− -> (IntMap.Key -> v -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) v')
59− -> IntMap v
60− -> Event (SpiderTimeline Global) (PatchIntMap v)
61− -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v'))
62− #-}
reflex-dom-core/src/Reflex/Dom/WebSocket.hs+14−5
@@ -46,6 +46,7 @@ import Control.Monad.IO.Class
46import Control.Monad.State
46import Control.Monad.State
47import Data.Aeson
47import Data.Aeson
48import Data.ByteString (ByteString)
48import Data.ByteString (ByteString)
49+import Data.ByteString.Lazy (toStrict)
49import Data.Default
50import Data.Default
50import Data.IORef
51import Data.IORef
51import Data.JSString.Text
52import Data.JSString.Text
@@ -54,6 +55,7 @@ import Data.Text
54import Data.Text.Encoding
55import Data.Text.Encoding
55import Foreign.JavaScript.Utils (jsonDecode)
56import Foreign.JavaScript.Utils (jsonDecode)
56import GHCJS.DOM.Types (runJSM, askJSM, MonadJSM, liftJSM, JSM)
57import GHCJS.DOM.Types (runJSM, askJSM, MonadJSM, liftJSM, JSM)
58+import GHCJS.DOM.WebSocket (getReadyState)
57import GHCJS.Marshal
59import GHCJS.Marshal
58import qualified Language.Javascript.JSaddle.Monad as JS (catch)
60import qualified Language.Javascript.JSaddle.Monad as JS (catch)
59
61
@@ -100,7 +102,7 @@ webSocket' url config onRawMessage = do
100 liftIO $ triggerEClose args
102 liftIO $ triggerEClose args
101 _ <- liftIO $ atomically $ tryTakeTMVar isOpen
103 _ <- liftIO $ atomically $ tryTakeTMVar isOpen
102 liftIO $ writeIORef currentSocketRef Nothing
104 liftIO $ writeIORef currentSocketRef Nothing
103− when (_webSocketConfig_reconnect config) $ do
105+ when (_webSocketConfig_reconnect config) $ forkJSM $ do
104 liftIO $ threadDelay 1000000
106 liftIO $ threadDelay 1000000
105 start
107 start
106 start = do
108 start = do
@@ -126,9 +128,11 @@ webSocket' url config onRawMessage = do
126 mws <- liftIO $ readIORef currentSocketRef
128 mws <- liftIO $ readIORef currentSocketRef
127 success <- case mws of
129 success <- case mws of
128 Nothing -> return False
130 Nothing -> return False
131+ Just ws -> flip runJSM ctx $ do
132+ rs <- getReadyState $ unWebSocket ws
133+ if rs == 1
129− Just ws -> runJSM ((webSocketSend ws payload >> return True)
134+ then (webSocketSend ws payload >> return True) `JS.catch` (\(_ :: SomeException) -> return False)
130− `JS.catch`
135+ else return False
131− (\(_ :: SomeException) -> return False)) ctx
132 unless success $ atomically $ unGetTQueue payloadQueue payload
136 unless success $ atomically $ unGetTQueue payloadQueue payload
133 return $ RawWebSocket eRecv eOpen eError eClose
137 return $ RawWebSocket eRecv eOpen eError eClose
134
138
@@ -137,9 +141,14 @@ textWebSocket url cfg = webSocket' url cfg (either (return . decodeUtf8) fromJSV
137
141
138jsonWebSocket :: (ToJSON a, FromJSON b, MonadJSM m, MonadJSM (Performable m), HasJSContext m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, Reflex t) => Text -> WebSocketConfig t a -> m (RawWebSocket t (Maybe b))
142jsonWebSocket :: (ToJSON a, FromJSON b, MonadJSM m, MonadJSM (Performable m), HasJSContext m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, Reflex t) => Text -> WebSocketConfig t a -> m (RawWebSocket t (Maybe b))
139jsonWebSocket url cfg = do
143jsonWebSocket url cfg = do
140− ws <- textWebSocket url $ cfg { _webSocketConfig_send = fmap encode <$> _webSocketConfig_send cfg }
144+ ws <- textWebSocket url $ cfg { _webSocketConfig_send = fmap (decodeUtf8 . toStrict . encode) <$> _webSocketConfig_send cfg }
141 return ws { _webSocket_recv = jsonDecode . textToJSString <$> _webSocket_recv ws }
145 return ws { _webSocket_recv = jsonDecode . textToJSString <$> _webSocket_recv ws }
142
146
147+forkJSM :: JSM () -> JSM ()
148+forkJSM a = do
149+ jsm <- askJSM
150+ void $ liftIO $ forkIO $ runJSM a jsm
151+
143#ifdef USE_TEMPLATE_HASKELL
152#ifdef USE_TEMPLATE_HASKELL
144makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig
153makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig
145makeLensesWith (lensRules & simpleLenses .~ True) ''RawWebSocket
154makeLensesWith (lensRules & simpleLenses .~ True) ''RawWebSocket
reflex-dom-core/src/Reflex/Dom/WebSocket/Foreign.hs+37−23
@@ -12,7 +12,6 @@ module Reflex.Dom.WebSocket.Foreign
12
12
13import Prelude hiding (all, concat, concatMap, div, mapM, mapM_, sequence, span)
13import Prelude hiding (all, concat, concatMap, div, mapM, mapM_, sequence, span)
14
14
15−import Control.Monad.Reader
16import Data.Bifoldable
15import Data.Bifoldable
17import Data.ByteString (ByteString)
16import Data.ByteString (ByteString)
18import qualified Data.ByteString.Lazy as LBS
17import qualified Data.ByteString.Lazy as LBS
@@ -20,12 +19,11 @@ import Data.Text (Text)
20import Data.Text.Encoding
19import Data.Text.Encoding
21import Foreign.JavaScript.Utils (bsFromMutableArrayBuffer, bsToArrayBuffer)
20import Foreign.JavaScript.Utils (bsFromMutableArrayBuffer, bsToArrayBuffer)
22import GHCJS.DOM.CloseEvent
21import GHCJS.DOM.CloseEvent
23−import GHCJS.DOM.EventM (on)
24import GHCJS.DOM.MessageEvent
22import GHCJS.DOM.MessageEvent
25−import GHCJS.DOM.Types (JSM, JSVal, liftJSM, fromJSValUnchecked)
23+import GHCJS.DOM.Types (JSM, JSVal, liftJSM, fromJSValUnchecked, WebSocket(..))
26−import GHCJS.DOM.WebSocket (WebSocket)
27import qualified GHCJS.DOM.WebSocket as DOM
24import qualified GHCJS.DOM.WebSocket as DOM
28import GHCJS.Foreign (JSType(..), jsTypeOf)
25import GHCJS.Foreign (JSType(..), jsTypeOf)
26+import Language.Javascript.JSaddle (fun, eval, toJSVal, call)
29import Language.Javascript.JSaddle.Helper (mutableArrayBufferFromJSVal)
27import Language.Javascript.JSaddle.Helper (mutableArrayBufferFromJSVal)
30import Language.Javascript.JSaddle.Types (ghcjsPure)
28import Language.Javascript.JSaddle.Types (ghcjsPure)
31
29
@@ -64,25 +62,41 @@ newWebSocket
64 -> ((Bool, Word, Text) -> JSM ()) -- onclose
62 -> ((Bool, Word, Text) -> JSM ()) -- onclose
65 -> JSM JSWebSocket
63 -> JSM JSWebSocket
66newWebSocket _ url protocols onMessage onOpen onError onClose = do
64newWebSocket _ url protocols onMessage onOpen onError onClose = do
67− ws <- DOM.newWebSocket url protocols
65+ let onOpenWrapped = fun $ \_ _ _ -> onOpen
68− DOM.setBinaryType ws "arraybuffer"
66+ onErrorWrapped = fun $ \_ _ _ -> onError
69− _ <- on ws DOM.open $ liftJSM onOpen
67+ onCloseWrapped = fun $ \_ _ (e:_) -> do
70− _ <- on ws DOM.error $ liftJSM onError
68+ let e' = CloseEvent e
71− _ <- on ws DOM.closeEvent $ do
72− e <- ask
73− wasClean <- getWasClean e
69+ wasClean <- getWasClean e'
74− code <- getCode e
70+ code <- getCode e'
75− reason <- getReason e
71+ reason <- getReason e'
76− liftJSM $ onClose (wasClean, code, reason)
72+ liftJSM $ onClose (wasClean, code, reason)
77− _ <- on ws DOM.message $ do
73+ onMessageWrapped = fun $ \_ _ (e:_) -> do
78− e <- ask
74+ let e' = MessageEvent e
79− d <- getData e
75+ d <- getData e'
80− liftJSM $ ghcjsPure (jsTypeOf d) >>= \case
76+ liftJSM $ ghcjsPure (jsTypeOf d) >>= \case
81− String -> onMessage $ Right d
77+ String -> onMessage $ Right d
82− _ -> do
78+ _ -> do
83− ab <- mutableArrayBufferFromJSVal d
79+ ab <- mutableArrayBufferFromJSVal d
84− bsFromMutableArrayBuffer ab >>= onMessage . Left
80+ bsFromMutableArrayBuffer ab >>= onMessage . Left
81+ newWS <- eval $ unlines
82+ [ "(function(url, protos, open, error, close, message) {"
83+ , " var ws = new window['WebSocket'](url, protos);"
84+ , " ws['binaryType'] = 'arraybuffer';"
85+ , " ws['addEventListener']('open', open);"
86+ , " ws['addEventListener']('error', error);"
87+ , " ws['addEventListener']('close', close);"
88+ , " ws['addEventListener']('message', message);"
89+ , " return ws;"
90+ , "})"
91+ ]
92+ url' <- toJSVal url
93+ protocols' <- toJSVal protocols
94+ onOpen' <- toJSVal onOpenWrapped
95+ onError' <- toJSVal onErrorWrapped
96+ onClose' <- toJSVal onCloseWrapped
97+ onMessage' <- toJSVal onMessageWrapped
98+ ws <- call newWS newWS [url', protocols', onOpen', onError', onClose', onMessage']
85− return $ JSWebSocket ws
99+ return $ JSWebSocket $ WebSocket ws
86
100
87onBSMessage :: Either ByteString JSVal -> JSM ByteString
101onBSMessage :: Either ByteString JSVal -> JSM ByteString
88onBSMessage = either return $ fmap encodeUtf8 . fromJSValUnchecked
102onBSMessage = either return $ fmap encodeUtf8 . fromJSValUnchecked
reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+55−44
@@ -10,12 +10,12 @@
10{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE ScopedTypeVariables #-}
11{-# LANGUAGE TypeFamilies #-}
11{-# LANGUAGE TypeFamilies #-}
12module Reflex.Dom.Widget.Basic
12module Reflex.Dom.Widget.Basic
13− ( partitionMapBySetLT
13+ (
14− , ChildResult (..)
15−
16 -- * Displaying Values
14 -- * Displaying Values
17− , text
15+ text
18 , dynText
16 , dynText
17+ , comment
18+ , dynComment
19 , display
19 , display
20 , button
20 , button
21 , dyn
21 , dyn
@@ -57,15 +57,16 @@ module Reflex.Dom.Widget.Basic
57 , module Data.Map.Misc
57 , module Data.Map.Misc
58 , module Reflex.Collection
58 , module Reflex.Collection
59 , module Reflex.Workflow
59 , module Reflex.Workflow
60+ , partitionMapBySetLT
60 ) where
61 ) where
61
62
63+import Reflex.Adjustable.Class
62import Reflex.Class
64import Reflex.Class
63import Reflex.Collection
65import Reflex.Collection
64import Reflex.Dom.Builder.Class
66import Reflex.Dom.Builder.Class
65import Reflex.Dom.Class
67import Reflex.Dom.Class
66import Reflex.Dynamic
68import Reflex.Dynamic
67import Reflex.Network
69import Reflex.Network
68−import Reflex.NotReady.Class
69import Reflex.PostBuild.Class
70import Reflex.PostBuild.Class
70import Reflex.Workflow
71import Reflex.Workflow
71
72
@@ -94,6 +95,7 @@ import Prelude hiding (mapM, mapM_, sequence, sequence_)
94-- No empty pieces will be included in the output.
95-- No empty pieces will be included in the output.
95
96
96--TODO: This can probably be done more efficiently by dividing and conquering, re-using the structure of the Set instead of going through the Set linearally
97--TODO: This can probably be done more efficiently by dividing and conquering, re-using the structure of the Set instead of going through the Set linearally
98+{-# DEPRECATED partitionMapBySetLT "This will be removed in future releases." #-}
97partitionMapBySetLT :: forall k v. Ord k => Set k -> Map k v -> Map (Either k ()) (Map k v)
99partitionMapBySetLT :: forall k v. Ord k => Set k -> Map k v -> Map (Either k ()) (Map k v)
98partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0
100partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0
99 where go :: [k] -> Map k v -> [(Either k (), Map k v)]
101 where go :: [k] -> Map k v -> [(Either k (), Map k v)]
@@ -106,8 +108,7 @@ partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0
106 then go t geq
108 then go t geq
107 else (Left h, lt) : go t geq
109 else (Left h, lt) : go t geq
108
110
109−newtype ChildResult t k a = ChildResult { unChildResult :: (a, Event t (Map k (Maybe (ChildResult t k a)))) }
111+{-# INLINABLE text #-}
110−
111text :: DomBuilder t m => Text -> m ()
112text :: DomBuilder t m => Text -> m ()
112text t = void $ textNode $ def & textNodeConfig_initialContents .~ t
113text t = void $ textNode $ def & textNodeConfig_initialContents .~ t
113
114
@@ -121,9 +122,23 @@ dynText t = do
121 ]
122 ]
122 notReadyUntil postBuild
123 notReadyUntil postBuild
123
124
125+comment :: DomBuilder t m => Text -> m ()
126+comment t = void $ commentNode $ def & commentNodeConfig_initialContents .~ t
127+
128+{-# INLINABLE dynComment #-}
129+dynComment :: forall t m. (PostBuild t m, DomBuilder t m) => Dynamic t Text -> m ()
130+dynComment t = do
131+ postBuild <- getPostBuild
132+ void $ commentNode $ (def :: CommentNodeConfig t) & commentNodeConfig_setContents .~ leftmost
133+ [ updated t
134+ , tag (current t) postBuild
135+ ]
136+ notReadyUntil postBuild
137+
124display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
138display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
125display = dynText . fmap (T.pack . show)
139display = dynText . fmap (T.pack . show)
126
140
141+{-# DEPRECATED button "Use 'elAttr'' in combination with 'domEvent' instead" #-}
127button :: DomBuilder t m => Text -> m (Event t ())
142button :: DomBuilder t m => Text -> m (Event t ())
128button t = do
143button t = do
129 (e, _) <- element "button" def $ text t
144 (e, _) <- element "button" def $ text t
@@ -131,63 +146,73 @@ button t = do
131
146
132--TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'?
147--TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'?
133-- | Given a Dynamic of widget-creating actions, create a widget that is recreated whenever the Dynamic updates.
148-- | Given a Dynamic of widget-creating actions, create a widget that is recreated whenever the Dynamic updates.
134−-- The returned Event of widget results occurs when the Dynamic does.
149+-- The returned Event occurs whenever the child widget is updated, which is
150+-- at post-build in addition to the times at which the input Dynamic is
151+-- updated, and its value is the result of running the widget.
135−-- Note: Often, the type 'a' is an Event, in which case the return value is an Event-of-Events that would typically be flattened (via 'switchPromptly').
152+-- Note: Often, the type @a@ is an 'Event', in which case the return value is an Event-of-Events that would typically be flattened (via 'switchHold').
136−dyn :: (DomBuilder t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
153+dyn :: (Adjustable t m, NotReady t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
137dyn = networkView
154dyn = networkView
138
155
139-- | Like 'dyn' but discards result.
156-- | Like 'dyn' but discards result.
140−dyn_ :: (DomBuilder t m, PostBuild t m) => Dynamic t (m a) -> m ()
157+dyn_ :: (Adjustable t m, NotReady t m, PostBuild t m) => Dynamic t (m a) -> m ()
141dyn_ = void . dyn
158dyn_ = void . dyn
142
159
143-- | Given an initial widget and an Event of widget-creating actions, create a widget that is recreated whenever the Event fires.
160-- | Given an initial widget and an Event of widget-creating actions, create a widget that is recreated whenever the Event fires.
144-- The returned Dynamic of widget results occurs when the Event does.
161-- The returned Dynamic of widget results occurs when the Event does.
145−-- Note: Often, the type 'a' is an Event, in which case the return value is a Dynamic-of-Events that would typically be flattened.
162+-- Note: Often, the type 'a' is an Event, in which case the return value is a Dynamic-of-Events that would typically be flattened (via 'switchDyn').
146−widgetHold :: (DomBuilder t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a)
163+widgetHold :: (Adjustable t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a)
147widgetHold = networkHold
164widgetHold = networkHold
148
165
149-- | Like 'widgetHold' but discards result.
166-- | Like 'widgetHold' but discards result.
150−widgetHold_ :: (DomBuilder t m, MonadHold t m) => m a -> Event t (m a) -> m ()
167+widgetHold_ :: (Adjustable t m, MonadHold t m) => m a -> Event t (m a) -> m ()
151widgetHold_ z = void . widgetHold z
168widgetHold_ z = void . widgetHold z
152
169
153-- | Create a DOM element
170-- | Create a DOM element
171+--
154−-- > el "div" (text "Hello World")
172+-- >>> el "div" (text "Hello World")
155-- <div>Hello World</div>
173-- <div>Hello World</div>
156{-# INLINABLE el #-}
174{-# INLINABLE el #-}
157el :: forall t m a. DomBuilder t m => Text -> m a -> m a
175el :: forall t m a. DomBuilder t m => Text -> m a -> m a
158el elementTag child = snd <$> el' elementTag child
176el elementTag child = snd <$> el' elementTag child
159
177
160-- | Create a DOM element with attributes
178-- | Create a DOM element with attributes
179+--
161−-- > elAttr "a" ("href" =: "http://google.com") (text "Google!")
180+-- >>> elAttr "a" ("href" =: "https://reflex-frp.org") (text "Reflex-FRP!")
162−-- <a href="http://google.com">Google!</a>
181+-- <a href="https://reflex-frp.org">Reflex-FRP!</a>
163{-# INLINABLE elAttr #-}
182{-# INLINABLE elAttr #-}
164elAttr :: forall t m a. DomBuilder t m => Text -> Map Text Text -> m a -> m a
183elAttr :: forall t m a. DomBuilder t m => Text -> Map Text Text -> m a -> m a
165elAttr elementTag attrs child = snd <$> elAttr' elementTag attrs child
184elAttr elementTag attrs child = snd <$> elAttr' elementTag attrs child
166
185
167-- | Create a DOM element with classes
186-- | Create a DOM element with classes
187+--
168−-- > elClass "div" "row" (return ())
188+-- >>> elClass "div" "row" (return ())
169-- <div class="row"></div>
189-- <div class="row"></div>
170{-# INLINABLE elClass #-}
190{-# INLINABLE elClass #-}
171elClass :: forall t m a. DomBuilder t m => Text -> Text -> m a -> m a
191elClass :: forall t m a. DomBuilder t m => Text -> Text -> m a -> m a
172elClass elementTag c child = snd <$> elClass' elementTag c child
192elClass elementTag c child = snd <$> elClass' elementTag c child
173
193
174-- | Create a DOM element with Dynamic Attributes
194-- | Create a DOM element with Dynamic Attributes
195+--
175−-- > elClass "div" (constDyn ("class" =: "row")) (return ())
196+-- >>> elClass "div" (constDyn ("class" =: "row")) (return ())
176-- <div class="row"></div>
197-- <div class="row"></div>
177{-# INLINABLE elDynAttr #-}
198{-# INLINABLE elDynAttr #-}
178elDynAttr :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t (Map Text Text) -> m a -> m a
199elDynAttr :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t (Map Text Text) -> m a -> m a
179elDynAttr elementTag attrs child = snd <$> elDynAttr' elementTag attrs child
200elDynAttr elementTag attrs child = snd <$> elDynAttr' elementTag attrs child
180
201
181-- | Create a DOM element with a Dynamic Class
202-- | Create a DOM element with a Dynamic Class
203+--
182−-- > elDynClass "div" (constDyn "row") (return ())
204+-- >>> elDynClass "div" (constDyn "row") (return ())
183-- <div class="row"></div>
205-- <div class="row"></div>
184{-# INLINABLE elDynClass #-}
206{-# INLINABLE elDynClass #-}
185elDynClass :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t Text -> m a -> m a
207elDynClass :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t Text -> m a -> m a
186elDynClass elementTag c child = snd <$> elDynClass' elementTag c child
208elDynClass elementTag c child = snd <$> elDynClass' elementTag c child
187
209
188-- | Create a DOM element and return the element
210-- | Create a DOM element and return the element
211+--
212+-- @
189−-- > do (e, _) <- el' "div" (text "Click")
213+-- do (e, _) <- el' "div" (text "Click")
190−-- > return $ domEvent Click e
214+-- return $ domEvent Click e
215+-- @
191{-# INLINABLE el' #-}
216{-# INLINABLE el' #-}
192el' :: forall t m a. DomBuilder t m => Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
217el' :: forall t m a. DomBuilder t m => Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
193el' elementTag = element elementTag def
218el' elementTag = element elementTag def
@@ -247,43 +272,25 @@ dynamicAttributesToModifyAttributesWithInitial attrs0 d = do
247 return $ if Map.null p then Nothing else Just p
272 return $ if Map.null p then Nothing else Just p
248 return modificationsNeeded
273 return modificationsNeeded
249
274
250−--------------------------------------------------------------------------------
275+{-# DEPRECATED Link "Will be removed when 'linkClass' and 'link' are removed. Follow those functions' deprecation instructions." #-}
251−-- Copied and pasted from Reflex.Widget.Class
252−--------------------------------------------------------------------------------
253−
254−{-
255−schedulePostBuild x = performEvent_ . (x <$) =<< getPostBuild
256−
257−elDynHtml' :: DomBuilder t m => Text -> Dynamic t Text -> m (El t)
258−elDynHtml' elementTag html = do
259− e <- buildEmptyElement elementTag (Map.empty :: Map Text Text)
260− schedulePostBuild $ setInnerHTML e . Just =<< sample (current html)
261− performEvent_ $ fmap (setInnerHTML e . Just) $ updated html
262− wrapElement defaultDomEventHandler e
263−
264−elDynHtmlAttr' :: DomBuilder t m => Text -> Map Text Text -> Dynamic t Text -> m (El t)
265−elDynHtmlAttr' elementTag attrs html = do
266− e <- buildEmptyElement elementTag attrs
267− schedulePostBuild $ setInnerHTML e . Just =<< sample (current html)
268− performEvent_ $ fmap (setInnerHTML e . Just) $ updated html
269− wrapElement defaultDomEventHandler e
270−-}
271−
272newtype Link t
276newtype Link t
273 = Link { _link_clicked :: Event t ()
277 = Link { _link_clicked :: Event t ()
274 }
278 }
275
279
280+{-# DEPRECATED linkClass "Use 'elAttr'' in combination with 'domEvent' for just clicks. Use 'routeLink' for Obelisk navigation" #-}
276linkClass :: DomBuilder t m => Text -> Text -> m (Link t)
281linkClass :: DomBuilder t m => Text -> Text -> m (Link t)
277linkClass s c = do
282linkClass s c = do
278 (l,_) <- elAttr' "a" ("class" =: c) $ text s
283 (l,_) <- elAttr' "a" ("class" =: c) $ text s
279 return $ Link $ domEvent Click l
284 return $ Link $ domEvent Click l
280
285
286+{-# DEPRECATED link "Use 'elAttr'' in combination with 'domEvent' for just clicks. Use 'routeLink' for Obelisk navigation" #-}
281link :: DomBuilder t m => Text -> m (Link t)
287link :: DomBuilder t m => Text -> m (Link t)
282link s = linkClass s ""
288link s = linkClass s ""
283
289
284divClass :: forall t m a. DomBuilder t m => Text -> m a -> m a
290divClass :: forall t m a. DomBuilder t m => Text -> m a -> m a
285divClass = elClass "div"
291divClass = elClass "div"
286
292
293+{-# DEPRECATED dtdd "Use an application specific widget generating function" #-}
287dtdd :: forall t m a. DomBuilder t m => Text -> m a -> m a
294dtdd :: forall t m a. DomBuilder t m => Text -> m a -> m a
288dtdd h w = do
295dtdd h w = do
289 el "dt" $ text h
296 el "dt" $ text h
@@ -292,7 +299,9 @@ dtdd h w = do
292blank :: forall m. Monad m => m ()
299blank :: forall m. Monad m => m ()
293blank = return ()
300blank = return ()
294
301
302+-- TODO: Move to an example project.
295-- | A widget to display a table with static columns and dynamic rows.
303-- | A widget to display a table with static columns and dynamic rows.
304+{-# DEPRECATED tableDynAttr "Use an application specific widget generating function" #-}
296tableDynAttr :: forall t m r k v. (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m)
305tableDynAttr :: forall t m r k v. (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m)
297 => Text -- ^ Class applied to <table> element
306 => Text -- ^ Class applied to <table> element
298 -> [(Text, k -> Dynamic t r -> m v)] -- ^ Columns of (header, row key -> row value -> child widget)
307 -> [(Text, k -> Dynamic t r -> m v)] -- ^ Columns of (header, row key -> row value -> child widget)
@@ -308,9 +317,11 @@ tableDynAttr klass cols dRows rowAttrs = elAttr "div" (Map.singleton "style" "zo
308 dAttrs <- rowAttrs k
317 dAttrs <- rowAttrs k
309 elDynAttr' "tr" dAttrs $ mapM (\x -> el "td" $ snd x k r) cols)
318 elDynAttr' "tr" dAttrs $ mapM (\x -> el "td" $ snd x k r) cols)
310
319
320+-- TODO: Move to an example project.
311-- | A widget to construct a tabbed view that shows only one of its child widgets at a time.
321-- | A widget to construct a tabbed view that shows only one of its child widgets at a time.
312-- Creates a header bar containing a <ul> with one <li> per child; clicking a <li> displays
322-- Creates a header bar containing a <ul> with one <li> per child; clicking a <li> displays
313-- the corresponding child and hides all others.
323-- the corresponding child and hides all others.
324+{-# DEPRECATED tabDisplay "Use an application specific widget generating function" #-}
314tabDisplay :: forall t m k. (MonadFix m, DomBuilder t m, MonadHold t m, PostBuild t m, Ord k)
325tabDisplay :: forall t m k. (MonadFix m, DomBuilder t m, MonadHold t m, PostBuild t m, Ord k)
315 => Text -- ^ Class applied to <ul> element
326 => Text -- ^ Class applied to <ul> element
316 -> Text -- ^ Class applied to currently active <li> element
327 -> Text -- ^ Class applied to currently active <li> element
reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+8−48
@@ -54,6 +54,7 @@ import qualified Text.Read as T
54import qualified GHCJS.DOM.Event as Event
54import qualified GHCJS.DOM.Event as Event
55import qualified GHCJS.DOM.HTMLInputElement as Input
55import qualified GHCJS.DOM.HTMLInputElement as Input
56
56
57+{-# DEPRECATED TextInput, _textInput_element, TextInputConfig, textInput "Use 'inputElement' directly" #-}
57data TextInput t
58data TextInput t
58 = TextInput { _textInput_value :: Dynamic t Text
59 = TextInput { _textInput_value :: Dynamic t Text
59 , _textInput_input :: Event t Text
60 , _textInput_input :: Event t Text
@@ -161,6 +162,7 @@ rangeInput (RangeInputConfig initial eSetValue dAttrs) = do
161 , _rangeInput_element = _inputElement_raw i
162 , _rangeInput_element = _inputElement_raw i
162 }
163 }
163
164
165+{-# DEPRECATED TextAreaConfig, TextArea, textArea "Use 'textAreaElement' directly" #-}
164data TextAreaConfig t
166data TextAreaConfig t
165 = TextAreaConfig { _textAreaConfig_initialValue :: Text
167 = TextAreaConfig { _textAreaConfig_initialValue :: Text
166 , _textAreaConfig_setValue :: Event t Text
168 , _textAreaConfig_setValue :: Event t Text
@@ -198,6 +200,7 @@ textArea (TextAreaConfig initial eSet attrs) = do
198 , _textArea_element = _textAreaElement_raw i
200 , _textArea_element = _textAreaElement_raw i
199 }
201 }
200
202
203+{-# DEPRECATED CheckboxConfig, Checkbox, checkbox, checkboxView, CheckboxViewEventResultType, regularToCheckboxViewEventType, CheckboxViewEventResult "Use 'inputElement' directly" #-}
201data CheckboxConfig t
204data CheckboxConfig t
202 = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
205 = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
203 , _checkboxConfig_attributes :: Dynamic t (Map Text Text)
206 , _checkboxConfig_attributes :: Dynamic t (Map Text Text)
@@ -287,7 +290,7 @@ regularToCheckboxViewEventType en r = case en of
287
290
288newtype CheckboxViewEventResult en = CheckboxViewEventResult { unCheckboxViewEventResult :: CheckboxViewEventResultType en }
291newtype CheckboxViewEventResult en = CheckboxViewEventResult { unCheckboxViewEventResult :: CheckboxViewEventResultType en }
289
292
290−--TODO
293+-- | Create a view only checkbox
291{-# INLINABLE checkboxView #-}
294{-# INLINABLE checkboxView #-}
292checkboxView :: forall t m. (DomBuilder t m, DomBuilderSpace m ~ GhcjsDomSpace, PostBuild t m, MonadHold t m) => Dynamic t (Map Text Text) -> Dynamic t Bool -> m (Event t Bool)
295checkboxView :: forall t m. (DomBuilder t m, DomBuilderSpace m ~ GhcjsDomSpace, PostBuild t m, MonadHold t m) => Dynamic t (Map Text Text) -> Dynamic t Bool -> m (Event t Bool)
293checkboxView dAttrs dValue = do
296checkboxView dAttrs dValue = do
@@ -321,6 +324,7 @@ checkboxView dAttrs dValue = do
321 i <- inputElement inputElementConfig
324 i <- inputElement inputElementConfig
322 return $ unCheckboxViewEventResult <$> select (_element_events $ _inputElement_element i) (WrapArg Click)
325 return $ unCheckboxViewEventResult <$> select (_element_events $ _inputElement_element i) (WrapArg Click)
323
326
327+{-# DEPRECATED FileInput, FileInputConfig, fileInput "Use 'inputElement' directly" #-}
324data FileInput d t
328data FileInput d t
325 = FileInput { _fileInput_value :: Dynamic t [File]
329 = FileInput { _fileInput_value :: Dynamic t [File]
326 , _fileInput_element :: RawInputElement d
330 , _fileInput_element :: RawInputElement d
@@ -346,16 +350,10 @@ fileInput config = do
346 & modifyAttributes .~ fmap mapKeysToAttributeName modifyAttrs
350 & modifyAttributes .~ fmap mapKeysToAttributeName modifyAttrs
347 & elementConfig_eventSpec . ghcjsEventSpec_filters .~ filters
351 & elementConfig_eventSpec . ghcjsEventSpec_filters .~ filters
348 cfg = (def :: InputElementConfig EventResult t (DomBuilderSpace m)) & inputElementConfig_elementConfig .~ elCfg
352 cfg = (def :: InputElementConfig EventResult t (DomBuilderSpace m)) & inputElementConfig_elementConfig .~ elCfg
349− eRaw <- inputElement cfg
353+ input <- inputElement cfg
350− let e = _inputElement_raw eRaw
351− eChange <- wrapDomEvent e (`on` Events.change) $ do
352− files <- Input.getFilesUnchecked e
353− len <- FileList.getLength files
354− mapM (fmap (fromMaybe (error "fileInput: fileList.item returned null")) . FileList.item files) [0 .. len-1]
355− dValue <- holdDyn [] eChange
356 return $ FileInput
354 return $ FileInput
357− { _fileInput_value = dValue
355+ { _fileInput_value = _inputElement_files input
358− , _fileInput_element = e
356+ , _fileInput_element = _inputElement_raw input
359 }
357 }
360
358
361data Dropdown t k
359data Dropdown t k
@@ -687,41 +685,3 @@ instance HasValue (Dropdown t k) where
687instance HasValue (Checkbox t) where
685instance HasValue (Checkbox t) where
688 type Value (Checkbox t) = Dynamic t Bool
686 type Value (Checkbox t) = Dynamic t Bool
689 value = _checkbox_value
687 value = _checkbox_value
690−
691−{-
692−type family Controller sm t a where
693− Controller Edit t a = (a, Event t a) -- Initial value and setter
694− Controller View t a = Dynamic t a -- Value (always)
695−
696−type family Output sm t a where
697− Output Edit t a = Dynamic t a -- Value (always)
698− Output View t a = Event t a -- Requested changes
699−
700−data CheckboxConfig sm t
701− = CheckboxConfig { _checkbox_input :: Controller sm t Bool
702− , _checkbox_attributes :: Attributes
703− }
704−
705−instance Reflex t => Default (CheckboxConfig Edit t) where
706− def = CheckboxConfig (False, never) mempty
707−
708−data Checkbox sm t
709− = Checkbox { _checkbox_output :: Output sm t Bool
710− }
711−
712−data StateMode = Edit | View
713−
714−--TODO: There must be a more generic way to get this witness and allow us to case on the type-level StateMode
715−data StateModeWitness (sm :: StateMode) where
716− EditWitness :: StateModeWitness Edit
717− ViewWitness :: StateModeWitness View
718−
719−class HasStateModeWitness (sm :: StateMode) where
720− stateModeWitness :: StateModeWitness sm
721−
722−instance HasStateModeWitness Edit where
723− stateModeWitness = EditWitness
724−
725−instance HasStateModeWitness View where
726− stateModeWitness = ViewWitness
727−-}
reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs+3−3
@@ -64,7 +64,7 @@ virtualListWithSelection heightPx rowPx maxIndex i0 setI listTag listAttrs rowTa
64 let indexAndLength = fmap snd window
64 let indexAndLength = fmap snd window
65 return (indexAndLength, sel)
65 return (indexAndLength, sel)
66 where
66 where
67− toStyleAttr m = "style" =: Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m
67+ toStyleAttr m = "style" =: Map.foldrWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m
68 toViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
68 toViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
69 "left" =: "0" <> "right" =: "0" <> "height" =: (T.pack (show h) <> "px")
69 "left" =: "0" <> "right" =: "0" <> "height" =: (T.pack (show h) <> "px")
70 toContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (T.pack (show h) <> "px")
70 toContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (T.pack (show h) <> "px")
@@ -108,7 +108,7 @@ virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBu
108 uniqWindow <- holdUniqDyn window
108 uniqWindow <- holdUniqDyn window
109 return (uniqWindow, result)
109 return (uniqWindow, result)
110 where
110 where
111− toStyleAttr m = "style" =: Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m
111+ toStyleAttr m = "style" =: Map.foldrWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m
112 mkViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
112 mkViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
113 "left" =: "0" <> "right" =: "0" <> "height" =: (T.pack (show h) <> "px")
113 "left" =: "0" <> "right" =: "0" <> "height" =: (T.pack (show h) <> "px")
114 mkContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (T.pack (show h) <> "px")
114 mkContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (T.pack (show h) <> "px")
@@ -142,7 +142,7 @@ virtualListBuffered buffer heightPx rowPx maxIndex i0 setI keyToIndex items0 ite
142 (win, m) <- virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder
142 (win, m) <- virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder
143 pb <- getPostBuild
143 pb <- getPostBuild
144 let extendWin o l = (max 0 (o - l * (buffer-1) `div` 2), l * buffer)
144 let extendWin o l = (max 0 (o - l * (buffer-1) `div` 2), l * buffer)
145− rec let winHitEdge = fmapMaybe id $ attachWith (\(oldOffset, oldLimit) (winOffset, winLimit) ->
145+ rec let winHitEdge = attachWithMaybe (\(oldOffset, oldLimit) (winOffset, winLimit) ->
146 if winOffset > oldOffset && winOffset + winLimit < oldOffset + oldLimit
146 if winOffset > oldOffset && winOffset + winLimit < oldOffset + oldLimit
147 then Nothing
147 then Nothing
148 else Just (extendWin winOffset winLimit)) (current winBuffered) (updated win)
148 else Just (extendWin winOffset winLimit)) (current winBuffered) (updated win)
reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs+5−1
@@ -32,7 +32,11 @@ import GHCJS.DOM.HTMLElement (getOffsetWidth, getOffsetHeight)
32import qualified GHCJS.DOM.Types as DOM
32import qualified GHCJS.DOM.Types as DOM
33
33
34-- | A widget that wraps the given widget in a div and fires an event when resized.
34-- | A widget that wraps the given widget in a div and fires an event when resized.
35−-- Adapted from github.com/marcj/css-element-queries
35+-- Adapted from @github.com\/marcj\/css-element-queries@
36+--
37+-- This function can cause strange scrollbars to appear in some circumstances.
38+-- These can be hidden with pseudo selectors, for example, in webkit browsers:
39+-- .wrapper *::-webkit-scrollbar { width: 0px; background: transparent; }
36resizeDetector :: (MonadJSM m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, DomBuilderSpace m ~ GhcjsDomSpace, MonadJSM (Performable m), MonadFix m) => m a -> m (Event t (), a)
40resizeDetector :: (MonadJSM m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, DomBuilderSpace m ~ GhcjsDomSpace, MonadJSM (Performable m), MonadFix m) => m a -> m (Event t (), a)
37resizeDetector = resizeDetectorWithStyle ""
41resizeDetector = resizeDetectorWithStyle ""
38
42
reflex-dom-core/src/Reflex/Dom/Xhr.hs+2−2
@@ -215,7 +215,7 @@ _xhrResponse_body :: XhrResponse -> Maybe Text
215_xhrResponse_body = _xhrResponse_responseText
215_xhrResponse_body = _xhrResponse_responseText
216
216
217{-# DEPRECATED xhrResponse_body "Use xhrResponse_response or xhrResponse_responseText instead." #-}
217{-# DEPRECATED xhrResponse_body "Use xhrResponse_response or xhrResponse_responseText instead." #-}
218−xhrResponse_body :: Lens XhrResponse XhrResponse (Maybe Text) (Maybe Text)
218+xhrResponse_body :: Lens' XhrResponse (Maybe Text)
219xhrResponse_body = lens _xhrResponse_responseText (\r t -> r { _xhrResponse_responseText = t })
219xhrResponse_body = lens _xhrResponse_responseText (\r t -> r { _xhrResponse_responseText = t })
220
220
221instance a ~ () => Default (XhrRequestConfig a) where
221instance a ~ () => Default (XhrRequestConfig a) where
@@ -378,7 +378,7 @@ decodeText = decode . BL.fromStrict . encodeUtf8
378
378
379-- | Convenience function to decode JSON-encoded responses.
379-- | Convenience function to decode JSON-encoded responses.
380decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
380decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
381−decodeXhrResponse = join . fmap decodeText . _xhrResponse_responseText
381+decodeXhrResponse = decodeText <=< _xhrResponse_responseText
382
382
383#ifdef USE_TEMPLATE_HASKELL
383#ifdef USE_TEMPLATE_HASKELL
384concat <$> mapM makeLenses
384concat <$> mapM makeLenses
reflex-dom-core/src/Reflex/Dom/Xhr/Foreign.hs−1
@@ -1,7 +1,6 @@
1{-# LANGUAGE FlexibleInstances #-}
1{-# LANGUAGE FlexibleInstances #-}
2{-# LANGUAGE JavaScriptFFI #-}
2{-# LANGUAGE JavaScriptFFI #-}
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE TypeSynonymInstances #-}
5
4
6module Reflex.Dom.Xhr.Foreign (
5module Reflex.Dom.Xhr.Foreign (
7 XMLHttpRequest
6 XMLHttpRequest
reflex-dom-core/src/Reflex/Dom/Xhr/FormData.hsnew file+47
@@ -0,0 +1,47 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+module Reflex.Dom.Xhr.FormData
4+ ( postForms
5+ , FormValue (..)
6+ , fileToFormValue
7+ )
8+ where
9+
10+import Control.Lens
11+import Data.Default
12+import Data.Map (Map)
13+import Data.Text (Text)
14+import Data.Traversable
15+import qualified GHCJS.DOM.FormData as FD
16+import Foreign.JavaScript.TH
17+import GHCJS.DOM.File (getName)
18+import GHCJS.DOM.Types (File, IsBlob)
19+import Language.Javascript.JSaddle.Monad (MonadJSM, liftJSM)
20+import Reflex
21+import Reflex.Dom.Xhr
22+
23+-- | A FormData value may be a blob/file or a string. The file can optionally be provided with filename.
24+data FormValue blob = FormValue_Text Text
25+ | FormValue_File blob (Maybe Text) -- maybe filename
26+
27+-- | Performs a POST request with the provided FormData payload
28+postForms
29+ :: ( IsBlob blob, HasJSContext (Performable m), MonadJSM (Performable m)
30+ , PerformEvent t m, TriggerEvent t m
31+ , Traversable f)
32+ => Text -- ^ The target url
33+ -> Event t (f (Map Text (FormValue blob))) -- ^ Maps of text keys and values that will be sent as "FormData"
34+ -> m (Event t (f XhrResponse))
35+postForms url payload = do
36+ performMkRequestsAsync $ ffor payload $ \fs -> for fs $ \u -> liftJSM $ do
37+ fd <- FD.newFormData Nothing
38+ iforM_ u $ \k v -> case v of
39+ FormValue_Text t -> FD.append fd k t
40+ FormValue_File b fn -> FD.appendBlob fd k b fn
41+ return $ xhrRequest "POST" url $ def & xhrRequestConfig_sendData .~ fd
42+
43+-- | Converts a File (e.g., the output of a 'FileInput') into a 'FormValue'. The filename will be included if it is available.
44+fileToFormValue :: MonadJSM m => File -> m (FormValue File)
45+fileToFormValue f = do
46+ fn <- getName f
47+ return $ FormValue_File f $ Just fn
reflex-dom-core/test/gc.hs+22−13
@@ -8,17 +8,26 @@ import Control.Exception
8import Control.Monad
8import Control.Monad
9import Control.Monad.IO.Class
9import Control.Monad.IO.Class
10import Data.Int
10import Data.Int
11−import GHC.Stats
11+import Data.Text as T
12import Language.Javascript.JSaddle.Warp
12import Language.Javascript.JSaddle.Warp
13import Reflex.Dom.Core
13import Reflex.Dom.Core
14import Reflex.Time
14import Reflex.Time
15import System.Exit
15import System.Exit
16−import System.IO.Temp
17−import System.Linux.Namespaces
18import System.Mem
16import System.Mem
19−import System.Posix
20import System.Process
17import System.Process
21
18
19+import Test.Util.ChromeFlags
20+import Test.Util.UnshareNetwork
21+
22+#if MIN_VERSION_base(4,11,0)
23+import GHC.Stats (getRTSStatsEnabled, getRTSStats, RTSStats(..), gcdetails_live_bytes, gc)
24+currentBytesUsed :: RTSStats -> Int64
25+currentBytesUsed = fromIntegral . gcdetails_live_bytes . gc
26+#else
27+import GHC.Stats (getGCStats, GCStats(..))
28+getRTSStats = getGCStats
29+#endif
30+
22-- In initial testing, the minimum live bytes count was 233128 and maximum was
31-- In initial testing, the minimum live bytes count was 233128 and maximum was
23-- 363712. Going over the maximum means the test has actually failed - we
32-- 363712. Going over the maximum means the test has actually failed - we
24-- probably have a memory leak; going under the minimum doesn't indicate a
33-- probably have a memory leak; going under the minimum doesn't indicate a
@@ -38,14 +47,14 @@ failureLimit = 0
38
47
39main :: IO ()
48main :: IO ()
40main = do
49main = do
41− uid <- getEffectiveUserID
42− handle (\(_ :: IOError) -> return ()) $ do -- If we run into an exception with sandboxing, just don't bother
50+ handle (\(_ :: IOError) -> return ()) $ unshareNetork -- If we run into an exception with sandboxing, just don't bother
43− unshare [User, Network]
51+ withSandboxedChromeFlags True $ \chromeFlags -> do
44− writeUserMappings Nothing [UserMapping 0 uid 1]
45− callCommand "ip link set lo up ; ip addr"
46− mainThread <- myThreadId
52+ mainThread <- myThreadId
47− withSystemTempDirectory "reflex-dom-core_test_gc" $ \tmp -> do
53+ browserProcess <- spawnCommand $ mconcat
48− browserProcess <- spawnCommand $ "echo 'Starting Chromium' ; chromium --headless --disable-gpu --no-sandbox --remote-debugging-port=9222 --user-data-dir=" ++ tmp ++ " http://localhost:3911 ; echo 'Chromium exited'"
54+ [ "echo 'Starting Chromium' ; chromium "
55+ , unpack $ T.unwords chromeFlags
56+ , " http://localhost:3911 ; echo 'Chromium exited'"
57+ ]
49 let finishTest result = do
58 let finishTest result = do
50 interruptProcessGroupOf browserProcess
59 interruptProcessGroupOf browserProcess
51 throwTo mainThread result
60 throwTo mainThread result
@@ -63,7 +72,7 @@ main = do
63 let f (!failures, !n) = liftIO $ if n < 3000
72 let f (!failures, !n) = liftIO $ if n < 3000
64 then do performMajorGC
73 then do performMajorGC
65 threadDelay 5000 -- Wait a bit to allow requestAnimationFrame to call its callback sometimes; this value was experimentally determined
74 threadDelay 5000 -- Wait a bit to allow requestAnimationFrame to call its callback sometimes; this value was experimentally determined
66− gcStats <- getGCStats
75+ gcStats <- getRTSStats
67 print $ currentBytesUsed gcStats
76 print $ currentBytesUsed gcStats
68 when (currentBytesUsed gcStats < minBytesAllowed) $ do
77 when (currentBytesUsed gcStats < minBytesAllowed) $ do
69 putStrLn "FAILED: currentBytesUsed < minBytesAllowed"
78 putStrLn "FAILED: currentBytesUsed < minBytesAllowed"
reflex-dom-core/test/hlint.hs+2
@@ -16,9 +16,11 @@ main = do
16 , "--ignore=Use unless"
16 , "--ignore=Use unless"
17 , "--ignore=Use if"
17 , "--ignore=Use if"
18 , "--ignore=Use fewer imports"
18 , "--ignore=Use fewer imports"
19+ , "--ignore=Use list comprehension"
19 , "--ignore=Unnecessary hiding" -- Interferes with cross-version compatibility
20 , "--ignore=Unnecessary hiding" -- Interferes with cross-version compatibility
20 , "--ignore=Use <$>"
21 , "--ignore=Use <$>"
21 , "--ignore=Reduce duplication" --TODO: Re-enable this test
22 , "--ignore=Reduce duplication" --TODO: Re-enable this test
23+ , "--ignore=Use list comprehension"
22 , "--cpp-define=USE_TEMPLATE_HASKELL"
24 , "--cpp-define=USE_TEMPLATE_HASKELL"
23 ]
25 ]
24 if null ideas then exitSuccess else exitFailure
26 if null ideas then exitSuccess else exitFailure
reflex-dom-core/test/hydration.hsnew file+1416
@@ -0,0 +1,1416 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE StandaloneDeriving #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE UndecidableInstances #-}
6+{-# LANGUAGE ConstraintKinds #-}
7+{-# LANGUAGE CPP #-}
8+{-# LANGUAGE FlexibleContexts #-}
9+{-# LANGUAGE FlexibleInstances #-}
10+{-# LANGUAGE GADTs #-}
11+{-# LANGUAGE LambdaCase #-}
12+{-# LANGUAGE MultiParamTypeClasses #-}
13+{-# LANGUAGE OverloadedStrings #-}
14+{-# LANGUAGE PolyKinds #-}
15+{-# LANGUAGE RankNTypes #-}
16+{-# LANGUAGE RecursiveDo #-}
17+{-# LANGUAGE ScopedTypeVariables #-}
18+{-# LANGUAGE TemplateHaskell #-}
19+{-# LANGUAGE TupleSections #-}
20+{-# LANGUAGE TypeApplications #-}
21+{-# LANGUAGE TypeFamilies #-}
22+
23+import Prelude hiding (fail)
24+import Control.Concurrent
25+import Control.Monad hiding (fail)
26+import Control.Monad.Catch
27+import Control.Monad.Fail
28+import Control.Monad.Fix
29+import Control.Monad.IO.Class
30+import Control.Monad.Ref
31+import Data.Constraint.Extras
32+import Data.Constraint.Extras.TH
33+import Data.Dependent.Map (DMap)
34+import Data.Dependent.Sum (DSum(..), (==>), EqTag(..), ShowTag(..))
35+import Data.Functor.Identity
36+import Data.Functor.Misc
37+import Data.GADT.Compare.TH
38+import Data.GADT.Show.TH
39+import Data.IORef (IORef)
40+import Data.Maybe
41+import Data.Proxy
42+import Data.Text (Text)
43+import Language.Javascript.JSaddle (syncPoint, liftJSM)
44+import Language.Javascript.JSaddle.Warp
45+import Network.HTTP.Types (status200)
46+import Network.Socket
47+import Network.Wai
48+import Network.WebSockets
49+import Reflex.Dom.Core
50+import Reflex.Patch.DMapWithMove
51+import System.Directory
52+import System.Environment
53+import System.IO (stderr)
54+import System.IO.Silently
55+import System.IO.Temp
56+import System.Process
57+import System.Which (staticWhich)
58+import qualified Test.HUnit as HUnit (assertEqual, assertFailure)
59+import qualified Test.Hspec as H
60+import qualified Test.Hspec.Core.Spec as H
61+import Test.Hspec (xit)
62+import Test.Hspec.WebDriver hiding (runWD, click, uploadFile, WD)
63+import qualified Test.Hspec.WebDriver as WD
64+import Test.WebDriver (WD(..))
65+import Test.WebDriver.Exceptions (ServerError(..))
66+
67+import qualified Data.ByteString.Lazy as LBS
68+import qualified Data.Dependent.Map as DMap
69+import qualified Data.IntMap as IntMap
70+import qualified Data.Text as T
71+import qualified Data.Text.Encoding as TE
72+import qualified GHCJS.DOM.File as File
73+import qualified Network.Wai.Handler.Warp as Warp
74+import qualified System.FilePath as FilePath
75+import qualified Test.WebDriver as WD
76+import qualified Test.WebDriver.Capabilities as WD
77+
78+import Test.Util.ChromeFlags
79+import Test.Util.UnshareNetwork
80+
81+chromium :: FilePath
82+chromium = $(staticWhich "chromium")
83+
84+seleniumPort, jsaddlePort :: PortNumber
85+seleniumPort = 8000
86+jsaddlePort = 8001
87+
88+-- TODO Remove orphan
89+instance MonadRef WD where
90+ type Ref WD = Ref IO
91+ newRef = WD . newRef
92+ readRef = WD . readRef
93+ writeRef r = WD . writeRef r
94+
95+assertEqual :: (MonadIO m, Eq a, Show a) => String -> a -> a -> m ()
96+assertEqual a b = liftIO . HUnit.assertEqual a b
97+
98+assertFailure :: MonadIO m => String -> m ()
99+assertFailure = liftIO . HUnit.assertFailure
100+
101+chromeConfig :: Text -> [Text] -> WD.WDConfig
102+chromeConfig fp flags = WD.useBrowser (WD.chrome { WD.chromeBinary = Just $ T.unpack fp, WD.chromeOptions = T.unpack <$> flags }) WD.defaultConfig
103+
104+keyMap :: DMap DKey Identity
105+keyMap = DMap.fromList
106+ [ Key_Int ==> 0
107+ , Key_Char ==> 'A'
108+ ]
109+
110+data DKey a where
111+ Key_Int :: DKey Int
112+ Key_Char :: DKey Char
113+ Key_Bool :: DKey Bool
114+
115+
116+textKey :: DKey a -> Text
117+textKey = \case
118+ Key_Int -> "Key_Int"
119+ Key_Char -> "Key_Char"
120+ Key_Bool -> "Key_Bool"
121+
122+deriveArgDict ''DKey
123+deriveGEq ''DKey
124+deriveGCompare ''DKey
125+deriveGShow ''DKey
126+
127+deriving instance MonadFail WD
128+
129+main :: IO ()
130+main = do
131+ unshareNetork
132+ isHeadless <- (== Nothing) <$> lookupEnv "NO_HEADLESS"
133+ withSandboxedChromeFlags isHeadless $ \chromeFlags -> do
134+ withSeleniumServer $ \selenium -> do
135+ let browserPath = T.strip $ T.pack chromium
136+ when (T.null browserPath) $ fail "No browser found"
137+ withDebugging <- isNothing <$> lookupEnv "NO_DEBUG"
138+ let wdConfig = WD.defaultConfig { WD.wdPort = fromIntegral $ _selenium_portNumber selenium }
139+ chromeCaps' = WD.getCaps $ chromeConfig browserPath chromeFlags
140+ hspec (tests withDebugging wdConfig [chromeCaps'] selenium) `finally` _selenium_stopServer selenium
141+
142+tests :: Bool -> WD.WDConfig -> [Capabilities] -> Selenium -> Spec
143+tests withDebugging wdConfig caps _selenium = do
144+ let putStrLnDebug :: MonadIO m => Text -> m ()
145+ putStrLnDebug m = when withDebugging $ liftIO $ putStrLn $ T.unpack m
146+ session' = sessionWith wdConfig "" . using (map (,"") caps)
147+ runWD m = runWDOptions (WdOptions False) $ do
148+ putStrLnDebug "before"
149+ r <- m
150+ putStrLnDebug "after"
151+ return r
152+ testWidgetStatic :: WD b -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> WD b
153+ testWidgetStatic = testWidgetStaticDebug withDebugging
154+ testWidget :: WD () -> WD b -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> WD b
155+ testWidget = testWidgetDebug withDebugging
156+ testWidget' :: WD a -> (a -> WD b) -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> WD b
157+ testWidget' = testWidgetDebug' withDebugging
158+ describe "text" $ session' $ do
159+ it "works" $ runWD $ do
160+ testWidgetStatic (checkBodyText "hello world") $ do
161+ text "hello world"
162+ it "works with postBuild" $ runWD $ do
163+ testWidgetStatic (checkBodyText "pb") $ do
164+ pb <- getPostBuild
165+ void $ textNode $ TextNodeConfig "" $ Just $ "pb" <$ pb
166+ it "works for adjacent text nodes" $ runWD $ do
167+ testWidgetStatic (checkBodyText "hello world") $ do
168+ text "hello "
169+ text "world"
170+ it "works for empty adjacent text nodes" $ runWD $ do
171+ testWidgetStatic (checkBodyText "hello world") $ do
172+ pb <- getPostBuild
173+ text ""
174+ text ""
175+ _ <- textNode $ TextNodeConfig "" $ Just $ "hello " <$ pb
176+ _ <- textNode $ TextNodeConfig "abc" $ Just $ "" <$ pb
177+ _ <- textNode $ TextNodeConfig "" $ Just $ "world" <$ pb
178+ text ""
179+ it "works when empty text nodes are the only children of an element" $ runWD $ do
180+ testWidgetStatic (checkBodyText "hello world") $ do
181+ el "div" $ do
182+ text ""
183+ text ""
184+ text "hello world"
185+ it "works when an empty text node is the first child before text" $ runWD $ do
186+ testWidgetStatic (checkTextInTag "div" "hello world") $ do
187+ el "div" $ do
188+ text ""
189+ text "hello world"
190+ it "works when an empty text node is the first child before element" $ runWD $ do
191+ testWidgetStatic (checkTextInTag "div" "hello world") $ do
192+ el "div" $ do
193+ text ""
194+ el "span" $ text "hello world"
195+ it "works when an empty text node is the last child" $ runWD $ do
196+ testWidgetStatic (checkTextInTag "div" "hello world") $ do
197+ el "div" $ do
198+ el "span" $ text "hello world"
199+ text ""
200+-- TODO I think these two tests are broken
201+-- it "updates after postBuild" $ runWD $ do
202+-- testWidget (checkBodyText "initial") (checkBodyText "after") $ do
203+-- after <- delay 0 =<< getPostBuild
204+-- void $ textNode $ TextNodeConfig "initial" $ Just $ "after" <$ after
205+-- it "updates immediately after postBuild" $ runWD $ do
206+-- testWidget (checkBodyText "pb") (checkBodyText "after") $ do
207+-- pb <- getPostBuild
208+-- after <- delay 0 pb
209+-- void $ textNode $ TextNodeConfig "initial" $ Just $ leftmost ["pb" <$ pb, "after" <$ after]
210+ it "updates in immediate mode" $ runWD $ do
211+ let checkUpdated = do
212+ checkBodyText "initial"
213+ WD.click =<< findElemWithRetry (WD.ByTag "button")
214+ checkBodyText "after"
215+ testWidget (pure ()) checkUpdated $ prerender_ (pure ()) $ do
216+ click <- button ""
217+ void $ textNode $ TextNodeConfig "initial" $ Just $ "after" <$ click
218+
219+ describe "element" $ session' $ do
220+ it "works with domEvent Click" $ runWD $ do
221+ clickedRef <- liftIO $ newRef False
222+ testWidget' (findElemWithRetry $ WD.ByTag "div") WD.click $ do
223+ (e, _) <- el' "div" $ text "hello world"
224+ performEvent_ $ liftIO (writeRef clickedRef True) <$ domEvent Click e
225+ readRef clickedRef `shouldBeWithRetryM` True
226+ it "works with eventFlags stopPropagation" $ runWD $ do
227+ firstClickedRef <- newRef False
228+ secondClickedRef <- newRef False
229+ let clickBoth = do
230+ findElemWithRetry (WD.ById "first") >>= WD.click
231+ findElemWithRetry (WD.ById "second") >>= WD.click
232+ testWidget (pure ()) clickBoth $ do
233+ (firstDivEl, _) <- el' "div" $ prerender_ (pure ()) $ do
234+ void $ elAttr "span" ("id" =: "first") $ text "hello world"
235+ performEvent_ $ liftIO (writeRef firstClickedRef True) <$ domEvent Click firstDivEl
236+ (secondDivEl, _) <- el' "div" $ prerender_ (pure ()) $ do
237+ let conf :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace
238+ conf = (def :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace)
239+ & initialAttributes .~ "id" =: "second"
240+ & elementConfig_eventSpec .~ addEventSpecFlags (Proxy :: Proxy GhcjsDomSpace) Click (\_ -> stopPropagation) def
241+ void $ element "span" conf $ text "hello world"
242+ performEvent_ $ liftIO (writeRef secondClickedRef True) <$ domEvent Click secondDivEl
243+ firstClicked <- readRef firstClickedRef
244+ secondClicked <- readRef secondClickedRef
245+ assertEqual "Click propagated when it should have stopped" (True, False) (firstClicked, secondClicked)
246+ it "works with eventFlags preventDefault" $ runWD $ do
247+ let click = do
248+ e <- findElemWithRetry $ WD.ByTag "input"
249+ s0 <- WD.isSelected e
250+ WD.click e
251+ s1 <- WD.isSelected e
252+ pure (s0, s1)
253+ clicked <- testWidget (pure ()) click $ prerender_ (pure ()) $ do
254+ let conf :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace
255+ conf = (def :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace)
256+ & elementConfig_eventSpec .~ addEventSpecFlags (Proxy :: Proxy GhcjsDomSpace) Click (\_ -> preventDefault) def
257+ & initialAttributes .~ "type" =: "checkbox"
258+ void $ element "input" conf $ text "hello world"
259+ assertEqual "Click not prevented" (False, False) clicked
260+ it "can add/update/remove attributes" $ runWD $ do
261+ let checkInitialAttrs = do
262+ e <- findElemWithRetry $ WD.ByTag "div"
263+ assertAttr e "const" (Just "const")
264+ assertAttr e "delete" (Just "delete")
265+ assertAttr e "init" (Just "init")
266+ assertAttr e "click" Nothing
267+ pure e
268+ checkModifyAttrs e = do
269+ WD.click e
270+ withRetry $ do
271+ assertAttr e "const" (Just "const")
272+ assertAttr e "delete" Nothing
273+ assertAttr e "init" (Just "click")
274+ assertAttr e "click" (Just "click")
275+ testWidget' checkInitialAttrs checkModifyAttrs $ mdo
276+ let conf = def
277+ & initialAttributes .~ "const" =: "const" <> "delete" =: "delete" <> "init" =: "init"
278+ & modifyAttributes .~ (("delete" =: Nothing <> "init" =: Just "click" <> "click" =: Just "click") <$ click)
279+ (e, ()) <- element "div" conf $ text "hello world"
280+ let click = domEvent Click e
281+ return ()
282+
283+ describe "inputElement" $ do
284+ describe "hydration" $ session' $ do
285+ it "doesn't wipe user input when switching over" $ runWD $ do
286+ inputRef <- newRef ("" :: Text)
287+ testWidget'
288+ (do
289+ e <- findElemWithRetry $ WD.ByTag "input"
290+ WD.sendKeys "hello world" e
291+ pure e)
292+ (\e -> do
293+ WD.attr e "value" `shouldBeWithRetryM` Just "hello world"
294+ WD.click =<< findElemWithRetry (WD.ByTag "button")
295+ readRef inputRef `shouldBeWithRetryM` "hello world"
296+ ) $ do
297+ e <- inputElement def
298+ click <- button "save"
299+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
300+ it "captures user input after switchover" $ runWD $ do
301+ inputRef <- newRef ("" :: Text)
302+ let checkValue = do
303+ WD.sendKeys "hello world" =<< findElemWithRetry (WD.ByTag "input")
304+ WD.click =<< findElemWithRetry (WD.ByTag "button")
305+ readRef inputRef `shouldBeWithRetryM` "hello world"
306+ testWidget (pure ()) checkValue $ do
307+ e <- inputElement def
308+ click <- button "save"
309+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
310+ it "sets focus appropriately" $ runWD $ do
311+ focusRef <- newRef False
312+ let checkValue = do
313+ readRef focusRef `shouldBeWithRetryM` False
314+ e <- findElemWithRetry $ WD.ByTag "input"
315+ WD.click e
316+ readRef focusRef `shouldBeWithRetryM` True
317+ testWidget (pure ()) checkValue $ do
318+ e <- inputElement def
319+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_inputElement_hasFocus e)
320+ it "sets focus when focus occurs before hydration" $ runWD $ do
321+ focusRef <- newRef False
322+ let setup = do
323+ e <- findElemWithRetry $ WD.ByTag "input"
324+ WD.click e
325+ ((== e) <$> WD.activeElem) `shouldBeWithRetryM` True
326+ readRef focusRef `shouldBeWithRetryM` False
327+ check = readRef focusRef `shouldBeWithRetryM` True
328+ testWidget setup check $ do
329+ e <- inputElement def
330+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_inputElement_hasFocus e)
331+ it "sets value appropriately" $ runWD $ do
332+ valueByUIRef <- newRef ("" :: Text)
333+ valueRef <- newRef ("" :: Text)
334+ setValueChan :: Chan Text <- liftIO newChan
335+ let checkValue = do
336+ readRef valueByUIRef `shouldBeWithRetryM` ""
337+ readRef valueRef `shouldBeWithRetryM` ""
338+ e <- findElemWithRetry $ WD.ByTag "input"
339+ WD.sendKeys "hello" e
340+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
341+ readRef valueRef `shouldBeWithRetryM` "hello"
342+ liftIO $ writeChan setValueChan "world"
343+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
344+ readRef valueRef `shouldBeWithRetryM` "world"
345+ testWidget (pure ()) checkValue $ do
346+ update <- triggerEventWithChan setValueChan
347+ e <- inputElement $ def & inputElementConfig_setValue .~ update
348+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _inputElement_input e
349+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
350+ it "sets checked appropriately" $ runWD $ do
351+ checkedByUIRef <- newRef False
352+ checkedRef <- newRef False
353+ setCheckedChan <- liftIO newChan
354+ let checkValue = do
355+ readRef checkedByUIRef `shouldBeWithRetryM` False
356+ readRef checkedRef `shouldBeWithRetryM` False
357+ e <- findElemWithRetry $ WD.ByTag "input"
358+ WD.moveToCenter e
359+ WD.click e
360+ readRef checkedByUIRef `shouldBeWithRetryM` True
361+ readRef checkedRef `shouldBeWithRetryM` True
362+ liftIO $ writeChan setCheckedChan False
363+ readRef checkedByUIRef `shouldBeWithRetryM` True
364+ readRef checkedRef `shouldBeWithRetryM` False
365+ testWidget (pure ()) checkValue $ do
366+ setChecked <- triggerEventWithChan setCheckedChan
367+ e <- inputElement $ def
368+ & initialAttributes .~ "type" =: "checkbox"
369+ & inputElementConfig_setChecked .~ setChecked
370+ performEvent_ $ liftIO . writeRef checkedByUIRef <$> _inputElement_checkedChange e
371+ performEvent_ $ liftIO . writeRef checkedRef <$> updated (_inputElement_checked e)
372+ it "captures file uploads" $ runWD $ do
373+ filesRef :: IORef [Text] <- newRef []
374+ let uploadFile = do
375+ e <- findElemWithRetry $ WD.ByTag "input"
376+ path <- liftIO $ writeSystemTempFile "testFile" "file contents"
377+ WD.sendKeys (T.pack path) e
378+ WD.click =<< findElemWithRetry (WD.ByTag "button")
379+ liftIO $ removeFile path
380+ readRef filesRef `shouldBeWithRetryM` [T.pack $ FilePath.takeFileName path]
381+ testWidget (pure ()) uploadFile $ do
382+ e <- inputElement $ def & initialAttributes .~ "type" =: "file"
383+ click <- button "save"
384+ prerender_ (pure ()) $ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
385+ names <- liftJSM $ traverse File.getName fs
386+ liftIO $ writeRef filesRef names
387+
388+ describe "hydration/immediate" $ session' $ do
389+ it "captures user input after switchover" $ runWD $ do
390+ inputRef :: IORef Text <- newRef ""
391+ let checkValue = do
392+ WD.sendKeys "hello world" =<< findElemWithRetry (WD.ByTag "input")
393+ WD.click =<< findElemWithRetry (WD.ByTag "button")
394+ readRef inputRef `shouldBeWithRetryM` "hello world"
395+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
396+ e <- inputElement def
397+ click <- button "save"
398+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
399+ it "sets focus appropriately" $ runWD $ do
400+ focusRef <- newRef False
401+ let checkValue = do
402+ readRef focusRef `shouldBeWithRetryM` False
403+ e <- findElemWithRetry $ WD.ByTag "input"
404+ WD.click e
405+ readRef focusRef `shouldBeWithRetryM` True
406+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
407+ e <- inputElement def
408+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_inputElement_hasFocus e)
409+ it "sets value appropriately" $ runWD $ do
410+ valueByUIRef :: IORef Text <- newRef ""
411+ valueRef :: IORef Text <- newRef ""
412+ setValueChan :: Chan Text <- liftIO newChan
413+ let checkValue = do
414+ readRef valueByUIRef `shouldBeWithRetryM` ""
415+ readRef valueRef `shouldBeWithRetryM` ""
416+ e <- findElemWithRetry $ WD.ByTag "input"
417+ WD.sendKeys "hello" e
418+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
419+ readRef valueRef `shouldBeWithRetryM` "hello"
420+ liftIO $ writeChan setValueChan "world"
421+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
422+ readRef valueRef `shouldBeWithRetryM` "world"
423+ testWidget (pure ()) checkValue $ do
424+ update <- triggerEventWithChan setValueChan
425+ prerender_ (pure ()) $ do
426+ e <- inputElement $ def & inputElementConfig_setValue .~ update
427+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _inputElement_input e
428+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
429+ it "sets checked appropriately" $ runWD $ do
430+ checkedByUIRef <- newRef False
431+ checkedRef <- newRef False
432+ setCheckedChan <- liftIO newChan
433+ let checkValue = do
434+ readRef checkedByUIRef `shouldBeWithRetryM` False
435+ readRef checkedRef `shouldBeWithRetryM` False
436+ e <- findElemWithRetry $ WD.ByTag "input"
437+ WD.moveToCenter e
438+ WD.click e
439+ readRef checkedByUIRef `shouldBeWithRetryM` True
440+ readRef checkedRef `shouldBeWithRetryM` True
441+ liftIO $ writeChan setCheckedChan False
442+ readRef checkedByUIRef `shouldBeWithRetryM` True
443+ readRef checkedRef `shouldBeWithRetryM` False
444+ testWidget (pure ()) checkValue $ do
445+ setChecked <- triggerEventWithChan setCheckedChan
446+ prerender_ (pure ()) $ do
447+ e <- inputElement $ def
448+ & initialAttributes .~ "type" =: "checkbox"
449+ & inputElementConfig_setChecked .~ setChecked
450+ performEvent_ $ liftIO . writeRef checkedByUIRef <$> _inputElement_checkedChange e
451+ performEvent_ $ liftIO . writeRef checkedRef <$> updated (_inputElement_checked e)
452+ it "captures file uploads" $ runWD $ do
453+ filesRef :: IORef [Text] <- newRef []
454+ let uploadFile = do
455+ e <- findElemWithRetry $ WD.ByTag "input"
456+ path <- liftIO $ writeSystemTempFile "testFile" "file contents"
457+ WD.sendKeys (T.pack path) e
458+ WD.click =<< findElemWithRetry (WD.ByTag "button")
459+ liftIO $ removeFile path
460+ readRef filesRef `shouldBeWithRetryM` [T.pack $ FilePath.takeFileName path]
461+ testWidget (pure ()) uploadFile $ prerender_ (pure ()) $ do
462+ e <- inputElement $ def & initialAttributes .~ "type" =: "file"
463+ click <- button "save"
464+ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
465+ names <- liftJSM $ traverse File.getName fs
466+ liftIO $ writeRef filesRef names
467+
468+ describe "textAreaElement" $ do
469+ describe "hydration" $ session' $ do
470+ it "doesn't wipe user input when switching over" $ runWD $ do
471+ inputRef <- newRef ("" :: Text)
472+ testWidget'
473+ (do
474+ e <- findElemWithRetry $ WD.ByTag "textarea"
475+ WD.sendKeys "hello world" e
476+ pure e)
477+ (\e -> do
478+ WD.attr e "value" `shouldBeWithRetryM` Just "hello world"
479+ WD.click <=< findElemWithRetry $ WD.ByTag "button"
480+ readRef inputRef `shouldBeWithRetryM` "hello world"
481+ ) $ do
482+ e <- textAreaElement def
483+ click <- button "save"
484+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
485+ it "captures user input after switchover" $ runWD $ do
486+ inputRef <- newRef ("" :: Text)
487+ let checkValue = do
488+ WD.sendKeys "hello world" =<< findElemWithRetry (WD.ByTag "textarea")
489+ WD.click =<< findElemWithRetry (WD.ByTag "button")
490+ readRef inputRef `shouldBeWithRetryM` "hello world"
491+ testWidget (pure ()) checkValue $ do
492+ e <- textAreaElement def
493+ click <- button "save"
494+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
495+ it "sets focus appropriately" $ runWD $ do
496+ focusRef <- newRef False
497+ let checkValue = do
498+ readRef focusRef `shouldBeWithRetryM` False
499+ e <- findElemWithRetry $ WD.ByTag "textarea"
500+ WD.click e
501+ readRef focusRef `shouldBeWithRetryM` True
502+ testWidget (pure ()) checkValue $ do
503+ e <- textAreaElement def
504+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_textAreaElement_hasFocus e)
505+ it "sets focus when focus occurs before hydration" $ runWD $ do
506+ focusRef <- newRef False
507+ let setup = do
508+ e <- findElemWithRetry $ WD.ByTag "textarea"
509+ WD.click e
510+ ((== e) <$> WD.activeElem) `shouldBeWithRetryM` True
511+ readRef focusRef `shouldBeWithRetryM` False
512+ check = readRef focusRef `shouldBeWithRetryM` True
513+ testWidget setup check $ do
514+ e <- textAreaElement def
515+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_textAreaElement_hasFocus e)
516+ it "sets value appropriately" $ runWD $ do
517+ valueByUIRef <- newRef ("" :: Text)
518+ valueRef <- newRef ("" :: Text)
519+ setValueChan :: Chan Text <- liftIO newChan
520+ let checkValue = do
521+ readRef valueByUIRef `shouldBeWithRetryM` ""
522+ readRef valueRef `shouldBeWithRetryM` ""
523+ e <- findElemWithRetry $ WD.ByTag "textarea"
524+ WD.sendKeys "hello" e
525+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
526+ readRef valueRef `shouldBeWithRetryM` "hello"
527+ liftIO $ writeChan setValueChan "world"
528+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
529+ readRef valueRef `shouldBeWithRetryM` "world"
530+ testWidget (pure ()) checkValue $ do
531+ setValue' <- triggerEventWithChan setValueChan
532+ e <- textAreaElement $ def { _textAreaElementConfig_setValue = Just setValue' }
533+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _textAreaElement_input e
534+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
535+
536+ describe "hydration/immediate" $ session' $ do
537+ it "captures user input after switchover" $ runWD $ do
538+ inputRef :: IORef Text <- newRef ""
539+ let checkValue = do
540+ WD.sendKeys "hello world" <=< findElemWithRetry $ WD.ByTag "textarea"
541+ WD.click <=< findElemWithRetry $ WD.ByTag "button"
542+ readRef inputRef `shouldBeWithRetryM` "hello world"
543+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
544+ e <- textAreaElement def
545+ click <- button "save"
546+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
547+ it "sets focus appropriately" $ runWD $ do
548+ focusRef <- newRef False
549+ let checkValue = do
550+ readRef focusRef `shouldBeWithRetryM` False
551+ e <- findElemWithRetry $ WD.ByTag "textarea"
552+ WD.click e
553+ readRef focusRef `shouldBeWithRetryM` True
554+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
555+ e <- textAreaElement def
556+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_textAreaElement_hasFocus e)
557+ it "sets value appropriately" $ runWD $ do
558+ valueByUIRef :: IORef Text <- newRef ""
559+ valueRef :: IORef Text <- newRef ""
560+ setValueChan :: Chan Text <- liftIO newChan
561+ let checkValue = do
562+ readRef valueByUIRef `shouldBeWithRetryM` ""
563+ readRef valueRef `shouldBeWithRetryM` ""
564+ e <- findElemWithRetry $ WD.ByTag "textarea"
565+ WD.sendKeys "hello" e
566+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
567+ readRef valueRef `shouldBeWithRetryM` "hello"
568+ liftIO $ writeChan setValueChan "world"
569+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
570+ readRef valueRef `shouldBeWithRetryM` "world"
571+ testWidget (pure ()) checkValue $ do
572+ setValue' <- triggerEventWithChan setValueChan
573+ prerender_ (pure ()) $ do
574+ e <- textAreaElement $ def { _textAreaElementConfig_setValue = Just setValue' }
575+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _textAreaElement_input e
576+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
577+
578+ describe "selectElement" $ do
579+ let options :: DomBuilder t m => m ()
580+ options = do
581+ elAttr "option" ("value" =: "one" <> "id" =: "one") $ text "one"
582+ elAttr "option" ("value" =: "two" <> "id" =: "two") $ text "two"
583+ elAttr "option" ("value" =: "three" <> "id" =: "three") $ text "three"
584+ describe "hydration" $ session' $ do
585+ it "sets initial value correctly" $ runWD $ do
586+ inputRef <- newRef ("" :: Text)
587+ let setup = do
588+ e <- findElemWithRetry $ WD.ByTag "select"
589+ assertAttr e "value" (Just "three")
590+ WD.click =<< findElemWithRetry (WD.ById "two")
591+ pure e
592+ check e = do
593+ assertAttr e "value" (Just "two")
594+ readRef inputRef `shouldBeWithRetryM` "three"
595+ WD.click =<< findElemWithRetry (WD.ByTag "button")
596+ assertAttr e "value" (Just "two")
597+ readRef inputRef `shouldBeWithRetryM` "two"
598+ testWidget' setup check $ do
599+ (e, ()) <- selectElement (def { _selectElementConfig_initialValue = "three" }) options
600+ click <- button "save"
601+ liftIO . writeRef inputRef <=< sample $ current $ _selectElement_value e
602+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (_selectElement_value e)) click
603+ it "captures user input after switchover" $ runWD $ do
604+ inputRef <- newRef ("" :: Text)
605+ let checkValue = do
606+ e <- findElemWithRetry $ WD.ByTag "select"
607+ assertAttr e "value" (Just "one")
608+ WD.click =<< findElemWithRetry (WD.ById "two")
609+ assertAttr e "value" (Just "two")
610+ WD.click =<< findElemWithRetry (WD.ByTag "button")
611+ readRef inputRef `shouldBeWithRetryM` "two"
612+ testWidget (pure ()) checkValue $ do
613+ (e, ()) <- selectElement def options
614+ click <- button "save"
615+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (_selectElement_value e)) click
616+ it "sets focus appropriately" $ runWD $ do
617+ focusRef <- newRef False
618+ let checkValue = do
619+ readRef focusRef `shouldBeWithRetryM` False
620+ e <- findElemWithRetry $ WD.ByTag "select"
621+ WD.click e
622+ readRef focusRef `shouldBeWithRetryM` True
623+ testWidget (pure ()) checkValue $ do
624+ (e, ()) <- selectElement def options
625+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_selectElement_hasFocus e)
626+ it "sets focus when focus occurs before hydration" $ runWD $ do
627+ focusRef <- newRef False
628+ let setup = do
629+ e <- findElemWithRetry $ WD.ByTag "select"
630+ WD.click e
631+ ((== e) <$> WD.activeElem) `shouldBeWithRetryM` True
632+ readRef focusRef `shouldBeWithRetryM` False
633+ check = readRef focusRef `shouldBeWithRetryM` True
634+ testWidget setup check $ do
635+ (e, ()) <- selectElement def options
636+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_selectElement_hasFocus e)
637+ it "sets value appropriately" $ runWD $ do
638+ valueByUIRef <- newRef ("" :: Text)
639+ valueRef <- newRef ("" :: Text)
640+ setValueChan :: Chan Text <- liftIO newChan
641+ let checkValue = do
642+ e <- findElemWithRetry $ WD.ByTag "select"
643+ assertAttr e "value" (Just "one")
644+ readRef valueByUIRef `shouldBeWithRetryM` "one"
645+ readRef valueRef `shouldBeWithRetryM` "one"
646+ WD.click <=< findElemWithRetry $ WD.ById "two"
647+ readRef valueByUIRef `shouldBeWithRetryM` "two"
648+ readRef valueRef `shouldBeWithRetryM` "two"
649+ liftIO $ writeChan setValueChan "three"
650+ readRef valueByUIRef `shouldBeWithRetryM` "two"
651+ readRef valueRef `shouldBeWithRetryM` "three"
652+ testWidget (pure ()) checkValue $ do
653+ setValue' <- triggerEventWithChan setValueChan
654+ (e, ()) <- selectElement def { _selectElementConfig_setValue = Just setValue' } options
655+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _selectElement_change e
656+ performEvent_ $ liftIO . writeRef valueRef <$> updated (_selectElement_value e)
657+
658+ describe "hydration/immediate" $ session' $ do
659+ it "captures user input after switchover" $ runWD $ do
660+ inputRef :: IORef Text <- newRef ""
661+ let checkValue = do
662+ WD.click <=< findElemWithRetry $ WD.ById "two"
663+ WD.click <=< findElemWithRetry $ WD.ByTag "button"
664+ readRef inputRef `shouldBeWithRetryM` "two"
665+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
666+ (e, ()) <- selectElement def options
667+ click <- button "save"
668+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (_selectElement_value e)) click
669+ it "sets focus appropriately" $ runWD $ do
670+ focusRef <- newRef False
671+ let checkValue = do
672+ readRef focusRef `shouldBeWithRetryM` False
673+ e <- findElemWithRetry $ WD.ByTag "select"
674+ WD.click e
675+ readRef focusRef `shouldBeWithRetryM` True
676+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
677+ (e, ()) <- selectElement def options
678+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_selectElement_hasFocus e)
679+ it "has correct initial value" $ runWD $ do
680+ valueRef :: IORef Text <- newRef ""
681+ let checkValue = readRef valueRef `shouldBeWithRetryM` "one"
682+ testWidget (pure ()) checkValue $ do
683+ prerender_ (pure ()) $ do
684+ (e, ()) <- selectElement def { _selectElementConfig_initialValue = "one" } options
685+ liftIO . writeRef valueRef <=< sample $ current $ _selectElement_value e
686+ it "sets value appropriately" $ runWD $ do
687+ valueByUIRef :: IORef Text <- newRef ""
688+ valueRef :: IORef Text <- newRef ""
689+ setValueChan :: Chan Text <- liftIO newChan
690+ let checkValue = do
691+ WD.click =<< findElemWithRetry (WD.ById "two")
692+ readRef valueByUIRef `shouldBeWithRetryM` "two"
693+ readRef valueRef `shouldBeWithRetryM` "two"
694+ liftIO $ writeChan setValueChan "three"
695+ readRef valueByUIRef `shouldBeWithRetryM` "two"
696+ readRef valueRef `shouldBeWithRetryM` "three"
697+ testWidget (pure ()) checkValue $ do
698+ setValue' <- triggerEventWithChan setValueChan
699+ prerender_ (pure ()) $ do
700+ (e, ()) <- selectElement def { _selectElementConfig_setValue = Just setValue' } options
701+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _selectElement_change e
702+ performEvent_ $ liftIO . writeRef valueRef <$> updated (_selectElement_value e)
703+
704+ describe "prerender" $ session' $ do
705+ it "works in simple case" $ runWD $ do
706+ testWidget (checkBodyText "One") (checkBodyText "Two") $ do
707+ prerender_ (text "One") (text "Two")
708+ it "removes element correctly" $ runWD $ do
709+ testWidget' (findElemWithRetry $ WD.ByTag "span") elementShouldBeRemoved $ do
710+ prerender_ (el "span" $ text "One") (text "Two")
711+ it "can be nested in server widget" $ runWD $ do
712+ testWidget (checkBodyText "One") (checkBodyText "Three") $ do
713+ prerender_ (prerender_ (text "One") (text "Two")) (text "Three")
714+ it "can be nested in client widget" $ runWD $ do
715+ testWidget (checkBodyText "One") (checkBodyText "Three") $ do
716+ prerender_ (text "One") (prerender_ (text "Two") (text "Three"))
717+ it "works with prerender_ siblings" $ runWD $ do
718+ testWidget
719+ (checkTextInId "a1" "One" >> checkTextInId "b1" "Three" >> checkTextInId "c1" "Five")
720+ (checkTextInId "a2" "Two" >> checkTextInId "b2" "Four" >> checkTextInId "c2" "Six") $ do
721+ prerender_ (divId "a1" $ text "One") (divId "a2" $ text "Two")
722+ prerender_ (divId "b1" $ text "Three") (divId "b2" $ text "Four")
723+ prerender_ (divId "c1" $ text "Five") (divId "c2" $ text "Six")
724+ it "works inside other element" $ runWD $ do
725+ testWidget (checkTextInTag "div" "One") (checkTextInTag "div" "Two") $ do
726+ el "div" $ prerender_ (text "One") (text "Two")
727+-- TODO re-enable this
728+-- it "places fences and removes them" $ runWD $ do
729+-- testWidget'
730+-- (do
731+-- scripts <- WD.findElems $ WD.ByTag "script"
732+-- filterM (\s -> maybe False (\t -> "prerender" `T.isPrefixOf` t) <$> WD.attr s "type") scripts
733+-- )
734+-- (traverse_ elementShouldBeRemoved)
735+-- (el "span" $ prerender_ (text "One") (text "Two"))
736+ it "postBuild works on server side" $ runWD $ do
737+ lock :: MVar () <- liftIO newEmptyMVar
738+ testWidget (liftIO $ takeMVar lock) (pure ()) $ do
739+ prerender_ (do
740+ pb <- getPostBuild
741+ performEvent_ $ liftIO (putMVar lock ()) <$ pb) blank
742+ it "postBuild works on client side" $ runWD $ do
743+ lock :: MVar () <- liftIO newEmptyMVar
744+ testWidget (pure ()) (liftIO $ takeMVar lock) $ do
745+ prerender_ blank $ do
746+ pb <- getPostBuild
747+ performEvent_ $ liftIO (putMVar lock ()) <$ pb
748+ it "result Dynamic is updated *after* switchover" $ runWD $ do
749+ let preSwitchover = checkBodyText "PostBuild"
750+ check = checkBodyText "Client"
751+ testWidget preSwitchover check $ void $ do
752+ d <- prerender (pure "Initial") (pure "Client")
753+ pb <- getPostBuild
754+ initial <- sample $ current d
755+ textNode $ TextNodeConfig initial $ Just $ leftmost [updated d, "PostBuild" <$ pb]
756+ -- This essentially checks that the client IO runs *after* switchover/postBuild,
757+ -- thus can't create conflicting DOM
758+ it "can't exploit IO to break hydration" $ runWD $ do
759+ let preSwitchover = checkBodyText "Initial"
760+ testWidgetStatic preSwitchover $ void $ do
761+ ref <- liftIO $ newRef "Initial"
762+ prerender_ (pure ()) (liftIO $ writeRef ref "Client")
763+ text <=< liftIO $ readRef ref
764+ -- As above, so below
765+ it "can't exploit triggerEvent to break hydration" $ runWD $ do
766+ let preSwitchover = checkBodyText "Initial"
767+ check = checkBodyText "Client"
768+ testWidget preSwitchover check $ void $ do
769+ (e, trigger) <- newTriggerEvent
770+ prerender_ (pure ()) (liftIO $ trigger "Client")
771+ textNode $ TextNodeConfig "Initial" $ Just e
772+
773+ describe "runWithReplace" $ session' $ do
774+ it "works" $ runWD $ do
775+ replaceChan :: Chan Text <- liftIO newChan
776+ let setup = findElemWithRetry $ WD.ByTag "div"
777+ check ssr = do
778+ -- Check that the original element still exists and has the correct text
779+ shouldContainText "two" ssr
780+ liftIO $ writeChan replaceChan "three"
781+ elementShouldBeRemoved ssr
782+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
783+ testWidget' setup check $ do
784+ replace <- triggerEventWithChan replaceChan
785+ pb <- getPostBuild
786+ void $ runWithReplace (el "div" $ text "one") $ leftmost
787+ [ el "div" (text "two") <$ pb
788+ , el "span" . text <$> replace
789+ ]
790+ it "can be nested in initial widget" $ runWD $ do
791+ replaceChan :: Chan Text <- liftIO newChan
792+ let setup = findElemWithRetry $ WD.ByTag "div"
793+ check ssr = do
794+ -- Check that the original element still exists and has the correct text
795+ shouldContainText "two" ssr
796+ liftIO $ writeChan replaceChan "three"
797+ elementShouldBeRemoved ssr
798+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
799+ testWidget' setup check $ void $ flip runWithReplace never $ do
800+ replace <- triggerEventWithChan replaceChan
801+ pb <- getPostBuild
802+ void $ runWithReplace (el "div" $ text "one") $ leftmost
803+ [ el "div" (text "two") <$ pb
804+ , el "span" . text <$> replace
805+ ]
806+ it "can be nested in postBuild widget" $ runWD $ do
807+ replaceChan :: Chan Text <- liftIO newChan
808+ let setup = findElemWithRetry $ WD.ByTag "div"
809+ check ssr = do
810+ -- Check that the original element still exists and has the correct text
811+ shouldContainText "two" ssr
812+ liftIO $ writeChan replaceChan "three"
813+ elementShouldBeRemoved ssr
814+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
815+ testWidget' setup check $ void $ do
816+ pb <- getPostBuild
817+ runWithReplace (pure ()) $ ffor pb $ \() -> do
818+ replace <- triggerEventWithChan replaceChan
819+ pb' <- getPostBuild
820+ void $ runWithReplace (el "div" $ text "one") $ leftmost
821+ [ el "div" (text "two") <$ pb'
822+ , el "span" . text <$> replace
823+ ]
824+ it "postBuild works in replaced widget" $ runWD $ do
825+ replaceChan <- liftIO newChan
826+ pbLock <- liftIO newEmptyMVar
827+ let check = liftIO $ do
828+ writeChan replaceChan ()
829+ takeMVar pbLock
830+ testWidget (pure ()) check $ void $ do
831+ replace <- triggerEventWithChan replaceChan
832+ runWithReplace (pure ()) $ ffor replace $ \() -> do
833+ pb <- getPostBuild
834+ performEvent_ $ liftIO (putMVar pbLock ()) <$ pb
835+ it "can be nested in event widget" $ runWD $ do
836+ replaceChan1 :: Chan Text <- liftIO newChan
837+ replaceChan2 :: Chan Text <- liftIO newChan
838+ lock :: MVar () <- liftIO newEmptyMVar
839+ let check = do
840+ shouldContainText "" =<< findElemWithRetry (WD.ByTag "body")
841+ liftIO $ do
842+ writeChan replaceChan1 "one"
843+ takeMVar lock
844+ one <- findElemWithRetry $ WD.ByTag "div"
845+ shouldContainText "pb" one
846+ liftIO $ writeChan replaceChan2 "two"
847+ elementShouldBeRemoved one
848+ shouldContainText "two" =<< findElemWithRetry (WD.ByTag "span")
849+ testWidget (pure ()) check $ void $ do
850+ replace1 <- triggerEventWithChan replaceChan1
851+ runWithReplace (pure ()) $ ffor replace1 $ \r1 -> do
852+ replace2 <- triggerEventWithChan replaceChan2
853+ pb <- getPostBuild
854+ performEvent_ $ liftIO (putMVar lock ()) <$ pb
855+ void $ runWithReplace (el "div" $ text r1) $ leftmost
856+ [ el "span" . text <$> replace2
857+ , el "div" (text "pb") <$ pb
858+ ]
859+ it "prerender works in replaced widget" $ runWD $ do
860+ replaceChan <- liftIO newChan
861+ lock :: MVar () <- liftIO newEmptyMVar
862+ pbLock :: MVar () <- liftIO newEmptyMVar
863+ let check = do
864+ liftIO $ do
865+ writeChan replaceChan ()
866+ takeMVar lock
867+ takeMVar pbLock
868+ testWidget (pure ()) check $ void $ do
869+ replace <- triggerEventWithChan replaceChan
870+ runWithReplace (pure ()) $ ffor replace $ \() -> do
871+ prerender_ (pure ()) $ do
872+ liftIO $ putMVar lock ()
873+ pb <- getPostBuild
874+ performEvent_ $ liftIO (putMVar pbLock ()) <$ pb
875+ it "prerender returns in immediate mode" $ runWD $ do
876+ replaceChan <- liftIO newChan
877+ pbLock <- liftIO newEmptyMVar
878+ let check = liftIO $ do
879+ writeChan replaceChan ()
880+ takeMVar pbLock
881+ testWidget (pure ()) check $ void $ do
882+ replace <- triggerEventWithChan replaceChan
883+ runWithReplace (pure ()) $ ffor replace $ \() -> do
884+ prerender_ (pure ()) (pure ())
885+ liftIO $ putMVar pbLock ()
886+ it "works in immediate mode (RHS of prerender)" $ runWD $ do
887+ replaceChan :: Chan Text <- liftIO newChan
888+ let check = do
889+ checkBodyText "one"
890+ one <- findElemWithRetry $ WD.ByTag "div"
891+ shouldContainText "one" one
892+ liftIO $ writeChan replaceChan "pb"
893+ elementShouldBeRemoved one
894+ shouldContainText "pb" =<< findElemWithRetry (WD.ByTag "span")
895+ testWidget (pure ()) check $ void $ do
896+ replace <- triggerEventWithChan replaceChan
897+ prerender_ blank $ do
898+ void $ runWithReplace (el "div" $ text "one") $ el "span" . text <$> replace
899+ it "works with postBuild in immediate mode (RHS of prerender)" $ runWD $ do
900+ replaceChan :: Chan Text <- liftIO newChan
901+ let check = do
902+ checkBodyText "two"
903+ two <- findElemWithRetry $ WD.ByTag "div"
904+ shouldContainText "two" two
905+ liftIO $ writeChan replaceChan "three"
906+ elementShouldBeRemoved two
907+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
908+ testWidget (pure ()) check $ void $ do
909+ replace <- triggerEventWithChan replaceChan
910+ prerender_ blank $ do
911+ pb <- getPostBuild
912+ void $ runWithReplace (el "div" $ text "one") $ leftmost
913+ [ el "div" (text "two") <$ pb
914+ , el "span" . text <$> replace
915+ ]
916+
917+ describe "traverseDMapWithKeyWithAdjust" $ session' $ do
918+ let widget :: DomBuilder t m => DKey a -> Identity a -> m (Identity a)
919+ widget k (Identity v) = elAttr "li" ("id" =: textKey k) $ do
920+ elClass "span" "key" $ text $ textKey k
921+ elClass "span" "value" $ text $ T.pack $ has @Show k $ show v
922+ pure (Identity v)
923+ checkItem :: WD.Element -> Text -> Text -> WD ()
924+ checkItem li k v = do
925+ putStrLnDebug "checkItem"
926+ shouldContainTextNoRetry k =<< WD.findElemFrom li (WD.ByClass "key")
927+ shouldContainTextNoRetry v =<< WD.findElemFrom li (WD.ByClass "value")
928+ checkInitialItems dm xs = do
929+ putStrLnDebug "checkInitialItems"
930+ liftIO $ assertEqual "Wrong amount of items in DOM" (DMap.size dm) (length xs)
931+ forM_ (zip xs (DMap.toList dm)) $ \(e, k :=> Identity v) -> checkItem e (textKey k) (T.pack $ has @Show k $ show v)
932+ getAndCheckInitialItems dm = withRetry $ do
933+ putStrLnDebug "getAndCheckInitialItems"
934+ xs <- WD.findElems (WD.ByTag "li")
935+ checkInitialItems dm xs
936+ pure xs
937+ checkRemoval chan k = do
938+ putStrLnDebug "checkRemoval"
939+ e <- findElemWithRetry (WD.ById $ textKey k)
940+ liftIO $ writeChan chan $ PatchDMap $ DMap.singleton k (ComposeMaybe Nothing)
941+ elementShouldBeRemoved e
942+ checkReplace chan k v = do
943+ putStrLnDebug "checkReplace"
944+ e <- findElemWithRetry (WD.ById $ textKey k)
945+ liftIO $ writeChan chan $ PatchDMap $ DMap.singleton k (ComposeMaybe $ Just $ Identity v)
946+ elementShouldBeRemoved e
947+ e' <- findElemWithRetry $ WD.ById $ textKey k
948+ checkItem e' (textKey k) (T.pack $ show v)
949+ checkInsert chan k v = do
950+ putStrLnDebug "checkInsert"
951+ liftIO $ writeChan chan $ PatchDMap $ DMap.singleton k (ComposeMaybe $ Just $ Identity v)
952+ e <- findElemWithRetry (WD.ById $ textKey k)
953+ checkItem e (textKey k) (T.pack $ show v)
954+ postBuildPatch = PatchDMap $ DMap.fromList [Key_Char :=> ComposeMaybe Nothing, Key_Bool :=> ComposeMaybe (Just $ Identity True)]
955+ it "doesn't replace elements at switchover, can delete/update/insert" $ runWD $ do
956+ chan <- liftIO newChan
957+ let preSwitchover :: WD [WD.Element]
958+ preSwitchover = getAndCheckInitialItems keyMap
959+ check :: [WD.Element] -> WD ()
960+ check xs = do
961+ checkInitialItems keyMap xs
962+ checkRemoval chan Key_Int
963+ checkReplace chan Key_Char 'B'
964+ checkInsert chan Key_Bool True
965+ testWidget' preSwitchover check $ do
966+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap =<< triggerEventWithChan chan
967+ liftIO $ dmap `H.shouldBe` keyMap
968+ it "handles postBuild correctly" $ runWD $ do
969+ chan <- liftIO newChan
970+ let preSwitchover = getAndCheckInitialItems $ applyAlways postBuildPatch keyMap
971+ check xs = do
972+ withRetry $ checkInitialItems (applyAlways postBuildPatch keyMap) xs
973+ checkRemoval chan Key_Int
974+ checkInsert chan Key_Char 'B'
975+ checkReplace chan Key_Bool True
976+ testWidget' preSwitchover check $ void $ do
977+ pb <- getPostBuild
978+ replace <- triggerEventWithChan chan
979+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap $ leftmost [postBuildPatch <$ pb, replace]
980+ liftIO $ dmap `H.shouldBe` keyMap
981+ it "can delete/update/insert when built in prerender" $ runWD $ do
982+ chan <- liftIO newChan
983+ let check = do
984+ _ <- getAndCheckInitialItems keyMap
985+ checkRemoval chan Key_Int
986+ checkReplace chan Key_Char 'B'
987+ checkInsert chan Key_Bool True
988+ testWidget (pure ()) check $ do
989+ replace <- triggerEventWithChan chan
990+ prerender_ (pure ()) $ do
991+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap replace
992+ liftIO $ dmap `H.shouldBe` keyMap
993+ it "can delete/update/insert when built in immediate mode" $ runWD $ do
994+ chan <- liftIO newChan
995+ let check = do
996+ _ <- getAndCheckInitialItems keyMap
997+ checkRemoval chan Key_Int
998+ checkReplace chan Key_Char 'B'
999+ checkInsert chan Key_Bool True
1000+ testWidget (pure ()) check $ void $ do
1001+ pb <- getPostBuild
1002+ runWithReplace (pure ()) $ ffor pb $ \() -> void $ do
1003+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap =<< triggerEventWithChan chan
1004+ liftIO $ dmap `H.shouldBe` keyMap
1005+ -- Should be fixed by prerender changes!
1006+ it "handles postBuild correctly in prerender" $ runWD $ do
1007+ chan <- liftIO newChan
1008+ let check = do
1009+ _ <- getAndCheckInitialItems $ applyAlways postBuildPatch keyMap
1010+ checkRemoval chan Key_Int
1011+ checkInsert chan Key_Char 'B'
1012+ checkReplace chan Key_Bool True
1013+ testWidget (pure ()) check $ do
1014+ replace <- triggerEventWithChan chan
1015+ prerender_ (pure ()) $ void $ do
1016+ pb <- getPostBuild
1017+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap $ leftmost [postBuildPatch <$ pb, replace]
1018+ liftIO $ dmap `H.shouldBe` keyMap
1019+
1020+ describe "traverseIntMapWithKeyWithAdjust" $ session' $ do
1021+ let textKeyInt k = "key" <> T.pack (show k)
1022+ intMap = IntMap.fromList
1023+ [ (1, "one")
1024+ , (2, "two")
1025+ , (3, "three")
1026+ ]
1027+ let widget :: DomBuilder t m => IntMap.Key -> Text -> m Text
1028+ widget k v = elAttr "li" ("id" =: textKeyInt k) $ do
1029+ elClass "span" "key" $ text $ textKeyInt k
1030+ elClass "span" "value" $ text v
1031+ pure v
1032+ checkItem :: WD.Element -> Text -> Text -> WD ()
1033+ checkItem li k v = do
1034+ putStrLnDebug "checkItem"
1035+ shouldContainTextNoRetry k =<< WD.findElemFrom li (WD.ByClass "key")
1036+ shouldContainTextNoRetry v =<< WD.findElemFrom li (WD.ByClass "value")
1037+ checkInitialItems dm xs = do
1038+ putStrLnDebug "checkInitialItems"
1039+ liftIO $ assertEqual "Wrong amount of items in DOM" (IntMap.size dm) (length xs)
1040+ forM_ (zip xs (IntMap.toList dm)) $ \(e, (k, v)) -> checkItem e (textKeyInt k) v
1041+ getAndCheckInitialItems dm = withRetry $ do
1042+ putStrLnDebug "getAndCheckInitialItems"
1043+ xs <- WD.findElems (WD.ByTag "li")
1044+ checkInitialItems dm xs
1045+ pure xs
1046+ checkRemoval chan k = do
1047+ putStrLnDebug "checkRemoval"
1048+ e <- findElemWithRetry (WD.ById $ textKeyInt k)
1049+ liftIO $ writeChan chan $ PatchIntMap $ IntMap.singleton k Nothing
1050+ elementShouldBeRemoved e
1051+ checkReplace chan k v = do
1052+ putStrLnDebug "checkReplace"
1053+ e <- findElemWithRetry (WD.ById $ textKeyInt k)
1054+ liftIO $ writeChan chan $ PatchIntMap $ IntMap.singleton k $ Just v
1055+ elementShouldBeRemoved e
1056+ e' <- findElemWithRetry (WD.ById $ textKeyInt k)
1057+ withRetry $ checkItem e' (textKeyInt k) v
1058+ checkInsert chan k v = do
1059+ putStrLnDebug "checkInsert"
1060+ liftIO $ writeChan chan $ PatchIntMap $ IntMap.singleton k $ Just v
1061+ e <- findElemWithRetry (WD.ById $ textKeyInt k)
1062+ checkItem e (textKeyInt k) v
1063+ postBuildPatch = PatchIntMap $ IntMap.fromList [(2, Nothing), (3, Just "trois"), (4, Just "four")]
1064+ xit "doesn't replace elements at switchover, can delete/update/insert" $ runWD $ do
1065+ chan <- liftIO newChan
1066+ let preSwitchover = getAndCheckInitialItems intMap
1067+ check xs = do
1068+ checkInitialItems intMap xs
1069+ checkRemoval chan 1
1070+ checkReplace chan 2 "deux"
1071+ checkInsert chan 4 "four"
1072+ testWidget' preSwitchover check $ void $ do
1073+ (im, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap =<< triggerEventWithChan chan
1074+ liftIO $ im `H.shouldBe` intMap
1075+ xit "handles postBuild correctly" $ runWD $ do
1076+ chan <- liftIO newChan
1077+ let preSwitchover = getAndCheckInitialItems $ applyAlways postBuildPatch intMap
1078+ check xs = do
1079+ withRetry $ checkInitialItems (applyAlways postBuildPatch intMap) xs
1080+ checkRemoval chan 1
1081+ checkInsert chan 2 "deux"
1082+ checkReplace chan 3 "trois"
1083+ testWidget' preSwitchover check $ void $ do
1084+ pb <- getPostBuild
1085+ replace <- triggerEventWithChan chan
1086+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap $ leftmost [postBuildPatch <$ pb, replace]
1087+ liftIO $ dmap `H.shouldBe` intMap
1088+ xit "can delete/update/insert when built in prerender" $ runWD $ do
1089+ chan <- liftIO newChan
1090+ let check = do
1091+ _ <- getAndCheckInitialItems intMap
1092+ checkRemoval chan 1
1093+ checkReplace chan 2 "deux"
1094+ checkInsert chan 4 "quatre"
1095+ testWidget (pure ()) check $ do
1096+ replace <- triggerEventWithChan chan
1097+ prerender_ (pure ()) $ do
1098+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap replace
1099+ liftIO $ dmap `H.shouldBe` intMap
1100+ xit "can delete/update/insert when built in immediate mode" $ runWD $ do
1101+ chan <- liftIO newChan
1102+ let check = do
1103+ _ <- getAndCheckInitialItems intMap
1104+ checkRemoval chan 1
1105+ checkReplace chan 2 "deux"
1106+ checkInsert chan 4 "quatre"
1107+ testWidget (pure ()) check $ void $ do
1108+ pb <- getPostBuild
1109+ runWithReplace (pure ()) $ ffor pb $ \() -> void $ do
1110+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap =<< triggerEventWithChan chan
1111+ liftIO $ dmap `H.shouldBe` intMap
1112+ -- Should be fixed by prerender changes!
1113+ xit "handles postBuild correctly in prerender" $ runWD $ do
1114+ chan <- liftIO newChan
1115+ let check = do
1116+ _ <- getAndCheckInitialItems $ applyAlways postBuildPatch intMap
1117+ checkRemoval chan 1
1118+ checkInsert chan 2 "deux"
1119+ checkReplace chan 3 "trois"
1120+ testWidget (pure ()) check $ do
1121+ replace <- triggerEventWithChan chan
1122+ prerender_ (pure ()) $ void $ do
1123+ pb <- getPostBuild
1124+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap $ leftmost [postBuildPatch <$ pb, replace]
1125+ liftIO $ dmap `H.shouldBe` intMap
1126+
1127+ describe "traverseDMapWithKeyWithAdjustWithMove" $ session' $ do
1128+ let widget :: DomBuilder t m => Key2 a -> Identity a -> m (Identity a)
1129+ widget k (Identity v) = elAttr "li" ("id" =: textKey2 k) $ do
1130+ elClass "span" "key" $ text $ textKey2 k
1131+ elClass "span" "value" $ text $ T.pack $ has @Show k $ show v
1132+ pure (Identity v)
1133+ textKey2 :: Key2 a -> Text
1134+ textKey2 = \case
1135+ Key2_Int i -> "i" <> T.pack (show i)
1136+ Key2_Char c -> "c" <> T.pack [c]
1137+ checkItem :: WD.Element -> Text -> Text -> WD ()
1138+ checkItem li k v = do
1139+ shouldContainTextNoRetry k =<< WD.findElemFrom li (WD.ByClass "key")
1140+ shouldContainTextNoRetry v =<< WD.findElemFrom li (WD.ByClass "value")
1141+ checkInitialItems dm xs = do
1142+ liftIO $ assertEqual "Wrong amount of items in DOM" (DMap.size dm) (length xs)
1143+ forM_ (zip xs (DMap.toList dm)) $ \(e, k :=> Identity v) -> checkItem e (textKey2 k) (T.pack $ has @Show k $ show v)
1144+ getAndCheckInitialItems dm = withRetry $ do
1145+ xs <- WD.findElems (WD.ByTag "li")
1146+ checkInitialItems dm xs
1147+ pure xs
1148+ moveSpec
1149+ :: (DMap Key2 Identity -> (WD.Element -> Chan (PatchDMapWithMove Key2 Identity) -> WD ()) -> WD ())
1150+ -> H.SpecM (WdTestSession ()) ()
1151+ moveSpec testMove = do
1152+ it "can insert an item" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 3 ==> 3]) $ \body chan -> do
1153+ shouldContainText (T.strip $ T.unlines ["i11","i33"]) body
1154+ liftIO $ writeChan chan (insertDMapKey (Key2_Int 2) 2)
1155+ shouldContainText (T.strip $ T.unlines ["i11","i22","i33"]) body
1156+ it "can delete an item" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 2 ==> 2, Key2_Int 3 ==> 3]) $ \body chan -> do
1157+ shouldContainText (T.strip $ T.unlines ["i11","i22","i33"]) body
1158+ liftIO $ writeChan chan (deleteDMapKey (Key2_Int 2))
1159+ shouldContainText (T.strip $ T.unlines ["i11","i33"]) body
1160+ it "can swap items" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 2 ==> 2, Key2_Int 3 ==> 3, Key2_Int 4 ==> 4]) $ \body chan -> do
1161+ shouldContainText (T.strip $ T.unlines ["i11","i22","i33","i44"]) body
1162+ liftIO $ writeChan chan (swapDMapKey (Key2_Int 2) (Key2_Int 3))
1163+ shouldContainText (T.strip $ T.unlines ["i11","i33","i22","i44"]) body
1164+-- TODO re-enable this
1165+-- it "can move items" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 2 ==> 2, Key2_Int 3 ==> 3, Key2_Int 4 ==> 4]) $ \body chan -> do
1166+-- shouldContainText (T.strip $ T.unlines ["i11","i22","i33", "i44"]) body
1167+-- liftIO $ writeChan chan (moveDMapKey (Key2_Int 2) (Key2_Int 3))
1168+-- shouldContainText (T.strip $ T.unlines ["i11","i22","i44"]) body
1169+-- liftIO $ writeChan chan (moveDMapKey (Key2_Int 2) (Key2_Int 3)) -- attempt a move to nonexistent key should delete
1170+-- shouldContainText (T.strip $ T.unlines ["i11","i44"]) body
1171+-- liftIO $ writeChan chan (moveDMapKey (Key2_Int 2) (Key2_Int 3)) -- this causes a JSException in immediate builder too
1172+-- shouldContainText (T.strip $ T.unlines ["i11","i44"]) body
1173+-- liftIO $ writeChan chan (insertDMapKey (Key2_Int 2) 2) -- this step will fail if the above caused an exception, thus works as a proxy for detecting the exception given we can't catch it
1174+-- shouldContainText (T.strip $ T.unlines ["i11","i22","i44"]) body
1175+
1176+ describe "hydration" $ moveSpec $ \initMap test -> do
1177+ chan <- liftIO newChan
1178+ let preSwitchover = getAndCheckInitialItems initMap
1179+ check xs = do
1180+ checkInitialItems initMap xs
1181+ body <- getBody
1182+ test body chan
1183+ testWidget' preSwitchover check $ void $ do
1184+ (dmap, _evt) <- traverseDMapWithKeyWithAdjustWithMove widget initMap =<< triggerEventWithChan chan
1185+ liftIO $ assertEqual "DMap" initMap dmap
1186+
1187+ describe "hydration/immediate" $ moveSpec $ \initMap test -> do
1188+ chan <- liftIO newChan
1189+ replace <- liftIO newChan
1190+ lock :: MVar () <- liftIO newEmptyMVar
1191+ let check = do
1192+ liftIO $ do
1193+ writeChan replace ()
1194+ takeMVar lock
1195+ xs <- getAndCheckInitialItems initMap
1196+ checkInitialItems initMap xs
1197+ body <- getBody
1198+ test body chan
1199+ testWidget (pure ()) check $ void $ do
1200+ e <- triggerEventWithChan replace
1201+ runWithReplace (pure ()) $ ffor e $ \() -> void $ do
1202+ pb <- getPostBuild
1203+ performEvent_ $ liftIO (putMVar lock ()) <$ pb
1204+ (dmap, _evt) <- traverseDMapWithKeyWithAdjustWithMove widget initMap =<< triggerEventWithChan chan
1205+ liftIO $ assertEqual "DMap" initMap dmap
1206+
1207+ describe "hydrating invalid HTML" $ session' $ do
1208+ it "can hydrate list in paragraph" $ runWD $ do
1209+ let preSwitchover = do
1210+ checkBodyText "before\ninner\nafter"
1211+ -- Two <p> tags should be present
1212+ (p1, p2) <- WD.findElems (WD.ByTag "p") >>= \case
1213+ [p1, p2] -> pure (p1, p2)
1214+ _ -> error "Unexpected number of `p` tags (expected 2)"
1215+ ol <- findElemWithRetry (WD.ByTag "ol")
1216+ shouldContainText "before" p1
1217+ shouldContainText "inner" ol
1218+ shouldContainText "" p2
1219+ pure (p1, ol, p2)
1220+ check (p1, ol, p2) = do
1221+ checkBodyText "before\ninner\nafter"
1222+ shouldContainText "before\ninner\nafter" p1
1223+ elementShouldBeRemoved ol
1224+ elementShouldBeRemoved p2
1225+ testWidget' preSwitchover check $ do
1226+ -- This is deliberately invalid HTML, the browser will interpret it as
1227+ -- <p>before</p><ol>inner</ol>after<p></p>
1228+ el "p" $ do
1229+ text "before"
1230+ el "ol" $ text "inner"
1231+ text "after"
1232+
1233+data Selenium = Selenium
1234+ { _selenium_portNumber :: PortNumber
1235+ , _selenium_stopServer :: IO ()
1236+ }
1237+
1238+startSeleniumServer :: PortNumber -> IO (IO ())
1239+startSeleniumServer port = do
1240+ (_,_,_,ph) <- createProcess $ (proc "selenium-server" ["-port", show port])
1241+ { std_in = NoStream
1242+ , std_out = NoStream
1243+ , std_err = NoStream
1244+ }
1245+ return $ terminateProcess ph
1246+
1247+withSeleniumServer :: (Selenium -> IO ()) -> IO ()
1248+withSeleniumServer f = do
1249+ stopServer <- startSeleniumServer seleniumPort
1250+ threadDelay $ 1000 * 1000 * 2 -- TODO poll or wait on a a signal to block on
1251+ f $ Selenium
1252+ { _selenium_portNumber = seleniumPort
1253+ , _selenium_stopServer = stopServer
1254+ }
1255+
1256+triggerEventWithChan :: (Reflex t, TriggerEvent t m, Prerender js t m) => Chan a -> m (Event t a)
1257+triggerEventWithChan chan = do
1258+ (e, trigger) <- newTriggerEvent
1259+ -- In prerender because we only want to do this on the client
1260+ prerender_ (pure ()) $ void $ liftIO $ forkIO $ forever $ trigger =<< readChan chan
1261+ pure e
1262+
1263+shouldBeWithRetryM :: (Eq a, Show a) => WD a -> a -> WD ()
1264+shouldBeWithRetryM m expected = withRetry $ do
1265+ got <- m
1266+ got `shouldBe` expected
1267+
1268+assertAttr :: WD.Element -> Text -> Maybe Text -> WD ()
1269+assertAttr e k v = liftIO . assertEqual "Incorrect attribute value" v =<< WD.attr e k
1270+
1271+elementShouldBeRemoved :: WD.Element -> WD ()
1272+elementShouldBeRemoved e = withRetry $ do
1273+ try (WD.getText e) >>= \case
1274+ Left (WD.FailedCommand WD.StaleElementReference _) -> return ()
1275+ Left err -> throwM err
1276+ Right !_ -> liftIO $ assertFailure "Expected element to be removed, but it still exists"
1277+
1278+shouldContainText :: Text -> WD.Element -> WD ()
1279+shouldContainText t = withRetry . shouldContainTextNoRetry t
1280+
1281+shouldContainTextNoRetry :: Text -> WD.Element -> WD ()
1282+shouldContainTextNoRetry t = flip shouldBe t <=< WD.getText
1283+
1284+checkBodyText :: Text -> WD ()
1285+checkBodyText = checkTextInTag "body"
1286+
1287+checkTextInTag :: Text -> Text -> WD ()
1288+checkTextInTag t expected = do
1289+ e <- findElemWithRetry (WD.ByTag t)
1290+ shouldContainText expected e
1291+
1292+checkTextInId :: Text -> Text -> WD ()
1293+checkTextInId i expected = do
1294+ e <- findElemWithRetry (WD.ById i)
1295+ shouldContainText expected e
1296+
1297+findElemWithRetry :: Selector -> WD WD.Element
1298+findElemWithRetry = withRetry . WD.findElem
1299+
1300+getBody :: WD WD.Element
1301+getBody = WD.findElem $ WD.ByTag "body"
1302+
1303+withRetry :: forall a. WD a -> WD a
1304+withRetry a = wait 300
1305+ where wait :: Int -> WD a
1306+ wait 0 = a
1307+ wait n = try a >>= \case
1308+ Left (_ :: SomeException) -> do
1309+ liftIO $ threadDelay 100000
1310+ wait $ n - 1
1311+ Right v -> return v
1312+
1313+divId :: DomBuilder t m => Text -> m a -> m a
1314+divId i = elAttr "div" ("id" =: i)
1315+
1316+type TestWidget js t m = (DomBuilder t m, MonadHold t m, PostBuild t m, Prerender js t m, PerformEvent t m, TriggerEvent t m, MonadFix m, MonadIO (Performable m), MonadIO m)
1317+
1318+testWidgetStaticDebug
1319+ :: Bool
1320+ -> WD b
1321+ -- ^ Webdriver commands to run before JS runs and after hydration switchover
1322+ -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ())
1323+ -- ^ Widget we are testing
1324+ -> WD b
1325+testWidgetStaticDebug withDebugging w = testWidgetDebug withDebugging (void w) w
1326+
1327+-- | TODO: do something about JSExceptions not causing tests to fail
1328+testWidgetDebug
1329+ :: Bool
1330+ -> WD ()
1331+ -- ^ Webdriver commands to run before the JS runs (i.e. on the statically rendered page)
1332+ -> WD b
1333+ -- ^ Webdriver commands to run after hydration switchover
1334+ -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ())
1335+ -- ^ Widget we are testing
1336+ -> WD b
1337+testWidgetDebug withDebugging beforeJS afterSwitchover =
1338+ testWidgetDebug' withDebugging beforeJS (const afterSwitchover)
1339+
1340+-- | TODO: do something about JSExceptions not causing tests to fail
1341+testWidgetDebug'
1342+ :: Bool
1343+ -> WD a
1344+ -- ^ Webdriver commands to run before the JS runs (i.e. on the statically rendered page)
1345+ -> (a -> WD b)
1346+ -- ^ Webdriver commands to run after hydration switchover
1347+ -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ())
1348+ -- ^ Widget we are testing (contents of body)
1349+ -> WD b
1350+testWidgetDebug' withDebugging beforeJS afterSwitchover bodyWidget = do
1351+ let putStrLnDebug :: MonadIO m => Text -> m ()
1352+ putStrLnDebug m = when withDebugging $ liftIO $ putStrLn $ T.unpack m
1353+ staticApp = do
1354+ el "head" $ pure ()
1355+ el "body" $ do
1356+ bodyWidget
1357+ el "script" $ text $ TE.decodeUtf8 $ LBS.toStrict $ jsaddleJs False
1358+ putStrLnDebug "rendering static"
1359+ ((), html) <- liftIO $ renderStatic $ runHydratableT staticApp
1360+ putStrLnDebug "rendered static"
1361+ waitBeforeJS <- liftIO newEmptyMVar -- Empty until JS should be run
1362+ waitUntilSwitchover <- liftIO newEmptyMVar -- Empty until switchover
1363+ let entryPoint = do
1364+ putStrLnDebug "taking waitBeforeJS"
1365+ liftIO $ takeMVar waitBeforeJS
1366+ let switchOverAction = do
1367+ putStrLnDebug "switchover syncPoint"
1368+ syncPoint
1369+ putStrLnDebug "putting waitUntilSwitchover"
1370+ liftIO $ putMVar waitUntilSwitchover ()
1371+ putStrLnDebug "put waitUntilSwitchover"
1372+ putStrLnDebug "running mainHydrationWidgetWithSwitchoverAction"
1373+ mainHydrationWidgetWithSwitchoverAction switchOverAction blank bodyWidget
1374+ putStrLnDebug "syncPoint after mainHydrationWidgetWithSwitchoverAction"
1375+ syncPoint
1376+ application <- liftIO $ jsaddleOr defaultConnectionOptions entryPoint $ \_ sendResponse -> do
1377+ putStrLnDebug "sending response"
1378+ r <- sendResponse $ responseLBS status200 [] $ "<!doctype html>\n" <> LBS.fromStrict html
1379+ putStrLnDebug "sent response"
1380+ return r
1381+ waitJSaddle <- liftIO newEmptyMVar
1382+ let settings = foldr ($) Warp.defaultSettings
1383+ [ Warp.setPort $ fromIntegral $ toInteger jsaddlePort
1384+ , Warp.setBeforeMainLoop $ do
1385+ putStrLnDebug "putting waitJSaddle"
1386+ putMVar waitJSaddle ()
1387+ putStrLnDebug "put waitJSaddle"
1388+ ]
1389+ -- hSilence to get rid of ConnectionClosed logs
1390+ silenceIfDebug = if withDebugging then id else hSilence [stderr]
1391+ jsaddleWarp = forkIO $ silenceIfDebug $ Warp.runSettings settings application
1392+ jsaddleTid <- liftIO jsaddleWarp
1393+ putStrLnDebug "taking waitJSaddle"
1394+ liftIO $ takeMVar waitJSaddle
1395+ putStrLnDebug "opening page"
1396+ WD.openPage $ "http://localhost:" <> show jsaddlePort
1397+ putStrLnDebug "running beforeJS"
1398+ a <- beforeJS
1399+ putStrLnDebug "putting waitBeforeJS"
1400+ liftIO $ putMVar waitBeforeJS ()
1401+ putStrLnDebug "taking waitUntilSwitchover"
1402+ liftIO $ takeMVar waitUntilSwitchover
1403+ putStrLnDebug "running afterSwitchover"
1404+ b <- afterSwitchover a
1405+ putStrLnDebug "killing jsaddle thread"
1406+ liftIO $ killThread jsaddleTid
1407+ return b
1408+
1409+data Key2 a where
1410+ Key2_Int :: Int -> Key2 Int
1411+ Key2_Char :: Char -> Key2 Char
1412+
1413+deriveGEq ''Key2
1414+deriveGCompare ''Key2
1415+deriveGShow ''Key2
1416+deriveArgDict ''Key2
reflex-dom/ChangeLog.mdnew file+18
@@ -0,0 +1,18 @@
1+# Revision history for reflex-dom
2+
3+## Unreleased
4+* On Android, enable prompting the user for geolocation
5+ permissions on demand by default. See
6+ https://developer.android.com/reference/android/webkit/WebChromeClient.html#onGeolocationPermissionsShowPrompt(java.lang.String,%20android.webkit.GeolocationPermissions.Callback)
7+ for details.
8+
9+## 0.5.1
10+
11+* The default jsaddle backend on macOS when built from nix
12+ is now jsaddle-wkwebview, matching the behaviour of cabal
13+ builds.
14+
15+## 0.5
16+
17+* Re-export new hydration widget "mainWidget" functions.
18+* The use-warp flag now properly takes precedence on macOS.
reflex-dom/benchmarks/krausest.hs+7−7
@@ -28,15 +28,15 @@ main = do
28 seed <- randomIO
28 seed <- randomIO
29 mainWidgetWithHead' (\_ -> headW, \_ -> bodyW seed)
29 mainWidgetWithHead' (\_ -> headW, \_ -> bodyW seed)
30
30
31−titleW :: MonadWidget t m => m ()
31+titleW :: DomBuilder t m => m ()
32titleW = text "Reflex-dom keyed"
32titleW = text "Reflex-dom keyed"
33
33
34−headW :: MonadWidget t m => m ()
34+headW :: DomBuilder t m => m ()
35headW = do
35headW = do
36 el "title" titleW
36 el "title" titleW
37 elAttr "link" ("href" =: "/css/currentStyle.css" <> "rel" =: "stylesheet") blank
37 elAttr "link" ("href" =: "/css/currentStyle.css" <> "rel" =: "stylesheet") blank
38
38
39−bodyW :: forall t m. MonadWidget t m => Entropy -> m ()
39+bodyW :: forall t m. (MonadFix m, MonadHold t m, DomBuilder t m) => Entropy -> m ()
40bodyW seed = divClass "main" $ divClass "container" $ mdo
40bodyW seed = divClass "main" $ divClass "container" $ mdo
41 buttonEvents <- divClass "jumbotron" $ divClass "row" $ do
41 buttonEvents <- divClass "jumbotron" $ divClass "row" $ do
42 divClass "col-md-6" $ el "h1" titleW
42 divClass "col-md-6" $ el "h1" titleW
@@ -75,14 +75,14 @@ step (m, t) f = ((m', t'), dt)
75 (m', dt) = f (m, t)
75 (m', dt) = f (m, t)
76 t' = applyAlways dt t
76 t' = applyAlways dt t
77
77
78−buttonW :: MonadWidget t m => Text -> Text -> a -> m (Event t a)
78+buttonW :: DomBuilder t m => Text -> Text -> a -> m (Event t a)
79buttonW id txt val = divClass "col-sm-6 smallpad" $ buttonW' ("class" =: "btn btn-primary btn-block" <> "id" =: id)
79buttonW id txt val = divClass "col-sm-6 smallpad" $ buttonW' ("class" =: "btn btn-primary btn-block" <> "id" =: id)
80 where
80 where
81 buttonW' attrs = do
81 buttonW' attrs = do
82 (b, _) <- elAttr' "button" attrs $ text txt
82 (b, _) <- elAttr' "button" attrs $ text txt
83 pure $ val <$ domEvent Click b
83 pure $ val <$ domEvent Click b
84
84
85−tableW :: MonadWidget t m => Event t TableDiff -> m (Event t (IntMap Step))
85+tableW :: (MonadHold t m, DomBuilder t m) => Event t TableDiff -> m (Event t (IntMap Step))
86tableW diff = do
86tableW diff = do
87 elClass "table" "table table-hover table-striped test-data" $ do
87 elClass "table" "table table-hover table-striped test-data" $ do
88 el "tbody" $ do
88 el "tbody" $ do
@@ -90,7 +90,7 @@ tableW diff = do
90 rowEvents <- holdIncremental v0 v'
90 rowEvents <- holdIncremental v0 v'
91 return $ mergeIntIncremental rowEvents
91 return $ mergeIntIncremental rowEvents
92
92
93−rowW :: MonadWidget t m => Row -> m (Event t Step)
93+rowW :: DomBuilder t m => Row -> m (Event t Step)
94rowW row = elClass "tr" (if selected row then "danger" else "") $
94rowW row = elClass "tr" (if selected row then "danger" else "") $
95 do
95 do
96 elClass "td" "col-md-1" $ do
96 elClass "td" "col-md-1" $ do
@@ -102,7 +102,7 @@ rowW row = elClass "tr" (if selected row then "danger" else "") $
102 where
102 where
103 tagClick f el = f row <$ (domEvent Click el)
103 tagClick f el = f row <$ (domEvent Click el)
104
104
105−deleteW :: MonadWidget t m => m ()
105+deleteW :: DomBuilder t m => m ()
106deleteW = el "a" $ elAttr "span" ("aria-hidden" =: "true" <> "class" =: "glyphicon glyphicon-remove") blank
106deleteW = el "a" $ elAttr "span" ("aria-hidden" =: "true" <> "class" =: "glyphicon glyphicon-remove") blank
107
107
108
108
reflex-dom/default.nixdeleted−35
@@ -1,35 +0,0 @@
1−{ mkDerivation, base, bytestring, jsaddle-webkit2gtk, jsaddle-wkwebview, jsaddle-warp, reflex
2−, reflex-dom-core, stdenv, text, ghc, hostPlatform, jsaddle-clib, android-activity ? null
3−, ghcBackend ? "webkit2gtk"
4−}:
5−assert (builtins.elem ghcBackend [ "warp" "webkit2gtk" ]);
6−let isAndroid = hostPlatform.libc == "bionic";
7− ghcBackendPackage = {
8− webkit2gtk = jsaddle-webkit2gtk;
9− warp = jsaddle-warp;
10− }.${ghcBackend};
11−in mkDerivation {
12− pname = "reflex-dom";
13− version = "0.4";
14− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
15− libraryHaskellDepends = [
16− base bytestring reflex reflex-dom-core text
17− ] ++ (if ghc.isGhcjs or false then [
18− ] else if hostPlatform.isDarwin then [
19− jsaddle-wkwebview
20− jsaddle-warp
21− ] else if isAndroid then [
22− jsaddle-clib
23− android-activity
24− ] else [
25− ghcBackendPackage
26− ]);
27− configureFlags = if isAndroid then [
28− "-fandroid"
29− ] else if ghcBackend == "warp" then [
30− "-fuse-warp"
31− ] else [
32− ];
33− description = "Functional Reactive Web Apps with Reflex";
34− license = stdenv.lib.licenses.bsd3;
35−}
reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+21−2
@@ -8,6 +8,7 @@ import android.util.Log;
8import android.view.ViewGroup.LayoutParams;
8import android.view.ViewGroup.LayoutParams;
9import android.view.Window;
9import android.view.Window;
10import android.webkit.CookieManager;
10import android.webkit.CookieManager;
11+import android.webkit.GeolocationPermissions;
11import android.webkit.JavascriptInterface;
12import android.webkit.JavascriptInterface;
12import android.webkit.MimeTypeMap;
13import android.webkit.MimeTypeMap;
13import android.webkit.PermissionRequest;
14import android.webkit.PermissionRequest;
@@ -21,6 +22,8 @@ import android.graphics.Bitmap;
21import java.io.IOException;
22import java.io.IOException;
22import java.io.InputStream;
23import java.io.InputStream;
23import android.content.Intent;
24import android.content.Intent;
25+import android.content.ActivityNotFoundException;
26+import java.util.concurrent.atomic.AtomicBoolean;
24
27
25import java.nio.charset.StandardCharsets;
28import java.nio.charset.StandardCharsets;
26
29
@@ -44,11 +47,17 @@ public class MainWidget {
44 wv.setWebContentsDebuggingEnabled(true);
47 wv.setWebContentsDebuggingEnabled(true);
45 // allow video to play without user interaction
48 // allow video to play without user interaction
46 wv.getSettings().setMediaPlaybackRequiresUserGesture(false);
49 wv.getSettings().setMediaPlaybackRequiresUserGesture(false);
50+ final AtomicBoolean jsaddleLoaded = new AtomicBoolean(false);
47
51
48 wv.setWebViewClient(new WebViewClient() {
52 wv.setWebViewClient(new WebViewClient() {
49 @Override
53 @Override
50 public void onPageFinished(WebView _view, String _url) {
54 public void onPageFinished(WebView _view, String _url) {
55+ Log.i("reflex", "onPageFinished");
56+ boolean alreadyLoaded = jsaddleLoaded.getAndSet(true);
57+ if(!alreadyLoaded) {
58+ Log.i("reflex", "loading jsaddle");
51− wv.evaluateJavascript(initialJS, null);
59+ wv.evaluateJavascript(initialJS, null);
60+ }
52 }
61 }
53
62
54 // Re-route / to /android_asset
63 // Re-route / to /android_asset
@@ -79,7 +88,12 @@ public class MainWidget {
79 @Override
88 @Override
80 public boolean shouldOverrideUrlLoading(WebView view, String url) {
89 public boolean shouldOverrideUrlLoading(WebView view, String url) {
81 if( url != null && !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
90 if( url != null && !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
91+ try {
82− view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
92+ view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
93+ }
94+ catch(ActivityNotFoundException e) {
95+ Log.e("reflex", "Starting activity for intent '" + url + "' failed!");
96+ }
83 return true;
97 return true;
84 } else {
98 } else {
85 return false;
99 return false;
@@ -109,6 +123,11 @@ public class MainWidget {
109 public Bitmap getDefaultVideoPoster() {
123 public Bitmap getDefaultVideoPoster() {
110 return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
124 return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
111 }
125 }
126+
127+ @Override
128+ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
129+ callback.invoke(origin, true, false);
130+ }
112 });
131 });
113
132
114 wv.addJavascriptInterface(new JSaddleCallbacks(jsaddleCallbacks), "jsaddle");
133 wv.addJavascriptInterface(new JSaddleCallbacks(jsaddleCallbacks), "jsaddle");
reflex-dom/reflex-dom.cabal+24−26
@@ -1,7 +1,10 @@
1Name: reflex-dom
1Name: reflex-dom
2−Version: 0.4
2+Version: 0.5.2.0
3Synopsis: Functional Reactive Web Apps with Reflex
3Synopsis: Functional Reactive Web Apps with Reflex
4+Description:
4−Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
5+ Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine.
6+ .
7+ For hackage documentation, please see: <https://hackage.haskell.org/package/reflex-dom-core>.
5License: BSD3
8License: BSD3
6License-file: LICENSE
9License-file: LICENSE
7Author: Ryan Trinkle
10Author: Ryan Trinkle
@@ -10,12 +13,9 @@ Stability: Experimental
10Category: FRP, Web, GUI, HTML, Javascript, Reactive, Reactivity, User Interfaces, User-interface
13Category: FRP, Web, GUI, HTML, Javascript, Reactive, Reactivity, User Interfaces, User-interface
11Build-type: Simple
14Build-type: Simple
12cabal-version: >=1.24
15cabal-version: >=1.24
13−-- Deal with https://github.com/haskell/cabal/issues/2544 / https://github.com/haskell/cabal/issues/367
14−extra-source-files: src/Reflex/Dom/Xhr/Foreign.hs
16+extra-source-files:
15− src/Reflex/Dom/WebSocket/Foreign.hs
16− src/Reflex/Dom/Xhr/ResponseType.hs
17− src/Reflex/Dom/Xhr/Exception.hs
18− java/org/reflexfrp/reflexdom/MainWidget.java
17+ java/org/reflexfrp/reflexdom/MainWidget.java
18+ ChangeLog.md
19
19
20flag use-warp
20flag use-warp
21 description: Use jsaddle-warp server
21 description: Use jsaddle-warp server
@@ -41,14 +41,9 @@ flag build-examples
41 default: False
41 default: False
42 manual: True
42 manual: True
43
43
44−flag android
45− description: Build an Android app
46− default: False
47− manual: True
48−
49library
44library
50 hs-source-dirs: src
45 hs-source-dirs: src
51− if flag(android)
46+ if os(android)
52 hs-source-dirs: src-android
47 hs-source-dirs: src-android
53 other-modules: Reflex.Dom.Android.MainWidget
48 other-modules: Reflex.Dom.Android.MainWidget
54 build-depends:
49 build-depends:
@@ -58,31 +53,32 @@ library
58 jsaddle
53 jsaddle
59 c-sources: cbits/MainWidget.c
54 c-sources: cbits/MainWidget.c
60 include-dirs: cbits/include
55 include-dirs: cbits/include
56+ install-includes: MainWidget.h
61 cpp-options: -DANDROID
57 cpp-options: -DANDROID
62 build-tools: hsc2hs
58 build-tools: hsc2hs
63 build-depends:
59 build-depends:
64− base >= 4.7 && < 4.11,
60+ base >= 4.7 && < 4.13,
65 bytestring == 0.10.*,
61 bytestring == 0.10.*,
66− reflex == 0.5.*,
62+ reflex >= 0.6.2,
67− reflex-dom-core >=0.4 && <0.5,
63+ reflex-dom-core >=0.5.2 && <0.6,
68 text == 1.2.*
64 text == 1.2.*
69 if !impl(ghcjs)
65 if !impl(ghcjs)
70− if os(osx) || os(ios)
66+ if flag(use-warp) && (os(linux) || os(osx))
71 build-depends:
67 build-depends:
72− data-default == 0.7.*,
73 jsaddle,
68 jsaddle,
74− jsaddle-wkwebview
69+ jsaddle-warp
75 else
70 else
76− if flag(use-warp)
71+ if os(osx) || os(ios)
77 build-depends:
72 build-depends:
73+ data-default == 0.7.*,
78 jsaddle,
74 jsaddle,
79− jsaddle-warp
75+ jsaddle-wkwebview
80 else
76 else
81 if flag(webkit2gtk)
77 if flag(webkit2gtk)
82 build-depends:
78 build-depends:
83 jsaddle-webkit2gtk
79 jsaddle-webkit2gtk
84 else
80 else
85− if !flag(android)
81+ if !os(android)
86 build-depends:
82 build-depends:
87 jsaddle-webkit2gtk
83 jsaddle-webkit2gtk
88
84
@@ -114,6 +110,7 @@ library
114 ghc-options: -fexpose-all-unfoldings
110 ghc-options: -fexpose-all-unfoldings
115 if flag(use-reflex-optimizer)
111 if flag(use-reflex-optimizer)
116 ghc-options: -fplugin=Reflex.Optimizer
112 ghc-options: -fplugin=Reflex.Optimizer
113+ default-language: Haskell2010
117
114
118executable sortableList
115executable sortableList
119 build-depends: base
116 build-depends: base
@@ -130,13 +127,13 @@ executable sortableList
130 hs-source-dirs: examples
127 hs-source-dirs: examples
131 main-is: sortableList.hs
128 main-is: sortableList.hs
132 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans -fspecialise-aggressively
129 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans -fspecialise-aggressively
133− ghc-prof-options: -fprof-auto
134 if !flag(build-examples)
130 if !flag(build-examples)
135 buildable: False
131 buildable: False
136 if flag(expose-all-unfoldings)
132 if flag(expose-all-unfoldings)
137 ghc-options: -fexpose-all-unfoldings
133 ghc-options: -fexpose-all-unfoldings
138 if flag(use-reflex-optimizer)
134 if flag(use-reflex-optimizer)
139 ghc-options: -fplugin=Reflex.Optimizer
135 ghc-options: -fplugin=Reflex.Optimizer
136+ default-language: Haskell2010
140
137
141executable benchmark
138executable benchmark
142 build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum
139 build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum
@@ -145,6 +142,7 @@ executable benchmark
145 ghc-options: -O2 -fspecialise-aggressively
142 ghc-options: -O2 -fspecialise-aggressively
146 if !impl(ghcjs)
143 if !impl(ghcjs)
147 buildable: False
144 buildable: False
145+ default-language: Haskell2010
148
146
149executable krausest
147executable krausest
150 build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum, vector
148 build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum, vector
@@ -152,11 +150,11 @@ executable krausest
152 main-is: krausest.hs
150 main-is: krausest.hs
153 ghc-options: -O2 -fspecialise-aggressively
151 ghc-options: -O2 -fspecialise-aggressively
154 if impl(ghcjs)
152 if impl(ghcjs)
155− ghc-options: -dedupe
153+ ghcjs-options: -dedupe
156 cpp-options: -DGHCJS_BROWSER -DGHCJS_GC_INTERVAL=60000
154 cpp-options: -DGHCJS_BROWSER -DGHCJS_GC_INTERVAL=60000
157 else
155 else
158 buildable: False
156 buildable: False
159− type: exitcode-stdio-1.0
157+ default-language: Haskell2010
160
158
161source-repository head
159source-repository head
162 type: git
160 type: git
reflex-dom/src/Reflex/Dom.hs+6−1
@@ -1,6 +1,11 @@
1+-- Disable haddocks on this module due to a bug on haddocks when selectively
2+-- reexporting on ghc > 8.2.
3+-- https://github.com/haskell/haddock/issues/979
4+{-# OPTIONS_HADDOCK hide, prune, ignore-exports #-}
1module Reflex.Dom (module X) where
5module Reflex.Dom (module X) where
2
6
3import Foreign.JavaScript.Orphans ()
7import Foreign.JavaScript.Orphans ()
4import Reflex.Dom.Core as X hiding (mainWidget, mainWidgetInElementById, mainWidgetWithCss,
8import Reflex.Dom.Core as X hiding (mainWidget, mainWidgetInElementById, mainWidgetWithCss,
5− mainWidgetWithHead, mainWidgetWithHead', runApp')
9+ mainWidgetWithHead, mainWidgetWithHead', runApp',
10+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
6import Reflex.Dom.Internal as X
11import Reflex.Dom.Internal as X
reflex-dom/src/Reflex/Dom/Internal.hs+37−12
@@ -1,21 +1,27 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2+{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE RankNTypes #-}
4{-# LANGUAGE RankNTypes #-}
4{-# LANGUAGE TypeFamilies #-}
5{-# LANGUAGE TypeFamilies #-}
5module Reflex.Dom.Internal
6module Reflex.Dom.Internal
6− (module Main, run, mainWidget, mainWidgetWithHead, mainWidgetWithCss,
7+ ( module Main
8+ , run
9+ , mainWidget
7− mainWidgetWithHead', mainWidgetInElementById, runApp') where
10+ , mainWidgetWithHead, mainWidgetWithCss, mainWidgetWithHead', mainWidgetInElementById, runApp'
11+ , mainHydrationWidgetWithHead, mainHydrationWidgetWithHead'
12+ ) where
8
13
9import Data.ByteString (ByteString)
14import Data.ByteString (ByteString)
10import Data.Text (Text)
15import Data.Text (Text)
11−import Reflex.Spider (Spider)
12import Reflex.Dom.Core (Widget)
16import Reflex.Dom.Core (Widget)
13import Reflex.Dom.Main as Main hiding
17import Reflex.Dom.Main as Main hiding
14 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
18 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
15− mainWidgetWithHead', mainWidgetInElementById, runApp')
19+ mainWidgetWithHead', mainWidgetInElementById, runApp',
20+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
16import qualified Reflex.Dom.Main as Main
21import qualified Reflex.Dom.Main as Main
17 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
22 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
18− mainWidgetWithHead', mainWidgetInElementById, runApp')
23+ mainWidgetWithHead', mainWidgetInElementById, runApp',
24+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
19
25
20#if defined(ghcjs_HOST_OS)
26#if defined(ghcjs_HOST_OS)
21run :: a -> a
27run :: a -> a
@@ -33,15 +39,26 @@ run jsm = do
33 putStrLn $ "Running jsaddle-warp server on port " <> show port
39 putStrLn $ "Running jsaddle-warp server on port " <> show port
34 JW.run port jsm
40 JW.run port jsm
35#elif defined(MIN_VERSION_jsaddle_wkwebview)
41#elif defined(MIN_VERSION_jsaddle_wkwebview)
36−import Language.Javascript.JSaddle.WKWebView (runFile)
42+#if defined(ios_HOST_OS)
43+import Data.Default
44+import Data.Monoid ((<>))
37import Language.Javascript.JSaddle (JSM)
45import Language.Javascript.JSaddle (JSM)
38−import Data.Default (def)
46+import Language.Javascript.JSaddle.WKWebView (run', mainBundleResourcePath)
47+import Language.Javascript.JSaddle.WKWebView.Internal (jsaddleMainHTMLWithBaseURL)
39
48
40−--TODO: Eliminate this; it is needed because otherwise the app's base
49+-- TODO: upstream to jsaddle-wkwebview
41−--URL will be set to "about:blank" due to jsaddleMain rather than
42−--jsaddleMainFile being used
43run :: JSM () -> IO ()
50run :: JSM () -> IO ()
44−run = runFile "index.html" "" def
51+run jsm = do
52+ let indexHtml = "<!DOCTYPE html><html><head></head><body></body></html>"
53+ baseUrl <- mainBundleResourcePath >>= \case
54+ Nothing -> do
55+ putStrLn "Reflex.Dom.run: unable to find main bundle resource path. Assets may not load properly."
56+ return ""
57+ Just p -> return $ "file://" <> p <> "/index.html"
58+ run' def $ jsaddleMainHTMLWithBaseURL indexHtml baseUrl jsm
59+#else
60+import Language.Javascript.JSaddle.WKWebView (run)
61+#endif
45#elif defined(ANDROID)
62#elif defined(ANDROID)
46import Android.HaskellActivity
63import Android.HaskellActivity
47import Control.Monad
64import Control.Monad
@@ -87,6 +104,14 @@ mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> IO ()
87mainWidgetInElementById eid w = run $ Main.mainWidgetInElementById eid w
104mainWidgetInElementById eid w = run $ Main.mainWidgetInElementById eid w
88{-# INLINE mainWidgetInElementById #-}
105{-# INLINE mainWidgetInElementById #-}
89
106
90−runApp' :: (t ~ Spider) => (forall x. AppInput t -> Widget x (AppOutput t)) -> IO ()
107+runApp' :: (forall x. AppInput DomTimeline -> Widget x (AppOutput DomTimeline)) -> IO ()
91runApp' app = run $ Main.runApp' app
108runApp' app = run $ Main.runApp' app
92{-# INLINE runApp' #-}
109{-# INLINE runApp' #-}
110+
111+mainHydrationWidgetWithHead :: (forall x. HydrationWidget x ()) -> (forall x. HydrationWidget x ()) -> IO ()
112+mainHydrationWidgetWithHead h b = run $ Main.mainHydrationWidgetWithHead h b
113+{-# INLINE mainHydrationWidgetWithHead #-}
114+
115+mainHydrationWidgetWithHead' :: HydrationWidget () () -> HydrationWidget () () -> IO ()
116+mainHydrationWidgetWithHead' h b = run $ Main.mainHydrationWidgetWithHead' h b
117+{-# INLINE mainHydrationWidgetWithHead' #-}
release.nixnew file+54
@@ -0,0 +1,54 @@
1+{ reflex-platform-fun ? import ./dep/reflex-platform
2+}:
3+
4+let
5+ native-reflex-platform = reflex-platform-fun {};
6+ inherit (native-reflex-platform.nixpkgs) lib;
7+ systems = ["x86_64-linux" "x86_64-darwin"];
8+
9+ perPlatform = lib.genAttrs systems (system: let
10+ reflex-platform = reflex-platform-fun { inherit system; };
11+ compilers = [
12+ "ghc"
13+ "ghcjs"
14+ ] ++ lib.optionals (reflex-platform.androidSupport) [
15+ "ghcAndroidAarch64"
16+ "ghcAndroidAarch32"
17+ ] ++ lib.optionals (reflex-platform.iosSupport) [
18+ "ghcIosAarch64"
19+ ];
20+ compilerPkgs = lib.genAttrs compilers (ghc: let
21+ reflex-platform = reflex-platform-fun {
22+ inherit system;
23+ haskellOverlays = [
24+ # Use this package's source for reflex
25+ (self: super: {
26+ _dep = super._dep // {
27+ reflex-dom = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [
28+ "release.nix"
29+ ".git"
30+ "dist"
31+ ])) ./.;
32+ };
33+ })
34+ ];
35+ };
36+ all = {
37+ inherit (reflex-platform.${ghc})
38+ reflex-dom-core
39+ reflex-dom
40+ ;
41+ };
42+ in all // {
43+ cache = reflex-platform.pinBuildInputs "reflex-${system}-${ghc}"
44+ (builtins.attrValues all);
45+ });
46+ in compilerPkgs // {
47+ cache = reflex-platform.pinBuildInputs "reflex-${system}"
48+ (map (a: a.cache) (builtins.attrValues compilerPkgs));
49+ });
50+
51+ metaCache = native-reflex-platform.pinBuildInputs "reflex-dom-everywhere"
52+ (map (a: a.cache) (builtins.attrValues perPlatform));
53+
54+in perPlatform // { inherit metaCache; }