on develop help sign in
Merge branch 'develop' into haddomevent-fundep
commitbaeaa7ff1e6bde9908dcc2a3fab658420b25b827 authorJohn Ericson <John.Ericson@Obsidian.Systems> dateSat 14 Dec 2019, 15:48 (7 years ago) parent2cb9c62df46ac9
49 files changed+4557−1263
CONTRIBUTING.md+65 FAQ.md+31 Quickref.md+1−1 README.md+11−5 chrome-test-utils/LICENSE+12 chrome-test-utils/chrome-test-utils.cabal+30 chrome-test-utils/src/Test/Util/ChromeFlags.hs+17 chrome-test-utils/src/Test/Util/UnshareNetwork.hs+12 default.nix−6 dep/reflex-platform/default.nix+7 dep/reflex-platform/github.json+7 reflex-dom-core/ChangeLog.md+42 reflex-dom-core/default.nix−43 reflex-dom-core/reflex-dom-core.cabal+84−22 reflex-dom-core/src/Foreign/JavaScript/Orphans.hs+6−3 reflex-dom-core/src/Foreign/JavaScript/TH.hs+10−7 reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+90−14 reflex-dom-core/src/Reflex/Dom/Builder/Class/Events.hs−1 reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hs+116 reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+1651−583 reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs+6 reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+79−6 reflex-dom-core/src/Reflex/Dom/Core.hs+6−1 reflex-dom-core/src/Reflex/Dom/Location.hs+39 reflex-dom-core/src/Reflex/Dom/Main.hs+167−36 reflex-dom-core/src/Reflex/Dom/Modals/Base.hs−199 reflex-dom-core/src/Reflex/Dom/Old.hs−2 reflex-dom-core/src/Reflex/Dom/Prerender.hs+290−49 reflex-dom-core/src/Reflex/Dom/Specializations.hs−62 reflex-dom-core/src/Reflex/Dom/WebSocket.hs+14−5 reflex-dom-core/src/Reflex/Dom/WebSocket/Foreign.hs+37−23 reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+55−44 reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+8−48 reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs+3−3 reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs+5−1 reflex-dom-core/src/Reflex/Dom/Xhr.hs+2−2 reflex-dom-core/src/Reflex/Dom/Xhr/Foreign.hs−1 reflex-dom-core/src/Reflex/Dom/Xhr/FormData.hs+47 reflex-dom-core/test/gc.hs+22−13 reflex-dom-core/test/hlint.hs+2 reflex-dom-core/test/hydration.hs+1416 reflex-dom/ChangeLog.md+18 reflex-dom/benchmarks/krausest.hs+7−7 reflex-dom/default.nix−35 reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+21−2 reflex-dom/reflex-dom.cabal+24−26 reflex-dom/src/Reflex/Dom.hs+6−1 reflex-dom/src/Reflex/Dom/Internal.hs+37−12 release.nix+54
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
2828-- This version returns the 'El' as well.
2929[W] el' :: Text -> m a -> m (El, a)
3030
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'
3232[W] elAttr :: Text -> Map Text Text -> m a -> m a
3333[W] elAttr' :: Text -> Map Text Text -> m a -> m (El, a)
3434
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+
25### A Framework for Functional Reactive Web Apps in Haskell
36
47Reflex-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).
58
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−
89A summary of Reflex-DOM functions is available in the [quick reference](Quickref.md).
910
11+**Visit https://reflex-frp.org/ for more information, tutorials, documentation and [examples](https://examples.reflex-frp.org/).**
12+
1013### Additional resources
1114[Get started with Reflex](https://github.com/reflex-frp/reflex-platform)
1215
1316[/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
1417
15−[hackage](https://hackage.haskell.org/package/reflex)
16−
1718[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 @@
11Name: reflex-dom-core
2−Version: 0.4
2+Version: 0.5.2.0
33Synopsis: Functional Reactive Web Apps with Reflex
44Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
55License: BSD3
@@ -17,6 +17,7 @@ extra-source-files: src-ghc/Foreign/JavaScript/Internal/Utils.hs
1717 src/Reflex/Dom/WebSocket/Foreign.hs
1818 src/Reflex/Dom/Xhr/ResponseType.hs
1919 src/Reflex/Dom/Xhr/Exception.hs
20+ ChangeLog.md
2021
2122flag use-template-haskell
2223 description: Use template haskell to generate lenses
@@ -38,22 +39,27 @@ flag profile-reflex
3839 default: False
3940 manual: True
4041
42+flag split-these
43+ description: Use split these/semialign packages
44+ manual: False
45+ default: True
46+
4147library
4248 hs-source-dirs: src
4349 build-depends:
44− aeson >= 0.8 && < 1.3,
45− base >= 4.7 && < 4.11,
50+ aeson >= 0.8 && < 1.5,
51+ base >= 4.7 && < 4.13,
4652 bifunctors >= 4.2 && < 6,
4753 bimap >= 0.3 && < 0.4,
4854 blaze-builder,
4955 bytestring == 0.10.*,
50− containers == 0.5.*,
51− constraints == 0.9.*,
52− contravariant >= 1.4 && < 1.5,
56+ containers >= 0.6 && < 0.7,
57+ constraints >= 0.9 && < 0.12,
58+ contravariant >= 1.4 && < 1.6,
5359 data-default >= 0.5 && < 0.8,
54− dependent-map == 0.2.*,
55− dependent-sum >= 0.3 && < 0.5,
56− dependent-sum-template >= 0.0.0.4 && < 0.1,
60+ dependent-map >= 0.3 && < 0.4,
61+ dependent-sum >= 0.6 && < 0.7,
62+ dependent-sum-template >= 0.1 && < 0.2,
5763 directory >= 1.2 && < 1.4,
5864 exception-transformers == 0.4.*,
5965 ghcjs-dom >= 0.9.1.0 && < 0.10,
@@ -64,12 +70,12 @@ library
6470 monad-control >= 1.0.1 && < 1.1,
6571 mtl >= 2.1 && < 2.3,
6672 primitive >= 0.5 && < 0.7,
73+ random,
6774 ref-tf == 0.4.*,
68− reflex == 0.5.*,
75+ reflex >= 0.6.2,
6976 semigroups >= 0.16 && < 0.19,
70− stm == 2.4.*,
77+ stm >= 2.4 && < 2.6,
7178 text == 1.2.*,
72− these >= 0.4 && < 0.8,
7379 transformers >= 0.3 && < 0.6,
7480 network-uri >= 2.6.1 && < 2.7,
7581 zenc == 0.1.*
@@ -84,6 +90,14 @@ library
8490 if !os(windows)
8591 build-depends: unix == 2.7.*
8692
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+
87101 exposed-modules:
88102 Foreign.JavaScript.TH
89103 Foreign.JavaScript.Orphans
@@ -92,16 +106,15 @@ library
92106 Reflex.Dom.Builder.Class.Events
93107 Reflex.Dom.Builder.Immediate
94108 Reflex.Dom.Builder.InputDisabled
109+ Reflex.Dom.Builder.Hydratable
95110 Reflex.Dom.Builder.Static
96111 Reflex.Dom.Class
97112 Reflex.Dom.Core
98113 Reflex.Dom.Location
99114 Reflex.Dom.Main
100− Reflex.Dom.Modals.Base
101115 Reflex.Dom.Modals.Class
102116 Reflex.Dom.Old
103117 Reflex.Dom.Prerender
104− Reflex.Dom.Specializations
105118 Reflex.Dom.Time
106119 Reflex.Dom.WebSocket
107120 Reflex.Dom.WebSocket.Query
@@ -111,6 +124,7 @@ library
111124 Reflex.Dom.Widget.Lazy
112125 Reflex.Dom.Widget.Resize
113126 Reflex.Dom.Xhr
127+ Reflex.Dom.Xhr.FormData
114128 other-modules:
115129 Foreign.JavaScript.Internal.Utils
116130 Reflex.Dom.WebSocket.Foreign
@@ -118,7 +132,7 @@ library
118132 Reflex.Dom.Xhr.ResponseType
119133 Reflex.Dom.Xhr.Exception
120134
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
122136 ghc-prof-options: -fprof-auto
123137
124138 if flag(expose-all-unfoldings)
@@ -132,8 +146,8 @@ library
132146
133147 if flag(use-template-haskell)
134148 build-depends:
135− dependent-sum >= 0.3 && < 0.5,
136− dependent-sum-template >= 0.0.0.5 && < 0.1,
149+ dependent-sum >= 0.6,
150+ dependent-sum-template >= 0.1 && < 0.2,
137151 template-haskell
138152 other-extensions: TemplateHaskell
139153 cpp-options: -DUSE_TEMPLATE_HASKELL
@@ -141,7 +155,7 @@ library
141155 Reflex.Dom.Builder.Class.TH
142156 else
143157 build-depends:
144− dependent-sum == 0.4.*
158+ dependent-sum == 0.6.*
145159
146160test-suite hlint
147161 build-depends: base, hlint
@@ -149,16 +163,64 @@ test-suite hlint
149163 main-is: hlint.hs
150164 type: exitcode-stdio-1.0
151165
152−test-suite gc
166+test-suite hydration
153167 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
154193 , reflex
155194 , 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
156218 , jsaddle
157219 , jsaddle-warp
158− , linux-namespaces
159220 , process
160− , temporary
161− , unix
221+ , reflex
222+ , reflex-dom-core
223+ , text
162224 hs-source-dirs: test
163225 ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
164226 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 #-}
12{-# LANGUAGE CPP #-}
23{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE UndecidableInstances #-}
5+
36{-# OPTIONS_GHC -fno-warn-orphans #-}
47module Foreign.JavaScript.Orphans where
58
@@ -7,8 +10,8 @@ module Foreign.JavaScript.Orphans where
710
811import Control.Monad.Trans.Class (lift)
912import GHCJS.DOM.Types (MonadJSM (..))
10−import Reflex.DynamicWriter (DynamicWriterT)
11−import Reflex.EventWriter (EventWriterT)
13+import Reflex.DynamicWriter.Base (DynamicWriterT)
14+import Reflex.EventWriter.Base (EventWriterT)
1215import Reflex.Host.Class (HostFrame, ReflexHost)
1316import Reflex.PerformEvent.Base (PerformEventT (..))
1417import Reflex.PostBuild.Base (PostBuildT)
@@ -16,7 +19,7 @@ import Reflex.Requester.Base (RequesterT)
1619import Reflex.TriggerEvent.Base
1720import Reflex.Query.Base (QueryT)
1821
19−instance (MonadJSM m, ReflexHost t) => MonadJSM (PostBuildT t m) where
22+instance MonadJSM m => MonadJSM (PostBuildT t m) where
2023 liftJSM' = lift . liftJSM'
2124instance (MonadJSM (HostFrame t), ReflexHost t) => MonadJSM (PerformEventT t m) where
2225 liftJSM' = PerformEventT . lift . liftJSM'
reflex-dom-core/src/Foreign/JavaScript/TH.hs+10−7
@@ -6,11 +6,9 @@
66{-# LANGUAGE FlexibleInstances #-}
77{-# LANGUAGE FunctionalDependencies #-}
88{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9−{-# LANGUAGE MultiParamTypeClasses #-}
109{-# LANGUAGE RankNTypes #-}
1110{-# LANGUAGE StandaloneDeriving #-}
1211{-# LANGUAGE TypeFamilies #-}
13−{-# LANGUAGE TypeSynonymInstances #-}
1412{-# LANGUAGE UndecidableInstances #-}
1513#ifdef USE_TEMPLATE_HASKELL
1614{-# LANGUAGE TemplateHaskell #-}
@@ -28,8 +26,9 @@ module Foreign.JavaScript.TH ( module Foreign.JavaScript.TH
2826import Foreign.JavaScript.Orphans ()
2927import Prelude hiding ((!!))
3028import Reflex.Class
31−import Reflex.DynamicWriter
32−import Reflex.EventWriter
29+import Reflex.Adjustable.Class
30+import Reflex.DynamicWriter.Base
31+import Reflex.EventWriter.Base
3332import Reflex.Host.Class
3433import Reflex.PerformEvent.Base
3534import Reflex.PerformEvent.Class
@@ -242,7 +241,7 @@ instance HasJS x m => HasJS x (ReaderT r m) where
242241 type JSX (ReaderT r m) = JSX m
243242 liftJS = lift . liftJS
244243
245−instance (HasJS x m, ReflexHost t) => HasJS x (PostBuildT t m) where
244+instance HasJS x m => HasJS x (PostBuildT t m) where
246245 type JSX (PostBuildT t m) = JSX m
247246 liftJS = lift . liftJS
248247
@@ -254,6 +253,10 @@ instance HasJS x m => HasJS x (DynamicWriterT t w m) where
254253 type JSX (DynamicWriterT t w m) = JSX m
255254 liftJS = lift . liftJS
256255
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+
257260instance HasJS x m => HasJS x (RequesterT t request response m) where
258261 type JSX (RequesterT t request response m) = JSX m
259262 liftJS = lift . liftJS
@@ -289,7 +292,7 @@ class Monad m => MonadJS x m | m -> x where
289292#ifdef ghcjs_HOST_OS
290293
291294data JSCtx_IO
292−type HasJS' = HasJS JSCtx_IO
295+type JS' = JSCtx_IO
293296
294297instance MonadIO m => HasJS JSCtx_IO (WithJSContextSingleton x m) where
295298 type JSX (WithJSContextSingleton x m) = IO
@@ -340,7 +343,7 @@ foreign import javascript unsafe "function(){ return $1(arguments); }" funWithAr
340343#else
341344
342345data JSCtx_JavaScriptCore x
343−type HasJS' = HasJS (JSCtx_JavaScriptCore ())
346+type JS' = JSCtx_JavaScriptCore ()
344347
345348instance IsJSContext (JSCtx_JavaScriptCore x) where
346349 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 @@
1111{-# LANGUAGE ImpredicativeTypes #-}
1212#endif
1313{-# LANGUAGE LambdaCase #-}
14−{-# LANGUAGE MultiParamTypeClasses #-}
1514{-# LANGUAGE OverloadedStrings #-}
1615{-# LANGUAGE PolyKinds #-}
17−{-# LANGUAGE RankNTypes #-}
16+{-# LANGUAGE Rank2Types #-}
1817{-# LANGUAGE RecursiveDo #-}
1918{-# LANGUAGE ScopedTypeVariables #-}
2019{-# LANGUAGE StandaloneDeriving #-}
@@ -29,13 +28,15 @@ module Reflex.Dom.Builder.Class
2928 , module Reflex.NotReady.Class
3029 ) where
3130
31+import Reflex.Adjustable.Class
3232import Reflex.Class as Reflex
3333import Reflex.Dom.Builder.Class.Events
3434#ifdef USE_TEMPLATE_HASKELL
3535import Reflex.Dom.Builder.Class.TH
3636#endif
37−import Reflex.DynamicWriter
38−import Reflex.EventWriter
37+import Reflex.BehaviorWriter.Base
38+import Reflex.DynamicWriter.Base
39+import Reflex.EventWriter.Base
3940import Reflex.NotReady.Class
4041import Reflex.PerformEvent.Class
4142import Reflex.PostBuild.Base
@@ -46,6 +47,7 @@ import Reflex.Requester.Base
4647import qualified Control.Category
4748import Control.Lens hiding (element)
4849import Control.Monad.Reader
50+import qualified Control.Monad.State as Lazy
4951import Control.Monad.State.Strict
5052import Control.Monad.Trans.Control
5153import Data.Default
@@ -59,12 +61,14 @@ import Data.String
5961import Data.Text (Text)
6062import Data.Type.Coercion
6163import GHCJS.DOM.Types (JSM)
64+import qualified GHCJS.DOM.Types as DOM
6265
6366class Default (EventSpec d EventResult) => DomSpace d where
6467 type EventSpec d :: (EventTag -> *) -> *
68+ type RawDocument d :: *
6569 type RawTextNode d :: *
70+ type RawCommentNode d :: *
6671 type RawElement d :: *
67− type RawFile d :: *
6872 type RawInputElement d :: *
6973 type RawTextAreaElement d :: *
7074 type RawSelectElement d :: *
@@ -83,6 +87,15 @@ class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), NotReady t m, Adjustable
8387 => TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
8488 textNode = lift . textNode
8589 {-# 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 #-}
8699 element :: Text -> ElementConfig er t (DomBuilderSpace m) -> m a -> m (Element er (DomBuilderSpace m) t, a)
87100 default element :: ( MonadTransControl f
88101 , StT f a ~ a
@@ -172,6 +185,28 @@ newtype TextNode d t = TextNode
172185 { _textNode_raw :: RawTextNode d
173186 }
174187
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+
175210data AttributeName = AttributeName !(Maybe Namespace) !Text deriving (Show, Read, Eq, Ord)
176211
177212mapKeysToAttributeName :: Map Text v -> Map AttributeName v
@@ -288,7 +323,7 @@ data InputElement er d t
288323 , _inputElement_hasFocus :: Dynamic t Bool
289324 , _inputElement_element :: Element er d t
290325 , _inputElement_raw :: RawInputElement d
291− , _inputElement_files :: Dynamic t [RawFile d]
326+ , _inputElement_files :: Dynamic t [DOM.File]
292327 }
293328
294329data TextAreaElementConfig er t m
@@ -390,6 +425,7 @@ data SelectElement er d t = SelectElement
390425#ifdef USE_TEMPLATE_HASKELL
391426concat <$> mapM (uncurry makeLensesWithoutField)
392427 [ (["_textNodeConfig_setContents"], ''TextNodeConfig)
428+ , (["_commentNodeConfig_setContents"], ''CommentNodeConfig)
393429 , ([ "_inputElementConfig_setValue"
394430 , "_inputElementConfig_setChecked" ], ''InputElementConfig)
395431 , (["_rawElementConfig_modifyAttributes"], ''RawElementConfig)
@@ -400,49 +436,56 @@ concat <$> mapM (uncurry makeLensesWithoutField)
400436#endif
401437
402438-- | 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)
404440textNodeConfig_setContents =
405441 let getter = fromMaybe never . _textNodeConfig_setContents
406442 setter t e = t { _textNodeConfig_setContents = Just e }
407443 in lens getter setter
408444
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+
409452-- | 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)
411454inputElementConfig_setValue =
412455 let getter = fromMaybe never . _inputElementConfig_setValue
413456 setter t e = t { _inputElementConfig_setValue = Just e }
414457 in lens getter setter
415458
416459-- | 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)
418461inputElementConfig_setChecked =
419462 let getter = fromMaybe never . _inputElementConfig_setChecked
420463 setter t e = t { _inputElementConfig_setChecked = Just e }
421464 in lens getter setter
422465
423466-- | 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)))
425468rawElementConfig_modifyAttributes =
426469 let getter = fromMaybe never . _rawElementConfig_modifyAttributes
427470 setter t e = t { _rawElementConfig_modifyAttributes = Just e }
428471 in lens getter setter
429472
430473-- | 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)))
432475elementConfig_modifyAttributes =
433476 let getter = fromMaybe never . _elementConfig_modifyAttributes
434477 setter t e = t { _elementConfig_modifyAttributes = Just e }
435478 in lens getter setter
436479
437480-- | 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)
439482textAreaElementConfig_setValue =
440483 let getter = fromMaybe never . _textAreaElementConfig_setValue
441484 setter t e = t { _textAreaElementConfig_setValue = Just e }
442485 in lens getter setter
443486
444487-- | 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)
446489selectElementConfig_setValue =
447490 let getter = fromMaybe never . _selectElementConfig_setValue
448491 setter t e = t { _selectElementConfig_setValue = Just e }
@@ -518,6 +561,7 @@ instance (MountableDomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m)
518561instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
519562 type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m
520563 textNode = liftTextNode
564+ commentNode = liftCommentNode
521565 element elementTag cfg (DynamicWriterT child) = DynamicWriterT $ do
522566 s <- get
523567 (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 (
536580instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT t request response m) where
537581 type DomBuilderSpace (RequesterT t request response m) = DomBuilderSpace m
538582 textNode = liftTextNode
583+ commentNode = liftCommentNode
539584 element elementTag cfg (RequesterT child) = RequesterT $ do
540585 r <- ask
541586 old <- get
@@ -556,6 +601,7 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT
556601instance (DomBuilder t m, MonadHold t m, MonadFix m, Semigroup w) => DomBuilder t (EventWriterT t w m) where
557602 type DomBuilderSpace (EventWriterT t w m) = DomBuilderSpace m
558603 textNode = liftTextNode
604+ commentNode = liftCommentNode
559605 element elementTag cfg (EventWriterT child) = EventWriterT $ do
560606 old <- get
561607 (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
571617 placeRawElement = lift . placeRawElement
572618 wrapRawElement e = lift . wrapRawElement e
573619
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
575621 type DomBuilderSpace (QueryT t q m) = DomBuilderSpace m
576622 textNode = liftTextNode
623+ commentNode = liftCommentNode
577624 element elementTag cfg (QueryT child) = QueryT $ do
578625 s <- get
579626 (e, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
@@ -642,6 +689,9 @@ liftWithStateless a = liftWith $ \run -> a $ fmap (fromStT (Proxy :: Proxy t)) .
642689liftTextNode :: (MonadTrans f, DomBuilder t m) => TextNodeConfig t -> f m (TextNode (DomBuilderSpace m) t)
643690liftTextNode = lift . textNode
644691
692+liftCommentNode :: (MonadTrans f, DomBuilder t m) => CommentNodeConfig t -> f m (CommentNode (DomBuilderSpace m) t)
693+liftCommentNode = lift . commentNode
694+
645695liftElement :: LiftDomBuilder t f m => Text -> ElementConfig er t (DomBuilderSpace m) -> f m a -> f m (Element er (DomBuilderSpace m) t, a)
646696liftElement elementTag cfg child = liftWithStateless $ \run -> element elementTag cfg $ run child
647697
@@ -660,7 +710,14 @@ instance DomRenderHook t m => DomRenderHook t (StateT e m) where
660710 requestDomAction = lift . requestDomAction
661711 requestDomAction_ = lift . requestDomAction_
662712
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)
663719deriving instance DomRenderHook t m => DomRenderHook t (EventWriterT t w m)
720+deriving instance DomRenderHook t m => DomRenderHook t (DynamicWriterT t w m)
664721deriving instance DomRenderHook t m => DomRenderHook t (RequesterT t req rsp m)
665722deriving instance DomRenderHook t m => DomRenderHook t (PostBuildT t m)
666723deriving 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)
668725{-# DEPRECATED liftElementConfig "Use 'id' instead; this function is no longer necessary" #-}
669726liftElementConfig :: ElementConfig er t s -> ElementConfig er t s
670727liftElementConfig = 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 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE DataKinds #-}
33{-# LANGUAGE GADTs #-}
4−{-# LANGUAGE KindSignatures #-}
54{-# LANGUAGE PolyKinds #-}
65#ifdef USE_TEMPLATE_HASKELL
76{-# 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 @@
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE ConstraintKinds #-}
44{-# LANGUAGE DataKinds #-}
5−{-# LANGUAGE DefaultSignatures #-}
5+{-# LANGUAGE DeriveFunctor #-}
6+{-# LANGUAGE DeriveTraversable #-}
67{-# LANGUAGE EmptyDataDecls #-}
78{-# LANGUAGE FlexibleContexts #-}
89{-# LANGUAGE FlexibleInstances #-}
@@ -11,14 +12,15 @@
1112{-# LANGUAGE LambdaCase #-}
1213{-# LANGUAGE MultiParamTypeClasses #-}
1314{-# LANGUAGE OverloadedStrings #-}
15+{-# LANGUAGE PatternGuards #-}
1416{-# LANGUAGE PolyKinds #-}
1517{-# LANGUAGE RankNTypes #-}
1618{-# LANGUAGE RecursiveDo #-}
1719{-# LANGUAGE ScopedTypeVariables #-}
18−{-# LANGUAGE StandaloneDeriving #-}
1920#ifdef USE_TEMPLATE_HASKELL
2021{-# LANGUAGE TemplateHaskell #-}
2122#endif
23+{-# LANGUAGE TupleSections #-}
2224{-# LANGUAGE TypeFamilies #-}
2325{-# LANGUAGE TypeOperators #-}
2426{-# LANGUAGE UndecidableInstances #-}
@@ -26,191 +28,346 @@
2628{-# LANGUAGE ForeignFunctionInterface #-}
2729{-# LANGUAGE JavaScriptFFI #-}
2830#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.
2946module Reflex.Dom.Builder.Immediate
30− ( EventTriggerRef (..)
31− , ImmediateDomBuilderEnv (..)
32− , ImmediateDomBuilderT (..)
33− , runImmediateDomBuilderT
34− , askParent
35− , askEvents
36− , append
37− , textNodeInternal
38− , deleteBetweenExclusive
39− , extractBetweenExclusive
40− , deleteUpTo
41− , extractUpTo
42− , SupportsImmediateDomBuilder
43− , collectUpTo
44− , collectUpToGivenParent
45− , EventFilterTriggerRef (..)
46− , wrap
47− , makeElement
48− , GhcjsDomHandler (..)
49− , GhcjsDomHandler1 (..)
50− , GhcjsDomEvent (..)
51− , GhcjsDomSpace
52− , GhcjsEventFilter (..)
53− , Pair1 (..)
54− , Maybe1 (..)
55− , GhcjsEventSpec (..)
56− , HasDocument (..)
57− , ghcjsEventSpec_filters
58− , ghcjsEventSpec_handler
59− , GhcjsEventHandler (..)
60−#ifndef USE_TEMPLATE_HASKELL
61− , phantom2
62−#endif
63− , drawChildUpdate
64− , ChildReadyState (..)
65− , ChildReadyStateInt (..)
66− , mkHasFocus
67− , insertBefore
68− , EventType
69− , defaultDomEventHandler
70− , defaultDomWindowEventHandler
71− , withIsEvent
72− , showEventName
73− , elementOnEventName
74− , windowOnEventName
75− , wrapDomEvent
76− , subscribeDomEvent
77− , wrapDomEventMaybe
78− , wrapDomEventsMaybe
79− , getKeyEvent
80− , getMouseEventCoords
81− , getTouchEvent
82− , WindowConfig (..)
83− , Window (..)
84− , wrapWindow
85− -- * Internal
86− , traverseDMapWithKeyWithAdjust'
87− , hoistTraverseWithKeyWithAdjust
88− , traverseIntMapWithKeyWithAdjust'
89− , hoistTraverseIntMapWithKeyWithAdjust
90− ) 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)
47+ ( HydrationDomBuilderT (..)
48+ , HydrationDomBuilderEnv (..)
49+ , HydrationMode (..)
50+ , HydrationRunnerT (..)
51+ , runHydrationRunnerT
52+ , ImmediateDomBuilderT
53+ , runHydrationDomBuilderT
54+ , getHydrationMode
55+ , addHydrationStep
56+ , addHydrationStepWithSetup
57+ , setPreviousNode
58+ , insertAfterPreviousNode
59+ , hydrateComment
60+ , askParent
61+ , askEvents
62+ , append
63+ , textNodeInternal
64+ , removeSubsequentNodes
65+ , deleteBetweenExclusive
66+ , extractBetweenExclusive
67+ , deleteUpTo
68+ , extractUpTo
69+ , SupportsHydrationDomBuilder
70+ , collectUpTo
71+ , collectUpToGivenParent
72+ , EventTriggerRef (..)
73+ , EventFilterTriggerRef (..)
74+ , wrap
75+ , elementInternal
76+ , HydrationDomSpace
77+ , GhcjsDomSpace
78+ , GhcjsDomHandler (..)
79+ , GhcjsDomHandler1 (..)
80+ , GhcjsDomEvent (..)
81+ , GhcjsEventFilter (..)
82+ , Pair1 (..)
83+ , Maybe1 (..)
84+ , GhcjsEventSpec (..)
85+ , HasDocument (..)
86+ , ghcjsEventSpec_filters
87+ , ghcjsEventSpec_handler
88+ , GhcjsEventHandler (..)
89+ , drawChildUpdate
90+ , ChildReadyState (..)
91+ , mkHasFocus
92+ , insertBefore
93+ , EventType
94+ , defaultDomEventHandler
95+ , defaultDomWindowEventHandler
96+ , withIsEvent
97+ , showEventName
98+ , elementOnEventName
99+ , windowOnEventName
100+ , wrapDomEvent
101+ , subscribeDomEvent
102+ , wrapDomEventMaybe
103+ , wrapDomEventsMaybe
104+ , getKeyEvent
105+ , getMouseEventCoords
106+ , getTouchEvent
107+ , WindowConfig (..)
108+ , Window (..)
109+ , wrapWindow
110+ -- * Internal
111+ , traverseDMapWithKeyWithAdjust'
112+ , hoistTraverseWithKeyWithAdjust
113+ , traverseIntMapWithKeyWithAdjust'
114+ , hoistTraverseIntMapWithKeyWithAdjust
115+ ) where
108116
109117import Control.Concurrent
110−import Control.Lens hiding (element, ix)
118+import Control.Exception (bracketOnError)
119+import Control.Lens (Identity(..), imapM_, iforM_, (^.), makeLenses)
111120import Control.Monad.Exception
112121import Control.Monad.Primitive
113122import Control.Monad.Reader
114123import Control.Monad.Ref
115−import Control.Monad.State.Strict (StateT)
116−import qualified Control.Monad.State as Lazy (StateT)
117−#ifndef USE_TEMPLATE_HASKELL
118−import Data.Functor.Contravariant (phantom)
119−#endif
124+import Control.Monad.State.Strict (StateT, mapStateT, get, modify', gets, runStateT)
120125import Data.Bitraversable
121126import Data.Default
122127import Data.Dependent.Map (DMap)
123−import qualified Data.Dependent.Map as DMap
124128import Data.Dependent.Sum
129+import Data.FastMutableIntMap (PatchIntMap (..))
130+import Data.Foldable (for_, traverse_)
125131import Data.Functor.Compose
126132import Data.Functor.Constant
127133import Data.Functor.Misc
128134import Data.Functor.Product
129135import Data.IORef
130−import qualified Data.Map as Map
136+import Data.IntMap.Strict (IntMap)
131137import Data.Maybe
132−import Data.Monoid hiding (Product)
133−import Data.Some (Some)
134−import qualified Data.Some as Some
138+import Data.Monoid ((<>))
139+import Data.Some (Some(..))
135140import 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
136176import qualified Data.Text as T
137177import qualified GHCJS.DOM as DOM
138−import GHCJS.DOM.RequestAnimationFrameCallback
139−import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode)
140−import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
178+import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
179+import qualified GHCJS.DOM.DocumentOrShadowRoot as Document
141180import qualified GHCJS.DOM.Element as Element
142181import 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)
146182import qualified GHCJS.DOM.EventM as DOM
147183import qualified GHCJS.DOM.FileList as FileList
184+import qualified GHCJS.DOM.GlobalEventHandlers as Events
148185import qualified GHCJS.DOM.HTMLInputElement as Input
149186import qualified GHCJS.DOM.HTMLSelectElement as Select
150187import 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
152190import qualified GHCJS.DOM.Touch as Touch
153191import qualified GHCJS.DOM.TouchEvent as TouchEvent
154192import 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)
161193import qualified GHCJS.DOM.Types as DOM
162−import GHCJS.DOM.UIEvent
163−import GHCJS.DOM.KeyboardEvent as KeyboardEvent
164194import qualified GHCJS.DOM.Window as Window
165−import Language.Javascript.JSaddle (call, eval)
166−import Data.IntMap.Strict (IntMap)
167−import qualified Data.IntMap.Strict as IntMap
168−import Data.FastMutableIntMap (PatchIntMap (..))
169−import qualified Data.FastMutableIntMap as FastMutableIntMap
195+import qualified Reflex.Patch.DMap as PatchDMap
196+import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
197+import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
198+import qualified Reflex.TriggerEvent.Base as TriggerEventT (askEvents)
170199
171−import Reflex.Requester.Base
172−import Reflex.Requester.Class
173−import Foreign.JavaScript.Internal.Utils
200+#ifndef USE_TEMPLATE_HASKELL
201+import Data.Functor.Contravariant (phantom)
202+import Control.Lens (Lens', Getter)
203+#endif
174204
175205#ifndef ghcjs_HOST_OS
176206import GHCJS.DOM.Types (MonadJSM (..))
177207
178−instance MonadJSM m => MonadJSM (ImmediateDomBuilderT t m) where
179− liftJSM' = ImmediateDomBuilderT . liftJSM'
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' #-}
218+ liftJSM' = lift . liftJSM'
180219#endif
181220
182−data ImmediateDomBuilderEnv t
183− = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
184− , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
185− , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
186− , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
187− }
221+data HydrationDomBuilderEnv t m = HydrationDomBuilderEnv
222+ { _hydrationDomBuilderEnv_document :: {-# UNPACK #-} !Document
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)
228+ -- ^ Number of children who still aren't fully rendered
229+ , _hydrationDomBuilderEnv_commitAction :: !(JSM ())
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 ()))
235+ }
188236
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 }
190243 deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
191244#if MIN_VERSION_base(4,9,1)
192245 , MonadAsyncException
193246#endif
194247 )
195248
196−instance PrimMonad m => PrimMonad (ImmediateDomBuilderT x m) where
197− type PrimState (ImmediateDomBuilderT x m) = PrimState m
249+instance PrimMonad m => PrimMonad (HydrationDomBuilderT s t m) where
250+ type PrimState (HydrationDomBuilderT s t m) = PrimState m
198251 primitive = lift . primitive
199252
200−instance MonadTrans (ImmediateDomBuilderT t) where
201− lift = ImmediateDomBuilderT . lift . lift . lift
253+instance MonadTrans (HydrationDomBuilderT s t) where
254+ lift = HydrationDomBuilderT . lift . lift
202255
203−instance (Reflex t, MonadFix m) => DomRenderHook t (ImmediateDomBuilderT t m) where
204− withRenderHook hook (ImmediateDomBuilderT a) = do
205− e <- ImmediateDomBuilderT ask
206− ImmediateDomBuilderT $ lift $ withRequesting $ \rsp -> do
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_
256+instance (Reflex t, MonadFix m) => DomRenderHook t (HydrationDomBuilderT s t m) where
257+ withRenderHook hook = HydrationDomBuilderT . mapReaderT (withRenderHook hook) . unHydrationDomBuilderT
258+ requestDomAction = HydrationDomBuilderT . lift . requestDomAction
259+ requestDomAction_ = HydrationDomBuilderT . lift . requestDomAction_
211260
212−{-# INLINABLE runImmediateDomBuilderT #-}
213−runImmediateDomBuilderT
261+-- | The monad which performs the delayed actions to reuse prerendered nodes and set up events.
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
214371 :: ( MonadFix m
215372 , PerformEvent t m
216373 , MonadReflexCreateTrigger t m
@@ -219,64 +376,76 @@ runImmediateDomBuilderT
219376 , MonadRef m
220377 , Ref m ~ IORef
221378 )
222− => ImmediateDomBuilderT t m a
223− -> ImmediateDomBuilderEnv t
379+ => HydrationDomBuilderT s t m a
380+ -> HydrationDomBuilderEnv t m
224381 -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
225382 -> m a
226−runImmediateDomBuilderT (ImmediateDomBuilderT a) env eventChan =
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
383+runHydrationDomBuilderT (HydrationDomBuilderT a) env = runDomRenderHookT (runReaderT a env)
236384
237−class Monad m => HasDocument 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
385+instance (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, Monad m) => HasDocument (HydrationDomBuilderT s t m) where
252386 {-# INLINABLE askDocument #-}
253− askDocument = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_document
387+ askDocument = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_document
254388
255389{-# INLINABLE askParent #-}
256−askParent :: Monad m => ImmediateDomBuilderT t m Node
257−askParent = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_parent
390+askParent :: Monad m => HydrationRunnerT t m DOM.Node
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)
258396
259397{-# INLINABLE askEvents #-}
260−askEvents :: Monad m => ImmediateDomBuilderT t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
261−askEvents = ImmediateDomBuilderT . lift . lift $ TriggerEventT.askEvents
398+askEvents :: Monad m => HydrationDomBuilderT s t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
399+askEvents = HydrationDomBuilderT . lift . DomRenderHookT . lift $ TriggerEventT.askEvents
262400
263−localEnv :: Monad m => (ImmediateDomBuilderEnv t -> ImmediateDomBuilderEnv t) -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m a
264−localEnv f = ImmediateDomBuilderT . local f . unImmediateDomBuilderT
401+{-# INLINABLE localEnv #-}
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
265404
266405{-# INLINABLE append #-}
267−append :: MonadJSM m => DOM.Node -> ImmediateDomBuilderT t m ()
406+append :: MonadJSM m => DOM.Node -> HydrationDomBuilderT s t m ()
268407append n = do
269− p <- askParent
408+ p <- getParent
270409 liftJSM $ appendChild_ p n
271410 return ()
272411
273−{-# INLINABLE textNodeInternal #-}
274−textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
275−textNodeInternal !t = do
276− doc <- askDocument
277− n <- liftJSM $ createTextNode doc t
278− append $ toNode n
279− return n
412+{-# SPECIALIZE append
413+ :: DOM.Node
414+ -> HydrationDomBuilderT s Spider HydrationM ()
415+ #-}
416+
417+data HydrationMode
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]
280449
281450-- | s and e must both be children of the same node and s must precede e;
282451-- 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
314483 void $ call f f (df, s, e)
315484#endif
316485
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))
318487
319488{-# INLINABLE collectUpTo #-}
320489collectUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m DOM.DocumentFragment
@@ -332,10 +501,15 @@ collectUpToGivenParent currentParent s e = do
332501
333502newtype EventFilterTriggerRef t er (en :: EventTag) = EventFilterTriggerRef (IORef (Maybe (EventTrigger t (er en))))
334503
335−{-# INLINABLE 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)
337−wrap e cfg = do
338− events <- askEvents
504+-- | This 'wrap' is only partial: it doesn't create the 'EventSelector' itself
505+{-# INLINE wrap #-}
506+wrap
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))
512+wrap events e cfg = do
339513 forM_ (_rawElementConfig_modifyAttributes cfg) $ \modifyAttrs -> requestDomAction_ $ ffor modifyAttrs $ imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
340514 Nothing -> maybe (removeAttribute e n) (setAttribute e n) mv
341515 Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv
@@ -352,46 +526,74 @@ wrap e cfg = do
352526 mv <- liftJSM k --TODO: Only do this when the event is subscribed
353527 liftIO $ forM_ mv $ \v -> writeChan events [EventTriggerRef triggerRef :=> TriggerInvocation v (return ())]
354528 return $ en :=> EventFilterTriggerRef triggerRef
355− es <- do
356− let h :: GhcjsEventHandler er
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
358− ctx <- askJSM
359− newFanEventWithTrigger $ \(WrapArg en) t ->
360− case DMap.lookup en eventTriggerRefs of
361− Just (EventFilterTriggerRef r) -> do
362− writeIORef r $ Just t
363− return $ do
364− writeIORef r Nothing
365− Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
366− evt <- DOM.event
367− mv <- lift $ unGhcjsEventHandler h (en, GhcjsDomEvent evt)
368− case mv of
369− Nothing -> return ()
370− 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
372− ref <- newIORef $ Just t
373− writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
374− return $ Element
375− { _element_events = es
376− , _element_raw = e
377− }
378−
379−{-# INLINABLE makeElement #-}
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)
381−makeElement elementTag cfg child = do
382− doc <- askDocument
383− e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
384− Nothing -> createElement doc elementTag
385− Just ens -> createElementNS doc (Just ens) elementTag
386− ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
387− Nothing -> lift $ setAttribute e n v
388− Just ans -> lift $ setAttributeNS e (Just ans) n v
389− result <- flip localEnv child $ \env -> env
390− { _immediateDomBuilderEnv_parent = toNode e
391− }
392− append $ toNode e
393− wrapped <- wrap e $ extractRawElementConfig cfg
394− return ((wrapped, result), e)
529+ return eventTriggerRefs
530+
531+{-# SPECIALIZE wrap
532+ :: Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
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 ())
556+triggerBody ctx cfg events eventTriggerRefs e (WrapArg en) t = case DMap.lookup en eventTriggerRefs of
557+ Just (EventFilterTriggerRef r) -> do
558+ writeIORef r $ Just t
559+ return $ do
560+ writeIORef r Nothing
561+ Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
562+ evt <- DOM.event
563+ mv <- lift $ unGhcjsEventHandler handler (en, GhcjsDomEvent evt)
564+ case mv of
565+ Nothing -> return ()
566+ Just v -> liftIO $ do
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
568+ ref <- newIORef $ Just t
569+ writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
570+ where
571+ -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't
572+ -- retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
573+ handler :: GhcjsEventHandler er
574+ !handler = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg
575+
576+{-# SPECIALIZE triggerBody
577+ :: DOM.JSContextRef
578+ -> RawElementConfig er DomTimeline HydrationDomSpace
579+ -> Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
580+ -> DMap EventName (EventFilterTriggerRef DomTimeline er)
581+ -> DOM.Element
582+ -> WrapArg er EventName x
583+ -> EventTrigger DomTimeline x
584+ -> IO (IO ())
585+ #-}
586+
587+{-# SPECIALIZE triggerBody
588+ :: DOM.JSContextRef
589+ -> RawElementConfig er DomTimeline GhcjsDomSpace
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+ #-}
395597
396598newtype GhcjsDomHandler a b = GhcjsDomHandler { unGhcjsDomHandler :: a -> JSM b }
397599
@@ -403,9 +605,10 @@ data GhcjsDomSpace
403605
404606instance DomSpace GhcjsDomSpace where
405607 type EventSpec GhcjsDomSpace = GhcjsEventSpec
608+ type RawDocument GhcjsDomSpace = DOM.Document
406609 type RawTextNode GhcjsDomSpace = DOM.Text
610+ type RawCommentNode GhcjsDomSpace = DOM.Comment
407611 type RawElement GhcjsDomSpace = DOM.Element
408− type RawFile GhcjsDomSpace = DOM.File
409612 type RawInputElement GhcjsDomSpace = DOM.HTMLInputElement
410613 type RawTextAreaElement GhcjsDomSpace = DOM.HTMLTextAreaElement
411614 type RawSelectElement GhcjsDomSpace = DOM.HTMLSelectElement
@@ -437,15 +640,11 @@ data GhcjsEventSpec er = GhcjsEventSpec
437640newtype GhcjsEventHandler er = GhcjsEventHandler { unGhcjsEventHandler :: forall en. (EventName en, GhcjsDomEvent en) -> JSM (Maybe (er en)) }
438641
439642#ifndef USE_TEMPLATE_HASKELL
440−phantom2 :: (Functor f, Contravariant f) => f a -> f b
441−phantom2 = phantom
442−{-# INLINE phantom2 #-}
443−
444643ghcjsEventSpec_filters :: forall er . Lens' (GhcjsEventSpec er) (DMap EventName (GhcjsEventFilter er))
445644ghcjsEventSpec_filters f (GhcjsEventSpec a b) = (\a' -> GhcjsEventSpec a' b) <$> f a
446645{-# INLINE ghcjsEventSpec_filters #-}
447646ghcjsEventSpec_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)
449648{-# INLINE ghcjsEventSpec_handler #-}
450649#endif
451650
@@ -458,137 +657,753 @@ instance er ~ EventResult => Default (GhcjsEventSpec er) where
458657 runReaderT (defaultDomEventHandler e en) evt
459658 }
460659
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
4621329 notReadyUntil e = do
4631330 eOnce <- headE e
464− env <- ImmediateDomBuilderT ask
465− let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
1331+ unreadyChildren <- askUnreadyChildren
1332+ commitAction <- askCommitAction
4661333 liftIO $ modifyIORef' unreadyChildren succ
4671334 let ready = do
4681335 old <- liftIO $ readIORef unreadyChildren
4691336 let new = pred old
4701337 liftIO $ writeIORef unreadyChildren $! new
471− when (new == 0) $ _immediateDomBuilderEnv_commitAction env
1338+ when (new == 0) commitAction
4721339 requestDomAction_ $ ready <$ eOnce
4731340 notReady = do
474− env <- ImmediateDomBuilderT ask
475− let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
1341+ unreadyChildren <- askUnreadyChildren
4761342 liftIO $ modifyIORef' unreadyChildren succ
4771343
478−instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t m) where
479− type DomBuilderSpace (ImmediateDomBuilderT t m) = GhcjsDomSpace
1344+data HydrationDomSpace
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
4801373 {-# INLINABLE textNode #-}
481− textNode (TextNodeConfig initialContents mSetContents) = do
482− n <- textNodeInternal initialContents
483− mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
484− return $ TextNode n
1374+ textNode = textNodeInternal
1375+ {-# INLINABLE commentNode #-}
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
4851388 {-# 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
4871394 {-# INLINABLE inputElement #-}
488− inputElement cfg = do
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− }
1395+ inputElement = inputElementImmediate
5391396 {-# INLINABLE textAreaElement #-}
540− textAreaElement cfg = do --TODO
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− }
1397+ textAreaElement = textAreaElementImmediate
5641398 {-# INLINABLE selectElement #-}
565− selectElement cfg child = do
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)
1399+ selectElement = selectElementImmediate
5901400 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
5921407
5931408data FragmentState
5941409 = FragmentState_Unmounted
@@ -608,21 +1423,21 @@ extractFragment fragment = do
6081423 extractBetweenExclusive (_immediateDomFragment_document fragment) before after
6091424 liftIO $ writeIORef (_immediateDomFragment_state fragment) FragmentState_Unmounted
6101425
611−instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomBuilderT t m) where
612− type DomFragment (ImmediateDomBuilderT t m) = ImmediateDomFragment
1426+instance SupportsHydrationDomBuilder t m => MountableDomBuilder t (HydrationDomBuilderT GhcjsDomSpace t m) where
1427+ type DomFragment (HydrationDomBuilderT GhcjsDomSpace t m) = ImmediateDomFragment
6131428 buildDomFragment w = do
6141429 df <- createDocumentFragment =<< askDocument
6151430 result <- flip localEnv w $ \env -> env
616− { _immediateDomBuilderEnv_parent = toNode df
1431+ { _hydrationDomBuilderEnv_parent = Left $ toNode df
6171432 }
6181433 state <- liftIO $ newIORef FragmentState_Unmounted
6191434 return (ImmediateDomFragment df state, result)
6201435 mountDomFragment fragment setFragment = do
621− parent <- askParent
1436+ parent <- getParent
6221437 extractFragment fragment
623− before <- textNodeInternal ("" :: Text)
1438+ before <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
6241439 appendChild_ parent $ _immediateDomFragment_document fragment
625− after <- textNodeInternal ("" :: Text)
1440+ after <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
6261441 xs <- foldDyn (\new (previous, _) -> (new, Just previous)) (fragment, Nothing) setFragment
6271442 requestDomAction_ $ ffor (updated xs) $ \(childFragment, Just previousFragment) -> do
6281443 extractFragment previousFragment
@@ -631,12 +1446,19 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB
6311446 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
6321447 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
6331448
634−instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
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+
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
6351456 {-# INLINABLE runWithReplace #-}
6361457 runWithReplace a0 a' = do
637− initialEnv <- ImmediateDomBuilderT ask
638− before <- textNodeInternal ("" :: Text)
639− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
1458+ initialEnv <- HydrationDomBuilderT ask
1459+ let hydrating = _hydrationDomBuilderEnv_hydrationMode initialEnv
1460+ (hydrateStart, before, beforeKey) <- skipToReplaceStart
1461+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
6401462 haveEverBeenReady <- liftIO $ newIORef False
6411463 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
6421464 let myCommitAction = do
@@ -647,79 +1469,117 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
6471469 old <- liftIO $ readIORef parentUnreadyChildren
6481470 let new = pred old
6491471 liftIO $ writeIORef parentUnreadyChildren $! new
650− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
1472+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
6511473 -- We draw 'after' in this roundabout way to avoid using MonadFix
6521474 doc <- askDocument
653− after <- createTextNode doc ("" :: Text)
1475+ parent <- getParent
1476+ (hydrateEnd, after) <- skipToReplaceEnd beforeKey
6541477 let drawInitialChild = do
1478+ h <- liftIO $ readIORef hydrating
1479+ p' <- case h of
1480+ HydrationMode_Hydrating -> pure parent
1481+ HydrationMode_Immediate -> toNode <$> createDocumentFragment doc
6551482 unreadyChildren <- liftIO $ newIORef 0
656− let f = do
657− result <- a0
658− append $ toNode after
659− return result
660− result <- runReaderT (unImmediateDomBuilderT f) $ initialEnv
661− { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
662− , _immediateDomBuilderEnv_commitAction = myCommitAction
1483+ let a0' = case h of
1484+ HydrationMode_Hydrating -> a0
1485+ HydrationMode_Immediate -> do
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
1492+ result <- runReaderT (unHydrationDomBuilderT a0') initialEnv
1493+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1494+ , _hydrationDomBuilderEnv_commitAction = myCommitAction
1495+ , _hydrationDomBuilderEnv_parent = Left p'
1496+ , _hydrationDomBuilderEnv_delayed = delayed
6631497 }
1498+ dom <- case h of
1499+ HydrationMode_Hydrating -> liftIO $ readIORef delayed
1500+ HydrationMode_Immediate -> pure $ pure ()
6641501 liftIO $ readIORef unreadyChildren >>= \case
6651502 0 -> writeIORef haveEverBeenReady True
6661503 _ -> modifyIORef' parentUnreadyChildren succ
667− return result
1504+ return (dom, result)
6681505 a'' <- numberOccurrences a'
669− (result0, child') <- ImmediateDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do
670− df <- createDocumentFragment doc
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
1510+ HydrationMode_Immediate -> toNode <$> createDocumentFragment doc
6711511 unreadyChildren <- liftIO $ newIORef 0
6721512 let commitAction = do
6731513 c <- liftIO $ readIORef currentCohort
6741514 when (c <= cohortId) $ do -- If a newer cohort has already been committed, just ignore this
675− deleteBetweenExclusive before after
676− insertBefore df after
1515+ !before' <- liftIO $ readIORef before
1516+ !after' <- liftIO $ readIORef after
1517+ deleteBetweenExclusive before' after'
1518+ insertBefore p' after'
6771519 liftIO $ writeIORef currentCohort cohortId
6781520 myCommitAction
679− result <- runReaderT (unImmediateDomBuilderT child) $ initialEnv
680− { _immediateDomBuilderEnv_parent = toNode df
681− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
682− , _immediateDomBuilderEnv_commitAction = commitAction
683− }
1521+ delayed <- case h of
1522+ HydrationMode_Hydrating -> liftIO $ newIORef $ pure ()
1523+ HydrationMode_Immediate -> pure $ _hydrationDomBuilderEnv_delayed initialEnv
1524+ result <- runReaderT (unHydrationDomBuilderT child) $ initialEnv
1525+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1526+ , _hydrationDomBuilderEnv_commitAction = case h of
1527+ HydrationMode_Hydrating -> myCommitAction
1528+ HydrationMode_Immediate -> commitAction
1529+ , _hydrationDomBuilderEnv_parent = Left p'
1530+ , _hydrationDomBuilderEnv_delayed = delayed
1531+ }
1532+ dom <- case h of
1533+ HydrationMode_Hydrating -> liftIO $ readIORef delayed
1534+ HydrationMode_Immediate -> pure $ pure ()
6841535 uc <- liftIO $ readIORef unreadyChildren
6851536 let commitActionToRunNow = if uc == 0
686− then Just commitAction
1537+ then Just $ commitAction
6871538 else Nothing -- A child will run it when unreadyChildren is decremented to 0
688− return (commitActionToRunNow, result)
689− requestDomAction_ $ fmapMaybe fst child'
1539+ actions = case h of
1540+ HydrationMode_Hydrating -> Left dom
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
1548+ requestDomAction_ $ fmapMaybe id commitAction
6901549 return (result0, snd <$> child')
1550+
6911551 {-# INLINABLE traverseIntMapWithKeyWithAdjust #-}
6921552 traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjust'
6931553 {-# INLINABLE traverseDMapWithKeyWithAdjust #-}
6941554 traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjust'
6951555 {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-}
6961556 traverseDMapWithKeyWithAdjustWithMove = do
697− let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState 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)
1557+ let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose (TraverseChild t m (Some k)) v')) old = do
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)
6991559 new k = PatchDMapWithMove.nodeInfoMapFromM $ \case
700− PatchDMapWithMove.From_Insert (Compose (_, _, sRef, _)) -> do
701− readIORef sRef >>= \case
1560+ PatchDMapWithMove.From_Insert (Compose (TraverseChild (Left _hydration) _)) -> return PatchDMapWithMove.From_Delete
1561+ PatchDMapWithMove.From_Insert (Compose (TraverseChild (Right immediate) _)) -> do
1562+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
7021563 ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete
7031564 ChildReadyState_Unready _ -> do
704− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
705− return $ PatchDMapWithMove.From_Insert $ Constant sRef
1565+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ Some k
1566+ return $ PatchDMapWithMove.From_Insert $ Constant (_traverseChildImmediate_childReadyState immediate)
7061567 PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete
7071568 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)
7091570 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
7111572 return $ Constant ()
7121573 p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p
7131574 _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old
7141575 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
7161577 let p = unPatchDMapWithMove p_
7171578 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)
7201580 collectIfMoved k e = do
721− let mThisPlaceholder = Map.lookup (Some.This k) phsBefore -- Will be Nothing if this element wasn't present before
722− nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsBefore
1581+ let mThisPlaceholder = Map.lookup (Some k) phsBefore -- Will be Nothing if this element wasn't present before
1582+ nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some k) phsBefore
7231583 case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of
7241584 False -> do
7251585 mapM_ (`deleteUpTo` nextPlaceholder) mThisPlaceholder
@@ -727,13 +1587,22 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
7271587 True -> do
7281588 Constant <$> mapM (`collectUpTo` nextPlaceholder) mThisPlaceholder
7291589 collected <- DMap.traverseWithKey collectIfMoved p
730− let !phsAfter = fromMaybe phsBefore $ apply (weakenPatchDMapWithMoveWith (\(Compose (_, ph, _, _)) -> ph) p_) phsBefore --TODO: Don't recompute this
731− let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant () a)
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
1599+ let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> JSM (Constant () a)
7321600 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
7341602 case PatchDMapWithMove._nodeInfo_from e of
735− PatchDMapWithMove.From_Insert (Compose (df, _, _, _)) -> do
736− df `insertBefore` nextPlaceholder
1603+ PatchDMapWithMove.From_Insert (Compose (TraverseChild x _)) -> case x of
1604+ Left _ -> pure ()
1605+ Right immediate -> _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
7371606 PatchDMapWithMove.From_Delete -> do
7381607 return ()
7391608 PatchDMapWithMove.From_Move fromKey -> do
@@ -744,105 +1613,166 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
7441613 liftIO $ writeIORef placeholders $! phsAfter
7451614
7461615{-# INLINABLE 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'))
1616+traverseDMapWithKeyWithAdjust'
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'))
7481622traverseDMapWithKeyWithAdjust' = do
749− let updateChildUnreadiness (p :: PatchDMap k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState 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)
1623+ let updateChildUnreadiness (p :: PatchDMap k (Compose (TraverseChild t m (Some k)) v')) old = do
1624+ let new :: forall a. k a -> ComposeMaybe (Compose (TraverseChild t m (Some k)) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState (Some k)))) a)
7511625 new k (ComposeMaybe m) = ComposeMaybe <$> case m of
7521626 Nothing -> return Nothing
753− Just (Compose (_, _, sRef, _)) -> do
754− readIORef sRef >>= \case
1627+ Just (Compose (TraverseChild (Left _hydration) _)) -> pure Nothing
1628+ Just (Compose (TraverseChild (Right immediate) _)) -> do
1629+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
7551630 ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
7561631 ChildReadyState_Unready _ -> do
757− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
758− return $ Just $ Constant sRef
1632+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ Some k
1633+ return $ Just $ Constant (_traverseChildImmediate_childReadyState immediate)
7591634 delete _ (Constant sRef) = do
7601635 writeIORef sRef $ ChildReadyState_Unready Nothing
7611636 return $ Constant ()
7621637 p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p
7631638 _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old
7641639 return $ applyAlways p' old
765− hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchDMap p) -> do
1640+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholder (PatchDMap patch) -> do
7661641 phs <- liftIO $ readIORef placeholders
767− forM_ (DMap.toList p) $ \(k :=> ComposeMaybe mv) -> do
768− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
769− let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phs
770− forM_ (Map.lookup (Some.This k) phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
771− forM_ mv $ \(Compose (df, _, _, _)) -> df `insertBefore` nextPlaceholder
772− liftIO $ writeIORef placeholders $! fromMaybe phs $ apply (weakenPatchDMapWith (\(Compose (_, ph, _, _)) -> ph) $ PatchDMap p) phs
1642+ forM_ (DMap.toList patch) $ \(k :=> ComposeMaybe mv) -> do
1643+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some k) phs
1644+ -- Delete old node
1645+ forM_ (Map.lookup (Some k) phs) $ \thisPlaceholder -> do
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
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
1659+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply filtered phs
7731660
7741661{-# 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'))
7761668traverseIntMapWithKeyWithAdjust' = do
777− let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) old = do
778− let new :: IntMap.Key -> Maybe (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IO (Maybe (IORef ChildReadyStateInt))
1669+ let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (TraverseChild t m Int v')) old = do
1670+ let new :: IntMap.Key -> Maybe (TraverseChild t m Int v') -> IO (Maybe (IORef (ChildReadyState Int)))
7791671 new k m = case m of
7801672 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
7821676 readIORef sRef >>= \case
783− ChildReadyStateInt_Ready -> return Nothing -- Delete this child, since it's ready
784− ChildReadyStateInt_Unready _ -> do
785− writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
1677+ ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
1678+ ChildReadyState_Unready _ -> do
1679+ writeIORef sRef $ ChildReadyState_Unready $ Just k
7861680 return $ Just sRef
7871681 delete _ sRef = do
788− writeIORef sRef $ ChildReadyStateInt_Unready Nothing
1682+ writeIORef sRef $ ChildReadyState_Unready Nothing
7891683 return ()
7901684 p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner
7911685 _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old
7921686 return $ applyAlways p' old
793− hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchIntMap p) -> do
1687+ hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholder (PatchIntMap p) -> do
7941688 phs <- liftIO $ readIORef placeholders
7951689 forM_ (IntMap.toList p) $ \(k, mv) -> do
796− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
7971690 let nextPlaceholder = maybe lastPlaceholder snd $ IntMap.lookupGT k phs
1691+ -- Delete old node
7981692 forM_ (IntMap.lookup k phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
799− forM_ mv $ \(df, _, _, _) -> df `insertBefore` nextPlaceholder
800− liftIO $ writeIORef placeholders $! fromMaybe phs $ apply ((\(_, ph, _, _) -> ph) <$> PatchIntMap p) phs
1693+ -- Insert new node
1694+ forM_ mv $ \(TraverseChild e _) -> case e of
1695+ Left _hydration -> pure ()
1696+ Right immediate -> do
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
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+ #-}
8011712
802−#if MIN_VERSION_base(4,9,0)
803−data ChildReadyState k
804−#else
805−data ChildReadyState (k :: * -> *)
806−#endif
1713+{-# SPECIALIZE traverseIntMapWithKeyWithAdjust'
1714+ :: (IntMap.Key -> v -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM v')
1715+ -> IntMap v
1716+ -> Event DomTimeline (PatchIntMap v)
1717+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (IntMap v', Event DomTimeline (PatchIntMap v'))
1718+ #-}
1719+
1720+data ChildReadyState a
8071721 = ChildReadyState_Ready
808− | ChildReadyState_Unready !(Maybe (Some k))
1722+ | ChildReadyState_Unready !(Maybe a)
8091723 deriving (Show, Read, Eq, Ord)
8101724
811−data ChildReadyStateInt
812− = ChildReadyStateInt_Ready
813− | ChildReadyStateInt_Unready !(Maybe Int)
814− deriving (Show, Read, Eq, Ord)
1725+insertAfterPreviousNode :: (Monad m, MonadJSM m) => DOM.IsNode node => node -> HydrationRunnerT t m ()
1726+insertAfterPreviousNode node = do
1727+ parent <- askParent
1728+ nextNode <- maybe (Node.getFirstChild parent) Node.getNextSibling =<< getPreviousNode
1729+ Node.insertBefore_ parent node nextNode
1730+ setPreviousNode $ Just $ toNode node
8151731
816−{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
817−hoistTraverseIntMapWithKeyWithAdjust :: forall v v' t m p.
1732+{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
1733+hoistTraverseWithKeyWithAdjust
1734+ ::
8181735 ( Adjustable t m
1736+ , MonadHold t m
1737+ , DMap.GCompare k
8191738 , MonadIO m
8201739 , MonadJSM m
821− , MonadFix m
8221740 , PrimMonad m
823− , Monoid (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
824− , Functor p
1741+ , MonadFix m
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
8251749 )
826− => ( (IntMap.Key -> v -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
827− -> IntMap v
828− -> Event t (p v)
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')))
830− ) -- ^ The base monad's traversal
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
832− -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ()) -- ^ Apply a patch to the DOM
833− -> (IntMap.Key -> v -> ImmediateDomBuilderT t m v')
834− -> IntMap v
835− -> Event t (p v)
836− -> ImmediateDomBuilderT t m (IntMap v', Event t (p v'))
837−hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
838− initialEnv <- ImmediateDomBuilderT ask
839− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
840− pendingChange :: IORef (IntMap (IORef ChildReadyStateInt), p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) <- liftIO $ newIORef mempty
1750+ => (forall vv vv'.
1751+ (forall a. k a -> vv a -> DomRenderHookT t m (vv' a))
1752+ -> DMap k vv
1753+ -> Event t (p k vv)
1754+ -> DomRenderHookT t m (DMap k vv', Event t (p k vv')))
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))))))
1759+ -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
1760+ -> (IORef (Map.Map (Some k) DOM.Text) -> DOM.Text -> p k (Compose (TraverseChild t m (Some k)) v') -> JSM ())
1761+ -- ^ Apply a patch to the DOM
1762+ -> (forall a. k a -> v a -> HydrationDomBuilderT s t m (v' a))
1763+ -> DMap k v
1764+ -> Event t (p k v)
1765+ -> HydrationDomBuilderT s t m (DMap k v', Event t (p k v'))
1766+hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
1767+ doc <- askDocument
1768+ initialEnv <- HydrationDomBuilderT ask
1769+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
1770+ pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState (Some k)))), p k (Compose (TraverseChild t m (Some k)) v')) <- liftIO $ newIORef mempty
8411771 haveEverBeenReady <- liftIO $ newIORef False
842− placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
843− lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1772+ placeholders <- liftIO $ newIORef Map.empty
1773+ lastPlaceholder <- createTextNode doc ("" :: Text)
8441774 let applyDomUpdate p = do
845− applyDomUpdate_ placeholders lastPlaceholderRef p
1775+ applyDomUpdate_ placeholders lastPlaceholder p
8461776 markSelfReady
8471777 liftIO $ writeIORef pendingChange $! mempty
8481778 markSelfReady = do
@@ -853,86 +1783,105 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_
8531783 old <- liftIO $ readIORef parentUnreadyChildren
8541784 let new = pred old
8551785 liftIO $ writeIORef parentUnreadyChildren $! new
856− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
857− markChildReady :: IORef ChildReadyStateInt -> JSM ()
1786+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
1787+ markChildReady :: IORef (ChildReadyState (Some k)) -> JSM ()
8581788 markChildReady childReadyState = do
8591789 liftIO (readIORef childReadyState) >>= \case
860− ChildReadyStateInt_Ready -> return ()
861− ChildReadyStateInt_Unready countedAt -> do
862− liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
1790+ ChildReadyState_Ready -> return ()
1791+ ChildReadyState_Unready countedAt -> do
1792+ liftIO $ writeIORef childReadyState ChildReadyState_Ready
8631793 case countedAt of
8641794 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
8661796 (oldUnready, p) <- liftIO $ readIORef pendingChange
867− when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
868− let newUnready = IntMap.delete k oldUnready
1797+ when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
1798+ let newUnready = DMap.delete k oldUnready
8691799 liftIO $ writeIORef pendingChange (newUnready, p)
870− when (IntMap.null newUnready) $ do
1800+ when (DMap.null newUnready) $ do
8711801 applyDomUpdate p
872− (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
873− let processChild k (_, _, sRef, _) = do
874− readIORef sRef >>= \case
875− ChildReadyStateInt_Ready -> return Nothing
876− ChildReadyStateInt_Unready _ -> do
877− writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
878− return $ Just sRef
879− initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
880− liftIO $ if IntMap.null initialUnready
1802+ (children0 :: DMap k (Compose (TraverseChild t m (Some k)) v'), children' :: Event t (p k (Compose (TraverseChild t m (Some k)) v')))
1803+ <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
1804+ let processChild k (Compose (TraverseChild e _)) = case e of
1805+ Left _ -> pure $ ComposeMaybe Nothing
1806+ Right immediate -> ComposeMaybe <$> do
1807+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
1808+ ChildReadyState_Ready -> return Nothing
1809+ ChildReadyState_Unready _ -> do
1810+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ Some k
1811+ return $ Just $ Constant (_traverseChildImmediate_childReadyState immediate)
1812+ initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
1813+ liftIO $ if DMap.null initialUnready
8811814 then writeIORef haveEverBeenReady True
8821815 else do
8831816 modifyIORef' parentUnreadyChildren succ
8841817 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
886− placeholders0 = fmap (\(_, ph, _, _) -> ph) children0
887− result' = ffor children' $ fmap $ \(_, _, _, r) -> r
888− liftIO $ writeIORef placeholders $! placeholders0
889− _ <- IntMap.traverseWithKey (\_ (df, _, _, _) -> void $ append $ toNode df) children0
890− liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1818+ getHydrationMode >>= \case
1819+ HydrationMode_Hydrating -> addHydrationStepWithSetup (holdIncremental children0 children') $ \children -> do
1820+ dm :: DMap k (Compose (TraverseChild t m (Some k)) v') <- sample $ currentIncremental children
1821+ phs <- traverse id $ weakenDMapWith (either _traverseChildHydration_delayed (pure . _traverseChildImmediate_placeholder) . _traverseChild_mode . getCompose) dm
1822+ liftIO $ writeIORef placeholders $! phs
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
8911831 requestDomAction_ $ ffor children' $ \p -> do
8921832 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
8931833 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
8941834 let !newP = p <> oldP
8951835 liftIO $ writeIORef pendingChange (newUnready, newP)
896− when (IntMap.null newUnready) $ do
1836+ when (DMap.null newUnready) $ do
8971837 applyDomUpdate newP
1838+ let result0 = DMap.map (_traverseChild_result . getCompose) children0
1839+ result' = ffor children' $ mapPatch $ _traverseChild_result . getCompose
8981840 return (result0, result')
8991841
900−{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
901−hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
1842+{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
1843+hoistTraverseIntMapWithKeyWithAdjust ::
9021844 ( Adjustable t m
9031845 , MonadHold t m
904− , DMap.GCompare k
905− , MonadIO m
9061846 , MonadJSM m
907− , PrimMonad m
9081847 , MonadFix m
909− , Patch (p k v)
910− , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int)
911− , Monoid (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v'))
912− , Patch (p k (Constant Int))
1848+ , PrimMonad m
1849+ , Monoid (p (TraverseChild t m Int v'))
1850+ , Functor p
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
9131856 )
914− => (forall vv vv'.
915− (forall a. k a -> vv a -> RequesterT t JSM Identity (TriggerEventT t m) (vv' a))
916− -> DMap k vv
917− -> Event t (p k vv)
918− -> RequesterT t JSM Identity (TriggerEventT t m) (DMap k vv', Event t (p k vv'))
919− ) -- ^ 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
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
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
923− -> (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a))
924− -> DMap k v
925− -> Event t (p k v)
926− -> ImmediateDomBuilderT t m (DMap k v', Event t (p k 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
928− initialEnv <- ImmediateDomBuilderT ask
929− let parentUnreadyChildren = _immediateDomBuilderEnv_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
1857+ => ((IntMap.Key -> v -> DomRenderHookT t m (TraverseChild t m Int v'))
1858+ -> IntMap v
1859+ -> Event t (p v)
1860+ -> DomRenderHookT t m (IntMap (TraverseChild t m Int v'), Event t (p (TraverseChild t m Int v'))))
1861+ -- ^ The base monad's traversal
1862+ -> (p (TraverseChild t m Int v')
1863+ -> IntMap (IORef (ChildReadyState Int))
1864+ -> IO (IntMap (IORef (ChildReadyState Int))))
1865+ -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
1866+ -> (IORef (IntMap DOM.Text)
1867+ -> DOM.Text
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
1873+ -> Event t (p v)
1874+ -> HydrationDomBuilderT s t m (IntMap v', Event t (p v'))
1875+hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
1876+ doc <- askDocument
1877+ initialEnv <- HydrationDomBuilderT ask
1878+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
1879+ pendingChange :: IORef (IntMap (IORef (ChildReadyState Int)), p (TraverseChild t m Int v')) <- liftIO $ newIORef mempty
9311880 haveEverBeenReady <- liftIO $ newIORef False
932− placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
933− lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1881+ placeholders <- liftIO $ newIORef IntMap.empty
1882+ lastPlaceholder <- createTextNode doc ("" :: Text)
9341883 let applyDomUpdate p = do
935− applyDomUpdate_ placeholders lastPlaceholderRef p
1884+ applyDomUpdate_ placeholders lastPlaceholder p
9361885 markSelfReady
9371886 liftIO $ writeIORef pendingChange $! mempty
9381887 markSelfReady = do
@@ -943,8 +1892,8 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda
9431892 old <- liftIO $ readIORef parentUnreadyChildren
9441893 let new = pred old
9451894 liftIO $ writeIORef parentUnreadyChildren $! new
946− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
947− markChildReady :: IORef (ChildReadyState k) -> JSM ()
1895+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
1896+ markChildReady :: IORef (ChildReadyState Int) -> JSM ()
9481897 markChildReady childReadyState = do
9491898 liftIO (readIORef childReadyState) >>= \case
9501899 ChildReadyState_Ready -> return ()
@@ -952,82 +1901,191 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda
9521901 liftIO $ writeIORef childReadyState ChildReadyState_Ready
9531902 case countedAt of
9541903 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
9561905 (oldUnready, p) <- liftIO $ readIORef pendingChange
957− when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
958− let newUnready = DMap.delete k oldUnready
1906+ when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
1907+ let newUnready = IntMap.delete k oldUnready
9591908 liftIO $ writeIORef pendingChange (newUnready, p)
960− when (DMap.null newUnready) $ do
1909+ when (IntMap.null newUnready) $ do
9611910 applyDomUpdate p
962− (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
963− let processChild k (Compose (_, _, sRef, _)) = ComposeMaybe <$> do
964− readIORef sRef >>= \case
965− ChildReadyState_Ready -> return Nothing
966− ChildReadyState_Unready _ -> do
967− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
968− return $ Just $ Constant sRef
969− initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
970− liftIO $ if DMap.null initialUnready
1911+ (children0 :: IntMap (TraverseChild t m Int v'), children' :: Event t (p (TraverseChild t m Int v')))
1912+ <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
1913+ let processChild k (TraverseChild e _) = case e of
1914+ Left _ -> pure Nothing
1915+ Right immediate -> do
1916+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
1917+ ChildReadyState_Ready -> return Nothing
1918+ ChildReadyState_Unready _ -> do
1919+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just k
1920+ return $ Just (_traverseChildImmediate_childReadyState immediate)
1921+ initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
1922+ liftIO $ if IntMap.null initialUnready
9711923 then writeIORef haveEverBeenReady True
9721924 else do
9731925 modifyIORef' parentUnreadyChildren succ
9741926 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
976− placeholders0 = weakenDMapWith (\(Compose (_, ph, _, _)) -> ph) children0
977− result' = ffor children' $ mapPatch $ \(Compose (_, _, _, r)) -> r
978− liftIO $ writeIORef placeholders $! placeholders0
979− _ <- DMap.traverseWithKey (\_ (Compose (df, _, _, _)) -> Constant () <$ append (toNode df)) children0
980− liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1927+ getHydrationMode >>= \case
1928+ HydrationMode_Hydrating -> addHydrationStepWithSetup (holdIncremental children0 children') $ \children -> do
1929+ dm :: IntMap (TraverseChild t m Int v') <- sample $ currentIncremental children
1930+ phs <- traverse (either _traverseChildHydration_delayed (pure . _traverseChildImmediate_placeholder) . _traverseChild_mode) dm
1931+ liftIO $ writeIORef placeholders $! phs
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
9811940 requestDomAction_ $ ffor children' $ \p -> do
9821941 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
9831942 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
9841943 let !newP = p <> oldP
9851944 liftIO $ writeIORef pendingChange (newUnready, newP)
986− when (DMap.null newUnready) $ do
1945+ when (IntMap.null newUnready) $ do
9871946 applyDomUpdate newP
1947+ let result0 = IntMap.map _traverseChild_result children0
1948+ result' = ffor children' $ fmap $ _traverseChild_result
9881949 return (result0, result')
9891950
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+
9902005{-# INLINABLE drawChildUpdate #-}
991−drawChildUpdate :: (MonadIO m, MonadJSM m)
992− => ImmediateDomBuilderEnv t
2006+drawChildUpdate :: (MonadJSM m, Reflex t)
2007+ => HydrationDomBuilderEnv t m
9932008 -> (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)
995− -> RequesterT t JSM Identity (TriggerEventT t m) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a)
2009+ -> HydrationDomBuilderT s t m (f a)
2010+ -> DomRenderHookT t m (Compose (TraverseChild t m k) f a)
9962011drawChildUpdate initialEnv markReady child = do
997− childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing
2012+ let doc = _hydrationDomBuilderEnv_document initialEnv
9982013 unreadyChildren <- liftIO $ newIORef 0
999− df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
1000− (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
1001− { _immediateDomBuilderEnv_parent = toNode df
1002− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
1003− , _immediateDomBuilderEnv_commitAction = markReady childReadyState
1004− }
1005− u <- liftIO $ readIORef unreadyChildren
1006− when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
1007− return $ Compose (df, placeholder, childReadyState, result)
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
2034+ df <- createDocumentFragment doc
2035+ placeholder <- createTextNode doc ("" :: Text)
2036+ Node.appendChild_ df placeholder
2037+ result <- runReaderT (unHydrationDomBuilderT child) initialEnv
2038+ { _hydrationDomBuilderEnv_parent = Left $ toNode df
2039+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
2040+ , _hydrationDomBuilderEnv_commitAction = markReady childReadyState
2041+ }
2042+ u <- liftIO $ readIORef unreadyChildren
2043+ when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
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+ }
10082052
1009−{-# INLINABLE drawChildUpdateInt #-}
1010−drawChildUpdateInt :: (MonadIO m, MonadJSM m)
1011− => ImmediateDomBuilderEnv t
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
1013− -> ImmediateDomBuilderT t m v'
1014− -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')
1015−drawChildUpdateInt initialEnv markReady child = do
1016− childReadyState <- liftIO $ newIORef $ ChildReadyStateInt_Unready Nothing
1017− unreadyChildren <- liftIO $ newIORef 0
1018− df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
1019− (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
1020− { _immediateDomBuilderEnv_parent = toNode df
1021− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
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)
2053+{-# SPECIALIZE drawChildUpdate
2054+ :: HydrationDomBuilderEnv DomTimeline HydrationM
2055+ -> (IORef (ChildReadyState Int) -> JSM ())
2056+ -> HydrationDomBuilderT s DomTimeline HydrationM (Identity a)
2057+ -> DomRenderHookT DomTimeline HydrationM (Compose (TraverseChild DomTimeline HydrationM Int) Identity a)
2058+ #-}
2059+
2060+{-# SPECIALIZE drawChildUpdate
2061+ :: HydrationDomBuilderEnv DomTimeline HydrationM
2062+ -> (IORef (ChildReadyState (Some k)) -> JSM ())
2063+ -> HydrationDomBuilderT s DomTimeline HydrationM (f a)
2064+ -> DomRenderHookT DomTimeline HydrationM (Compose (TraverseChild DomTimeline HydrationM (Some k)) f a)
2065+ #-}
10272066
1028−mkHasFocus :: (MonadHold t m, Reflex t) => Element er d t -> m (Dynamic t Bool)
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)))
2085+ => Element er d t -> m (Dynamic t Bool)
10292086mkHasFocus 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
10312089 holdDyn initialFocus $ leftmost
10322090 [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
10332091 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
@@ -1036,39 +2094,49 @@ mkHasFocus e = do
10362094insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
10372095insertBefore new existing = do
10382096 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
10402098
1041−instance PerformEvent t m => PerformEvent t (ImmediateDomBuilderT t m) where
1042− type Performable (ImmediateDomBuilderT t m) = Performable m
2099+type ImmediateDomBuilderT = HydrationDomBuilderT GhcjsDomSpace
2100+
2101+instance PerformEvent t m => PerformEvent t (HydrationDomBuilderT s t m) where
2102+ type Performable (HydrationDomBuilderT s t m) = Performable m
10432103 {-# INLINABLE performEvent_ #-}
10442104 performEvent_ e = lift $ performEvent_ e
10452105 {-# INLINABLE performEvent #-}
10462106 performEvent e = lift $ performEvent e
10472107
1048−instance PostBuild t m => PostBuild t (ImmediateDomBuilderT t m) where
2108+instance PostBuild t m => PostBuild t (HydrationDomBuilderT s t m) where
10492109 {-# INLINABLE getPostBuild #-}
10502110 getPostBuild = lift getPostBuild
10512111
1052−instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (ImmediateDomBuilderT t m) where
2112+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydrationDomBuilderT s t m) where
10532113 {-# INLINABLE newEventWithTrigger #-}
10542114 newEventWithTrigger = lift . newEventWithTrigger
10552115 {-# INLINABLE newFanEventWithTrigger #-}
10562116 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
10572117
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
10592127 {-# INLINABLE newTriggerEvent #-}
1060− newTriggerEvent = ImmediateDomBuilderT . lift . lift $ newTriggerEvent
2128+ newTriggerEvent = DomRenderHookT . lift $ newTriggerEvent
10612129 {-# INLINABLE newTriggerEventWithOnComplete #-}
1062− newTriggerEventWithOnComplete = ImmediateDomBuilderT . lift . lift $ newTriggerEventWithOnComplete
2130+ newTriggerEventWithOnComplete = DomRenderHookT . lift $ newTriggerEventWithOnComplete
10632131 {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
1064− newEventWithLazyTriggerWithOnComplete f = ImmediateDomBuilderT . lift . lift $ newEventWithLazyTriggerWithOnComplete f
2132+ newEventWithLazyTriggerWithOnComplete f = DomRenderHookT . lift $ newEventWithLazyTriggerWithOnComplete f
10652133
1066−instance HasJSContext m => HasJSContext (ImmediateDomBuilderT t m) where
1067− type JSContextPhantom (ImmediateDomBuilderT t m) = JSContextPhantom m
2134+instance HasJSContext m => HasJSContext (HydrationDomBuilderT s t m) where
2135+ type JSContextPhantom (HydrationDomBuilderT s t m) = JSContextPhantom m
10682136 askJSContext = lift askJSContext
10692137
1070−instance MonadRef m => MonadRef (ImmediateDomBuilderT t m) where
1071− type Ref (ImmediateDomBuilderT t m) = Ref m
2138+instance MonadRef m => MonadRef (HydrationDomBuilderT s t m) where
2139+ type Ref (HydrationDomBuilderT s t m) = Ref m
10722140 {-# INLINABLE newRef #-}
10732141 newRef = lift . newRef
10742142 {-# INLINABLE readRef #-}
@@ -1076,12 +2144,12 @@ instance MonadRef m => MonadRef (ImmediateDomBuilderT t m) where
10762144 {-# INLINABLE writeRef #-}
10772145 writeRef r = lift . writeRef r
10782146
1079−instance MonadAtomicRef m => MonadAtomicRef (ImmediateDomBuilderT t m) where
2147+instance MonadAtomicRef m => MonadAtomicRef (HydrationDomBuilderT s t m) where
10802148 {-# INLINABLE atomicModifyRef #-}
10812149 atomicModifyRef r = lift . atomicModifyRef r
10822150
1083−instance (HasJS x m, ReflexHost t) => HasJS x (ImmediateDomBuilderT t m) where
1084− type JSX (ImmediateDomBuilderT t m) = JSX m
2151+instance (HasJS x m, ReflexHost t) => HasJS x (HydrationDomBuilderT s t m) where
2152+ type JSX (HydrationDomBuilderT s t m) = JSX m
10852153 liftJS = lift . liftJS
10862154
10872155type family EventType en where
@@ -1539,11 +2607,11 @@ getTouchEvent = do
15392607 , _touchEventResult_touches = touches
15402608 }
15412609
1542−instance MonadSample t m => MonadSample t (ImmediateDomBuilderT t m) where
2610+instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
15432611 {-# INLINABLE sample #-}
15442612 sample = lift . sample
15452613
1546−instance MonadHold t m => MonadHold t (ImmediateDomBuilderT t m) where
2614+instance MonadHold t m => MonadHold t (HydrationDomBuilderT s t m) where
15472615 {-# INLINABLE hold #-}
15482616 hold v0 v' = lift $ hold v0 v'
15492617 {-# INLINABLE holdDyn #-}
@@ -1565,7 +2633,7 @@ data Window t = Window
15652633 , _window_raw :: DOM.Window
15662634 }
15672635
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)
15692637wrapWindow wv _ = do
15702638 events <- wrapDomEventsMaybe wv (defaultDomWindowEventHandler wv) windowOnEventName
15712639 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
8080 runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a')
8181 traverseDMapWithKeyWithAdjust f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
8282 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')
8384
8485instance NotReady t m => NotReady t (InputDisabledT m) where
8586 notReadyUntil = lift . notReadyUntil
@@ -108,3 +109,8 @@ instance HasJSContext m => HasJSContext (InputDisabledT m) where
108109instance HasJS js m => HasJS js (InputDisabledT m) where
109110 type JSX (InputDisabledT m) = JSX m
110111 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 @@
1515{-# LANGUAGE UndecidableInstances #-}
1616module Reflex.Dom.Builder.Static where
1717
18+import Data.IORef (IORef)
1819import Blaze.ByteString.Builder.Html.Utf8
1920import Control.Lens hiding (element)
2021import Control.Monad.Exception
@@ -32,23 +33,29 @@ import qualified Data.Dependent.Map as DMap
3233import Data.Dependent.Sum (DSum (..))
3334import Data.Functor.Compose
3435import Data.Functor.Constant
36+import Data.IntMap (IntMap)
37+import qualified Data.IntMap as IntMap
3538import qualified Data.Map as Map
3639import Data.Map.Misc (applyMap)
37−import Data.Monoid
40+import Data.Monoid ((<>))
3841import qualified Data.Set as Set
3942import Data.Text (Text)
43+import qualified Data.Text as T
4044import Data.Text.Encoding
4145import Data.Tuple
4246import GHC.Generics
47+import Reflex.Adjustable.Class
4348import Reflex.Class
49+import Reflex.Dom.Main (DomHost, DomTimeline, runDomHost)
4450import Reflex.Dom.Builder.Class
4551import Reflex.Dynamic
4652import Reflex.Host.Class
4753import Reflex.PerformEvent.Base
4854import Reflex.PerformEvent.Class
4955import Reflex.PostBuild.Base
50−import Reflex.Spider
56+import Reflex.PostBuild.Class
5157import Reflex.TriggerEvent.Class
58+import System.Random (randomRIO)
5259
5360data StaticDomBuilderEnv t = StaticDomBuilderEnv
5461 { _staticDomBuilderEnv_shouldEscape :: Bool
@@ -76,6 +83,10 @@ runStaticDomBuilderT (StaticDomBuilderT a) e = do
7683 (result, a') <- runStateT (runReaderT a e) []
7784 return (result, mconcat $ reverse a')
7885
86+instance PostBuild t m => PostBuild t (StaticDomBuilderT t m) where
87+ {-# INLINABLE getPostBuild #-}
88+ getPostBuild = lift getPostBuild
89+
7990instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (StaticDomBuilderT t m) where
8091 {-# INLINABLE newEventWithTrigger #-}
8192 newEventWithTrigger = lift . newEventWithTrigger
@@ -138,29 +149,82 @@ instance Default (StaticEventSpec er)
138149
139150instance DomSpace StaticDomSpace where
140151 type EventSpec StaticDomSpace = StaticEventSpec
152+ type RawDocument StaticDomSpace = ()
141153 type RawTextNode StaticDomSpace = ()
154+ type RawCommentNode StaticDomSpace = ()
142155 type RawElement StaticDomSpace = ()
143− type RawFile StaticDomSpace = ()
144156 type RawInputElement StaticDomSpace = ()
145157 type RawTextAreaElement StaticDomSpace = ()
146158 type RawSelectElement StaticDomSpace = ()
147159 addEventSpecFlags _ _ _ _ = StaticEventSpec
148160
149−instance (Reflex t, Adjustable t m, MonadHold t m) => Adjustable t (StaticDomBuilderT t m) where
161+instance (SupportsStaticDomBuilder t m, Monad m) => HasDocument (StaticDomBuilderT t m) where
162+ askDocument = pure ()
163+
164+instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m) => Adjustable t (StaticDomBuilderT t m) where
150165 runWithReplace a0 a' = do
151166 e <- StaticDomBuilderT ask
167+ key <- replaceStart
152168 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
153169 o <- hold (snd result0) $ fmapCheap snd result'
154170 StaticDomBuilderT $ modify $ (:) $ join o
171+ replaceEnd key
155172 return (fst result0, fmapCheap fst result')
173+ traverseIntMapWithKeyWithAdjust = hoistIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust
156174 traverseDMapWithKeyWithAdjust = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap
157175 traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove
158176
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+
159222hoistDMapWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
160223 ( Adjustable t m
161224 , MonadHold t m
162225 , PatchTarget (p k (Constant (Behavior t Builder))) ~ DMap k (Constant (Behavior t Builder))
163226 , 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
164228 )
165229 => (forall vv vv'.
166230 (forall a. k a -> vv a -> m (vv' a))
@@ -205,6 +269,15 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
205269 Nothing -> return (pure (escape initialContents))
206270 Just setContents -> hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
207271 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 ()
208281 {-# INLINABLE element #-}
209282 element elementTag cfg child = do
210283 -- 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
283356 wrapRawElement () _ = return $ Element (EventSelector $ const never) ()
284357
285358--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))
287360
288361{-# INLINE renderStatic #-}
289362renderStatic :: StaticWidget x a -> IO (a, ByteString)
290363renderStatic w = do
291− runSpiderHost $ do
364+ runDomHost $ do
292365 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
293366 let env0 = StaticDomBuilderEnv True Nothing
294367 ((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 #-}
15module Reflex.Dom.Core (module X) where
26
37import Reflex as X hiding (askEvents)
48import Reflex.Dom.Builder.Class as X
9+import Reflex.Dom.Builder.Hydratable as X
510import Reflex.Dom.Builder.Immediate as X
611import Reflex.Dom.Builder.InputDisabled as X
712import Reflex.Dom.Builder.Static as X
813import Reflex.Dom.Class as X
914import Reflex.Dom.Location as X
1015import Reflex.Dom.Main as X
11−import Reflex.Dom.Modals.Base as X
1216import Reflex.Dom.Modals.Class as X
1317import Reflex.Dom.Old as X
1418import Reflex.Dom.Prerender as X
1519import Reflex.Dom.WebSocket as X
1620import Reflex.Dom.Widget as X
1721import 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
1212 , getLocationProtocol
1313 , getLocationUrl
1414 , manageHistory
15+ , manageHistory'
1516 , HistoryCommand (..)
1617 , HistoryStateUpdate (..)
1718 , HistoryItem (..)
@@ -23,8 +24,11 @@ import Reflex.Dom.Builder.Immediate (wrapDomEvent)
2324
2425import Control.Lens ((^.))
2526import Control.Monad ((>=>))
27+import Control.Monad.Fix (MonadFix)
28+import Data.Align (align)
2629import Data.Monoid
2730import Data.Text (Text)
31+import Data.These (These(..))
2832import qualified GHCJS.DOM as DOM
2933import qualified GHCJS.DOM.EventM as DOM
3034import qualified GHCJS.DOM.Location as Location
@@ -159,3 +163,38 @@ manageHistory runCmd = do
159163 <*> getLocationUri location
160164 holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
161165--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
1717
1818import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
1919
20+import Reflex.Adjustable.Class
21+import Reflex.Class
2022import Reflex.Dom.Builder.Immediate
2123import Reflex.Dom.Class
2224import Reflex.Host.Class
@@ -24,8 +26,10 @@ import Reflex.PerformEvent.Base
2426import Reflex.PostBuild.Base
2527import Reflex.Spider (Global, Spider, SpiderHost, runSpiderHost)
2628import Reflex.TriggerEvent.Base
27−
28−import Reflex.Dom.Specializations () -- For SPECIALIZATION pragmas, which are always re-exported
29+import Reflex.TriggerEvent.Class
30+#ifdef PROFILE_REFLEX
31+import Reflex.Profiled
32+#endif
2933
3034import Control.Concurrent
3135import Control.Lens
@@ -34,6 +38,7 @@ import Control.Monad.Reader hiding (forM, forM_, mapM, mapM_, sequence, sequence
3438import Control.Monad.Ref
3539import Data.ByteString (ByteString)
3640import Data.Dependent.Sum (DSum (..))
41+import Data.Foldable (for_)
3742import Data.IORef
3843import Data.Maybe
3944import Data.Monoid ((<>))
@@ -52,6 +57,104 @@ import qualified GHCJS.DOM.Types as DOM
5257import Reflex.Profiled
5358#endif
5459
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+
55158{-# INLINE mainWidget #-}
56159mainWidget :: (forall x. Widget x ()) -> JSM ()
57160mainWidget = mainWidget'
@@ -103,40 +206,54 @@ runDomHost = runSpiderHost
103206 . runProfiledM
104207#endif
105208
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)
107210
108211{-# INLINABLE attachWidget #-}
109212attachWidget :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> JSM a
110213attachWidget rootElement wv w = fst <$> attachWidget' rootElement wv w
111214
112−-- | Warning: `mainWidgetWithHead'` is provided only as performance tweak. It is expected to disappear in future releases.
113−mainWidgetWithHead' :: (a -> Widget () b, b -> Widget () a) -> JSM ()
114−mainWidgetWithHead' widgets = withJSContextSingletonMono $ \jsSing -> do
115− doc <- currentDocumentUnchecked
116− headElement <- getHeadUnchecked doc
117− headFragment <- createDocumentFragment doc
118− bodyElement <- getBodyUnchecked doc
119− bodyFragment <- createDocumentFragment doc
120− (events, fc) <- liftIO . attachWidget'' $ \events -> do
121− let (headWidget, bodyWidget) = widgets
215+{-# INLINABLE runImmediateWidgetWithHeadAndBody #-}
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 ()
222+runImmediateWidgetWithHeadAndBody app = withJSContextSingletonMono $ \jsSing -> do
223+ globalDoc <- currentDocumentUnchecked
224+ headElement <- getHeadUnchecked globalDoc
225+ bodyElement <- getBodyUnchecked globalDoc
226+ headFragment <- createDocumentFragment globalDoc
227+ bodyFragment <- createDocumentFragment globalDoc
228+ (events, fc) <- liftIO . attachImmediateWidget $ \hydrationMode events -> do
122229 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
123− let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT DomTimeline DomHost c
124− go w df = do
230+ let go :: forall c. DOM.DocumentFragment -> Widget () c -> FloatingWidget () c
231+ go df w = do
125232 unreadyChildren <- liftIO $ newIORef 0
126− let builderEnv = ImmediateDomBuilderEnv
127− { _immediateDomBuilderEnv_document = toDocument doc
128− , _immediateDomBuilderEnv_parent = toNode df
129− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
130− , _immediateDomBuilderEnv_commitAction = return () --TODO
233+ delayed <- liftIO $ newIORef $ pure ()
234+ let builderEnv = HydrationDomBuilderEnv
235+ { _hydrationDomBuilderEnv_document = globalDoc
236+ , _hydrationDomBuilderEnv_parent = Left $ toNode df
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
131242 }
132− runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
133− rec b <- go (headWidget a) headFragment
134− a <- go (bodyWidget b) bodyFragment
243+ lift $ runHydrationDomBuilderT w builderEnv events
244+ runWithJSContextSingleton (runPostBuildT (runTriggerEventT (app (go headFragment) (go bodyFragment)) events) postBuild) jsSing
135245 return (events, postBuildTriggerRef)
136246 replaceElementContents headElement headFragment
137247 replaceElementContents bodyElement bodyFragment
138248 liftIO $ processAsyncEvents events fc
139249
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+
140257replaceElementContents :: DOM.IsElement e => e -> DOM.DocumentFragment -> JSM ()
141258replaceElementContents e df = do
142259 setInnerHTML e ("" :: String)
@@ -148,16 +265,20 @@ attachWidget' :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> J
148265attachWidget' rootElement jsSing w = do
149266 doc <- getOwnerDocumentUnchecked rootElement
150267 df <- createDocumentFragment doc
151− ((a, events), fc) <- liftIO . attachWidget'' $ \events -> do
268+ ((a, events), fc) <- liftIO . attachImmediateWidget $ \hydrationMode events -> do
152269 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
153270 unreadyChildren <- liftIO $ newIORef 0
154− let builderEnv = ImmediateDomBuilderEnv
155− { _immediateDomBuilderEnv_document = toDocument doc
156− , _immediateDomBuilderEnv_parent = toNode df
157− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
158− , _immediateDomBuilderEnv_commitAction = return () --TODO
271+ delayed <- liftIO $ newIORef $ pure ()
272+ let builderEnv = HydrationDomBuilderEnv
273+ { _hydrationDomBuilderEnv_document = toDocument doc
274+ , _hydrationDomBuilderEnv_parent = Left $ toNode df
275+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
276+ , _hydrationDomBuilderEnv_commitAction = return () --TODO
277+ , _hydrationDomBuilderEnv_switchover = never
278+ , _hydrationDomBuilderEnv_delayed = delayed
279+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
159280 }
160− a <- runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
281+ a <- runWithJSContextSingleton (runPostBuildT (runHydrationDomBuilderT w builderEnv events) postBuild) jsSing
161282 return ((a, events), postBuildTriggerRef)
162283 replaceElementContents rootElement df
163284 liftIO $ processAsyncEvents events fc
@@ -165,12 +286,18 @@ attachWidget' rootElement jsSing w = do
165286
166287type EventChannel = Chan [DSum (EventTriggerRef DomTimeline) TriggerInvocation]
167288
168−{-# INLINABLE attachWidget'' #-}
169−attachWidget'' :: (EventChannel -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))) -> IO (a, FireCommand DomTimeline DomHost)
170−attachWidget'' w = do
289+{-# INLINABLE attachImmediateWidget #-}
290+attachImmediateWidget
291+ :: ( IORef HydrationMode
292+ -> EventChannel
293+ -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))
294+ )
295+ -> IO (a, FireCommand DomTimeline DomHost)
296+attachImmediateWidget w = do
297+ hydrationMode <- liftIO $ newIORef HydrationMode_Immediate
171298 events <- newChan
172299 runDomHost $ do
173− ((result, postBuildTriggerRef), fc@(FireCommand fire)) <- hostPerformEventT $ w events
300+ ((result, postBuildTriggerRef), fc@(FireCommand fire)) <- hostPerformEventT $ w hydrationMode events
174301 mPostBuildTrigger <- readRef postBuildTriggerRef
175302 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
176303 return (result, fc)
@@ -186,7 +313,6 @@ processAsyncEvents events (FireCommand fire) = void $ forkIO $ forever $ do
186313 liftIO $ forM_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb
187314 return ()
188315
189−
190316-- | Run a reflex-dom application inside of an existing DOM element with the given ID
191317mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> JSM ()
192318mainWidgetInElementById eid w = withJSContextSingleton $ \jsSing -> do
@@ -208,8 +334,13 @@ runApp' app = withJSContextSingleton $ \jsSing -> do
208334 body <- getBodyUnchecked doc
209335 win <- getDefaultViewUnchecked doc
210336 rec o <- attachWidget body jsSing $ do
211− w <- lift $ wrapWindow win $ _appOutput_windowConfig o
337+ w <- wrapWindow win $ _appOutput_windowConfig o
212338 app $ AppInput
213339 { _appInput_window = w
214340 }
215341 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 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE ConstraintKinds #-}
3−{-# LANGUAGE ExplicitForAll #-}
43{-# LANGUAGE FlexibleContexts #-}
54{-# LANGUAGE FlexibleInstances #-}
65{-# LANGUAGE MultiParamTypeClasses #-}
@@ -103,7 +102,6 @@ elConfig_attributes f (ElConfig a b) = (\b' -> ElConfig a b') <$> f b
103102{-# INLINE elConfig_attributes #-}
104103#endif
105104
106−--TODO: HasDocument is still not accounted for
107105type MonadWidgetConstraints t m =
108106 ( DomBuilder t m
109107 , DomBuilderSpace m ~ GhcjsDomSpace
reflex-dom-core/src/Reflex/Dom/Prerender.hs+290−49
@@ -1,82 +1,323 @@
11{-# LANGUAGE ConstraintKinds #-}
2−{-# LANGUAGE EmptyDataDecls #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE EmptyCase #-}
34{-# LANGUAGE FlexibleContexts #-}
45{-# LANGUAGE FlexibleInstances #-}
56{-# LANGUAGE FunctionalDependencies #-}
6−{-# LANGUAGE MultiParamTypeClasses #-}
7+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8+{-# LANGUAGE LambdaCase #-}
9+{-# LANGUAGE OverloadedStrings #-}
710{-# LANGUAGE Rank2Types #-}
11+{-# LANGUAGE RecursiveDo #-}
812{-# LANGUAGE ScopedTypeVariables #-}
913{-# LANGUAGE TypeFamilies #-}
10−{-# LANGUAGE TypeSynonymInstances #-}
1114{-# LANGUAGE UndecidableInstances #-}
15+{-# LANGUAGE UndecidableSuperClasses #-}
16+
17+-- | Render the first widget on the server, and the second on the client.
1218module Reflex.Dom.Prerender
1319 ( Prerender (..)
14− , prerender
20+ , prerender_
21+ , PrerenderClientConstraint
22+ , PrerenderBaseConstraints
1523 ) where
1624
25+import Control.Monad.Primitive (PrimMonad(..))
1726import 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
1932import Foreign.JavaScript.TH
2033import GHCJS.DOM.Types (MonadJSM)
21−import Reflex
22−import Reflex.Dom.Builder.InputDisabled
34+import Reflex hiding (askEvents)
35+import Reflex.Dom.Builder.Class
36+import Reflex.Dom.Builder.Hydratable
2337import Reflex.Dom.Builder.Immediate
38+import Reflex.Dom.Builder.InputDisabled
2439import Reflex.Dom.Builder.Static
2540import Reflex.Host.Class
41+import Data.IntMap.Strict (IntMap)
42+import qualified Data.IntMap.Strict as IntMap
2643
27−type PrerenderClientConstraint js m =
28− ( HasJS js m
29− , HasJS js (Performable m)
30− , MonadJSM m
31− , MonadJSM (Performable m)
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+
48+type PrerenderClientConstraint js t m =
49+ ( DomBuilder t m
50+ , DomBuilderSpace m ~ GhcjsDomSpace
51+ , DomRenderHook t m
52+ , HasDocument m
53+ , TriggerEvent t m
54+ , PrerenderBaseConstraints js t m
55+ )
56+
57+type PrerenderBaseConstraints js t m =
58+ ( HasJSContext (Performable m)
3259 , HasJSContext m
33− , HasJSContext (Performable m)
3460 , MonadFix m
35− , MonadFix (Performable 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
74+ , HasJS js (Performable m)
3675 )
3776
38−class Prerender js m | m -> js where
39− prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m))
77+-- | Render the first widget on the server, and the second on the client. The
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
40240
41−-- | Draw one widget when prerendering (e.g. server-side) and another when the
42−-- widget is fully instantiated. In a given execution of this function, there
43−-- will be exactly one invocation of exactly one of the arguments.
44−prerender :: forall js m a. Prerender js m => m a -> (PrerenderClientConstraint js m => m a) -> m a
45−prerender server client = case prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)) of
46− Nothing -> server
47− Just Dict -> client
241+instance (Prerender js t m, Monad m) => Prerender js t (ReaderT r m) where
242+ type Client (ReaderT r m) = ReaderT r (Client m)
243+ prerender server client = do
244+ r <- ask
245+ lift $ prerender (runReaderT server r) (runReaderT client r)
48246
49−instance ( HasJS js m
50− , HasJS js (Performable m)
51− , HasJSContext m
52− , HasJSContext (Performable m)
53− , MonadJSM m
54− , MonadJSM (Performable m)
55− , MonadFix m
56− , MonadFix (Performable m)
57− , ReflexHost t
58− ) => Prerender js (ImmediateDomBuilderT t m) where
59− prerenderClientDict = Just Dict
247+instance (Prerender js t m, Monad m, Reflex t, MonadFix m, Monoid w) => Prerender js t (DynamicWriterT t w m) where
248+ type Client (DynamicWriterT t w m) = DynamicWriterT t w (Client m)
249+ prerender server client = do
250+ x <- lift $ prerender (runDynamicWriterT server) (runDynamicWriterT client)
251+ let (a, w') = splitDynPure x
252+ w = join w'
253+ tellDyn w
254+ pure a
60255
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
62264
63−instance js ~ NoJavaScript => Prerender js (StaticDomBuilderT t m) where
64− prerenderClientDict = Nothing
265+instance (Prerender js t m, MonadFix m, Reflex t) => Prerender js t (RequesterT t request response m) where
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
65276
66−instance (Prerender js m, ReflexHost t) => Prerender js (PostBuildT t m) where
67− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
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
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
68285
69−instance Prerender js m => Prerender js (DynamicWriterT t w m) where
70− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
286+instance (Prerender js t m, Monad m) => Prerender js t (InputDisabledT m) where
287+ type Client (InputDisabledT m) = InputDisabledT (Client m)
288+ prerender (InputDisabledT server) (InputDisabledT client) = InputDisabledT $ prerender server client
71289
72−instance Prerender js m => Prerender js (ReaderT w m) where
73− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
290+instance (Prerender js t m, Monad m) => Prerender js t (HydratableT m) where
291+ type Client (HydratableT m) = HydratableT (Client m)
292+ prerender (HydratableT server) (HydratableT client) = HydratableT $ prerender server client
74293
75−instance Prerender js m => Prerender js (RequesterT t request response m) where
76− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
294+instance (Prerender js t m, Monad m, ReflexHost t) => Prerender js t (PostBuildT t m) where
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)
77299
78−instance Prerender js m => Prerender js (QueryT t q m) where
79− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
300+startMarker, endMarker :: Text
301+startMarker = "prerender/start"
302+endMarker = "prerender/end"
80303
81−instance Prerender js m => Prerender js (InputDisabledT m) where
82− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
304+deleteToPrerenderEnd :: (MonadIO m, MonadJSM m, Reflex t, MonadFix m) => DOM.Document -> HydrationRunnerT t m DOM.Comment
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
4646import Control.Monad.State
4747import Data.Aeson
4848import Data.ByteString (ByteString)
49+import Data.ByteString.Lazy (toStrict)
4950import Data.Default
5051import Data.IORef
5152import Data.JSString.Text
@@ -54,6 +55,7 @@ import Data.Text
5455import Data.Text.Encoding
5556import Foreign.JavaScript.Utils (jsonDecode)
5657import GHCJS.DOM.Types (runJSM, askJSM, MonadJSM, liftJSM, JSM)
58+import GHCJS.DOM.WebSocket (getReadyState)
5759import GHCJS.Marshal
5860import qualified Language.Javascript.JSaddle.Monad as JS (catch)
5961
@@ -100,7 +102,7 @@ webSocket' url config onRawMessage = do
100102 liftIO $ triggerEClose args
101103 _ <- liftIO $ atomically $ tryTakeTMVar isOpen
102104 liftIO $ writeIORef currentSocketRef Nothing
103− when (_webSocketConfig_reconnect config) $ do
105+ when (_webSocketConfig_reconnect config) $ forkJSM $ do
104106 liftIO $ threadDelay 1000000
105107 start
106108 start = do
@@ -126,9 +128,11 @@ webSocket' url config onRawMessage = do
126128 mws <- liftIO $ readIORef currentSocketRef
127129 success <- case mws of
128130 Nothing -> return False
129− Just ws -> runJSM ((webSocketSend ws payload >> return True)
130− `JS.catch`
131− (\(_ :: SomeException) -> return False)) ctx
131+ Just ws -> flip runJSM ctx $ do
132+ rs <- getReadyState $ unWebSocket ws
133+ if rs == 1
134+ then (webSocketSend ws payload >> return True) `JS.catch` (\(_ :: SomeException) -> return False)
135+ else return False
132136 unless success $ atomically $ unGetTQueue payloadQueue payload
133137 return $ RawWebSocket eRecv eOpen eError eClose
134138
@@ -137,9 +141,14 @@ textWebSocket url cfg = webSocket' url cfg (either (return . decodeUtf8) fromJSV
137141
138142jsonWebSocket :: (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))
139143jsonWebSocket 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 }
141145 return ws { _webSocket_recv = jsonDecode . textToJSString <$> _webSocket_recv ws }
142146
147+forkJSM :: JSM () -> JSM ()
148+forkJSM a = do
149+ jsm <- askJSM
150+ void $ liftIO $ forkIO $ runJSM a jsm
151+
143152#ifdef USE_TEMPLATE_HASKELL
144153makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig
145154makeLensesWith (lensRules & simpleLenses .~ True) ''RawWebSocket
reflex-dom-core/src/Reflex/Dom/WebSocket/Foreign.hs+37−23
@@ -12,7 +12,6 @@ module Reflex.Dom.WebSocket.Foreign
1212
1313import Prelude hiding (all, concat, concatMap, div, mapM, mapM_, sequence, span)
1414
15−import Control.Monad.Reader
1615import Data.Bifoldable
1716import Data.ByteString (ByteString)
1817import qualified Data.ByteString.Lazy as LBS
@@ -20,12 +19,11 @@ import Data.Text (Text)
2019import Data.Text.Encoding
2120import Foreign.JavaScript.Utils (bsFromMutableArrayBuffer, bsToArrayBuffer)
2221import GHCJS.DOM.CloseEvent
23−import GHCJS.DOM.EventM (on)
2422import GHCJS.DOM.MessageEvent
25−import GHCJS.DOM.Types (JSM, JSVal, liftJSM, fromJSValUnchecked)
26−import GHCJS.DOM.WebSocket (WebSocket)
23+import GHCJS.DOM.Types (JSM, JSVal, liftJSM, fromJSValUnchecked, WebSocket(..))
2724import qualified GHCJS.DOM.WebSocket as DOM
2825import GHCJS.Foreign (JSType(..), jsTypeOf)
26+import Language.Javascript.JSaddle (fun, eval, toJSVal, call)
2927import Language.Javascript.JSaddle.Helper (mutableArrayBufferFromJSVal)
3028import Language.Javascript.JSaddle.Types (ghcjsPure)
3129
@@ -64,25 +62,41 @@ newWebSocket
6462 -> ((Bool, Word, Text) -> JSM ()) -- onclose
6563 -> JSM JSWebSocket
6664newWebSocket _ url protocols onMessage onOpen onError onClose = do
67− ws <- DOM.newWebSocket url protocols
68− DOM.setBinaryType ws "arraybuffer"
69− _ <- on ws DOM.open $ liftJSM onOpen
70− _ <- on ws DOM.error $ liftJSM onError
71− _ <- on ws DOM.closeEvent $ do
72− e <- ask
73− wasClean <- getWasClean e
74− code <- getCode e
75− reason <- getReason e
76− liftJSM $ onClose (wasClean, code, reason)
77− _ <- on ws DOM.message $ do
78− e <- ask
79− d <- getData e
80− liftJSM $ ghcjsPure (jsTypeOf d) >>= \case
81− String -> onMessage $ Right d
82− _ -> do
83− ab <- mutableArrayBufferFromJSVal d
84− bsFromMutableArrayBuffer ab >>= onMessage . Left
85− return $ JSWebSocket ws
65+ let onOpenWrapped = fun $ \_ _ _ -> onOpen
66+ onErrorWrapped = fun $ \_ _ _ -> onError
67+ onCloseWrapped = fun $ \_ _ (e:_) -> do
68+ let e' = CloseEvent e
69+ wasClean <- getWasClean e'
70+ code <- getCode e'
71+ reason <- getReason e'
72+ liftJSM $ onClose (wasClean, code, reason)
73+ onMessageWrapped = fun $ \_ _ (e:_) -> do
74+ let e' = MessageEvent e
75+ d <- getData e'
76+ liftJSM $ ghcjsPure (jsTypeOf d) >>= \case
77+ String -> onMessage $ Right d
78+ _ -> do
79+ ab <- mutableArrayBufferFromJSVal d
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']
99+ return $ JSWebSocket $ WebSocket ws
86100
87101onBSMessage :: Either ByteString JSVal -> JSM ByteString
88102onBSMessage = either return $ fmap encodeUtf8 . fromJSValUnchecked
reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+55−44
@@ -10,12 +10,12 @@
1010{-# LANGUAGE ScopedTypeVariables #-}
1111{-# LANGUAGE TypeFamilies #-}
1212module Reflex.Dom.Widget.Basic
13− ( partitionMapBySetLT
14− , ChildResult (..)
15−
13+ (
1614 -- * Displaying Values
17− , text
15+ text
1816 , dynText
17+ , comment
18+ , dynComment
1919 , display
2020 , button
2121 , dyn
@@ -57,15 +57,16 @@ module Reflex.Dom.Widget.Basic
5757 , module Data.Map.Misc
5858 , module Reflex.Collection
5959 , module Reflex.Workflow
60+ , partitionMapBySetLT
6061 ) where
6162
63+import Reflex.Adjustable.Class
6264import Reflex.Class
6365import Reflex.Collection
6466import Reflex.Dom.Builder.Class
6567import Reflex.Dom.Class
6668import Reflex.Dynamic
6769import Reflex.Network
68−import Reflex.NotReady.Class
6970import Reflex.PostBuild.Class
7071import Reflex.Workflow
7172
@@ -94,6 +95,7 @@ import Prelude hiding (mapM, mapM_, sequence, sequence_)
9495-- No empty pieces will be included in the output.
9596
9697--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." #-}
9799partitionMapBySetLT :: forall k v. Ord k => Set k -> Map k v -> Map (Either k ()) (Map k v)
98100partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0
99101 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
106108 then go t geq
107109 else (Left h, lt) : go t geq
108110
109−newtype ChildResult t k a = ChildResult { unChildResult :: (a, Event t (Map k (Maybe (ChildResult t k a)))) }
110−
111+{-# INLINABLE text #-}
111112text :: DomBuilder t m => Text -> m ()
112113text t = void $ textNode $ def & textNodeConfig_initialContents .~ t
113114
@@ -121,9 +122,23 @@ dynText t = do
121122 ]
122123 notReadyUntil postBuild
123124
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+
124138display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
125139display = dynText . fmap (T.pack . show)
126140
141+{-# DEPRECATED button "Use 'elAttr'' in combination with 'domEvent' instead" #-}
127142button :: DomBuilder t m => Text -> m (Event t ())
128143button t = do
129144 (e, _) <- element "button" def $ text t
@@ -131,63 +146,73 @@ button t = do
131146
132147--TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'?
133148-- | 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.
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').
136−dyn :: (DomBuilder t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
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.
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').
153+dyn :: (Adjustable t m, NotReady t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
137154dyn = networkView
138155
139156-- | 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 ()
141158dyn_ = void . dyn
142159
143160-- | Given an initial widget and an Event of widget-creating actions, create a widget that is recreated whenever the Event fires.
144161-- 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.
146−widgetHold :: (DomBuilder t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a)
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').
163+widgetHold :: (Adjustable t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a)
147164widgetHold = networkHold
148165
149166-- | 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 ()
151168widgetHold_ z = void . widgetHold z
152169
153170-- | Create a DOM element
154−-- > el "div" (text "Hello World")
171+--
172+-- >>> el "div" (text "Hello World")
155173-- <div>Hello World</div>
156174{-# INLINABLE el #-}
157175el :: forall t m a. DomBuilder t m => Text -> m a -> m a
158176el elementTag child = snd <$> el' elementTag child
159177
160178-- | Create a DOM element with attributes
161−-- > elAttr "a" ("href" =: "http://google.com") (text "Google!")
162−-- <a href="http://google.com">Google!</a>
179+--
180+-- >>> elAttr "a" ("href" =: "https://reflex-frp.org") (text "Reflex-FRP!")
181+-- <a href="https://reflex-frp.org">Reflex-FRP!</a>
163182{-# INLINABLE elAttr #-}
164183elAttr :: forall t m a. DomBuilder t m => Text -> Map Text Text -> m a -> m a
165184elAttr elementTag attrs child = snd <$> elAttr' elementTag attrs child
166185
167186-- | Create a DOM element with classes
168−-- > elClass "div" "row" (return ())
187+--
188+-- >>> elClass "div" "row" (return ())
169189-- <div class="row"></div>
170190{-# INLINABLE elClass #-}
171191elClass :: forall t m a. DomBuilder t m => Text -> Text -> m a -> m a
172192elClass elementTag c child = snd <$> elClass' elementTag c child
173193
174194-- | Create a DOM element with Dynamic Attributes
175−-- > elClass "div" (constDyn ("class" =: "row")) (return ())
195+--
196+-- >>> elClass "div" (constDyn ("class" =: "row")) (return ())
176197-- <div class="row"></div>
177198{-# INLINABLE elDynAttr #-}
178199elDynAttr :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t (Map Text Text) -> m a -> m a
179200elDynAttr elementTag attrs child = snd <$> elDynAttr' elementTag attrs child
180201
181202-- | Create a DOM element with a Dynamic Class
182−-- > elDynClass "div" (constDyn "row") (return ())
203+--
204+-- >>> elDynClass "div" (constDyn "row") (return ())
183205-- <div class="row"></div>
184206{-# INLINABLE elDynClass #-}
185207elDynClass :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t Text -> m a -> m a
186208elDynClass elementTag c child = snd <$> elDynClass' elementTag c child
187209
188210-- | Create a DOM element and return the element
189−-- > do (e, _) <- el' "div" (text "Click")
190−-- > return $ domEvent Click e
211+--
212+-- @
213+-- do (e, _) <- el' "div" (text "Click")
214+-- return $ domEvent Click e
215+-- @
191216{-# INLINABLE el' #-}
192217el' :: forall t m a. DomBuilder t m => Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
193218el' elementTag = element elementTag def
@@ -247,43 +272,25 @@ dynamicAttributesToModifyAttributesWithInitial attrs0 d = do
247272 return $ if Map.null p then Nothing else Just p
248273 return modificationsNeeded
249274
250−--------------------------------------------------------------------------------
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−
275+{-# DEPRECATED Link "Will be removed when 'linkClass' and 'link' are removed. Follow those functions' deprecation instructions." #-}
272276newtype Link t
273277 = Link { _link_clicked :: Event t ()
274278 }
275279
280+{-# DEPRECATED linkClass "Use 'elAttr'' in combination with 'domEvent' for just clicks. Use 'routeLink' for Obelisk navigation" #-}
276281linkClass :: DomBuilder t m => Text -> Text -> m (Link t)
277282linkClass s c = do
278283 (l,_) <- elAttr' "a" ("class" =: c) $ text s
279284 return $ Link $ domEvent Click l
280285
286+{-# DEPRECATED link "Use 'elAttr'' in combination with 'domEvent' for just clicks. Use 'routeLink' for Obelisk navigation" #-}
281287link :: DomBuilder t m => Text -> m (Link t)
282288link s = linkClass s ""
283289
284290divClass :: forall t m a. DomBuilder t m => Text -> m a -> m a
285291divClass = elClass "div"
286292
293+{-# DEPRECATED dtdd "Use an application specific widget generating function" #-}
287294dtdd :: forall t m a. DomBuilder t m => Text -> m a -> m a
288295dtdd h w = do
289296 el "dt" $ text h
@@ -292,7 +299,9 @@ dtdd h w = do
292299blank :: forall m. Monad m => m ()
293300blank = return ()
294301
302+-- TODO: Move to an example project.
295303-- | A widget to display a table with static columns and dynamic rows.
304+{-# DEPRECATED tableDynAttr "Use an application specific widget generating function" #-}
296305tableDynAttr :: forall t m r k v. (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m)
297306 => Text -- ^ Class applied to <table> element
298307 -> [(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
308317 dAttrs <- rowAttrs k
309318 elDynAttr' "tr" dAttrs $ mapM (\x -> el "td" $ snd x k r) cols)
310319
320+-- TODO: Move to an example project.
311321-- | A widget to construct a tabbed view that shows only one of its child widgets at a time.
312322-- Creates a header bar containing a <ul> with one <li> per child; clicking a <li> displays
313323-- the corresponding child and hides all others.
324+{-# DEPRECATED tabDisplay "Use an application specific widget generating function" #-}
314325tabDisplay :: forall t m k. (MonadFix m, DomBuilder t m, MonadHold t m, PostBuild t m, Ord k)
315326 => Text -- ^ Class applied to <ul> element
316327 -> 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
5454import qualified GHCJS.DOM.Event as Event
5555import qualified GHCJS.DOM.HTMLInputElement as Input
5656
57+{-# DEPRECATED TextInput, _textInput_element, TextInputConfig, textInput "Use 'inputElement' directly" #-}
5758data TextInput t
5859 = TextInput { _textInput_value :: Dynamic t Text
5960 , _textInput_input :: Event t Text
@@ -161,6 +162,7 @@ rangeInput (RangeInputConfig initial eSetValue dAttrs) = do
161162 , _rangeInput_element = _inputElement_raw i
162163 }
163164
165+{-# DEPRECATED TextAreaConfig, TextArea, textArea "Use 'textAreaElement' directly" #-}
164166data TextAreaConfig t
165167 = TextAreaConfig { _textAreaConfig_initialValue :: Text
166168 , _textAreaConfig_setValue :: Event t Text
@@ -198,6 +200,7 @@ textArea (TextAreaConfig initial eSet attrs) = do
198200 , _textArea_element = _textAreaElement_raw i
199201 }
200202
203+{-# DEPRECATED CheckboxConfig, Checkbox, checkbox, checkboxView, CheckboxViewEventResultType, regularToCheckboxViewEventType, CheckboxViewEventResult "Use 'inputElement' directly" #-}
201204data CheckboxConfig t
202205 = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
203206 , _checkboxConfig_attributes :: Dynamic t (Map Text Text)
@@ -287,7 +290,7 @@ regularToCheckboxViewEventType en r = case en of
287290
288291newtype CheckboxViewEventResult en = CheckboxViewEventResult { unCheckboxViewEventResult :: CheckboxViewEventResultType en }
289292
290−--TODO
293+-- | Create a view only checkbox
291294{-# INLINABLE checkboxView #-}
292295checkboxView :: 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)
293296checkboxView dAttrs dValue = do
@@ -321,6 +324,7 @@ checkboxView dAttrs dValue = do
321324 i <- inputElement inputElementConfig
322325 return $ unCheckboxViewEventResult <$> select (_element_events $ _inputElement_element i) (WrapArg Click)
323326
327+{-# DEPRECATED FileInput, FileInputConfig, fileInput "Use 'inputElement' directly" #-}
324328data FileInput d t
325329 = FileInput { _fileInput_value :: Dynamic t [File]
326330 , _fileInput_element :: RawInputElement d
@@ -346,16 +350,10 @@ fileInput config = do
346350 & modifyAttributes .~ fmap mapKeysToAttributeName modifyAttrs
347351 & elementConfig_eventSpec . ghcjsEventSpec_filters .~ filters
348352 cfg = (def :: InputElementConfig EventResult t (DomBuilderSpace m)) & inputElementConfig_elementConfig .~ elCfg
349− eRaw <- 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
353+ input <- inputElement cfg
356354 return $ FileInput
357− { _fileInput_value = dValue
358− , _fileInput_element = e
355+ { _fileInput_value = _inputElement_files input
356+ , _fileInput_element = _inputElement_raw input
359357 }
360358
361359data Dropdown t k
@@ -687,41 +685,3 @@ instance HasValue (Dropdown t k) where
687685instance HasValue (Checkbox t) where
688686 type Value (Checkbox t) = Dynamic t Bool
689687 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
6464 let indexAndLength = fmap snd window
6565 return (indexAndLength, sel)
6666 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
6868 toViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
6969 "left" =: "0" <> "right" =: "0" <> "height" =: (T.pack (show h) <> "px")
7070 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
108108 uniqWindow <- holdUniqDyn window
109109 return (uniqWindow, result)
110110 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
112112 mkViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
113113 "left" =: "0" <> "right" =: "0" <> "height" =: (T.pack (show h) <> "px")
114114 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
142142 (win, m) <- virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder
143143 pb <- getPostBuild
144144 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) ->
146146 if winOffset > oldOffset && winOffset + winLimit < oldOffset + oldLimit
147147 then Nothing
148148 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)
3232import qualified GHCJS.DOM.Types as DOM
3333
3434-- | 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; }
3640resizeDetector :: (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)
3741resizeDetector = resizeDetectorWithStyle ""
3842
reflex-dom-core/src/Reflex/Dom/Xhr.hs+2−2
@@ -215,7 +215,7 @@ _xhrResponse_body :: XhrResponse -> Maybe Text
215215_xhrResponse_body = _xhrResponse_responseText
216216
217217{-# 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)
219219xhrResponse_body = lens _xhrResponse_responseText (\r t -> r { _xhrResponse_responseText = t })
220220
221221instance a ~ () => Default (XhrRequestConfig a) where
@@ -378,7 +378,7 @@ decodeText = decode . BL.fromStrict . encodeUtf8
378378
379379-- | Convenience function to decode JSON-encoded responses.
380380decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
381−decodeXhrResponse = join . fmap decodeText . _xhrResponse_responseText
381+decodeXhrResponse = decodeText <=< _xhrResponse_responseText
382382
383383#ifdef USE_TEMPLATE_HASKELL
384384concat <$> mapM makeLenses
reflex-dom-core/src/Reflex/Dom/Xhr/Foreign.hs−1
@@ -1,7 +1,6 @@
11{-# LANGUAGE FlexibleInstances #-}
22{-# LANGUAGE JavaScriptFFI #-}
33{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE TypeSynonymInstances #-}
54
65module Reflex.Dom.Xhr.Foreign (
76 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
88import Control.Monad
99import Control.Monad.IO.Class
1010import Data.Int
11−import GHC.Stats
11+import Data.Text as T
1212import Language.Javascript.JSaddle.Warp
1313import Reflex.Dom.Core
1414import Reflex.Time
1515import System.Exit
16−import System.IO.Temp
17−import System.Linux.Namespaces
1816import System.Mem
19−import System.Posix
2017import System.Process
2118
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+
2231-- In initial testing, the minimum live bytes count was 233128 and maximum was
2332-- 363712. Going over the maximum means the test has actually failed - we
2433-- probably have a memory leak; going under the minimum doesn't indicate a
@@ -38,14 +47,14 @@ failureLimit = 0
3847
3948main :: IO ()
4049main = do
41− uid <- getEffectiveUserID
42− handle (\(_ :: IOError) -> return ()) $ do -- If we run into an exception with sandboxing, just don't bother
43− unshare [User, Network]
44− writeUserMappings Nothing [UserMapping 0 uid 1]
45− callCommand "ip link set lo up ; ip addr"
46− mainThread <- myThreadId
47− withSystemTempDirectory "reflex-dom-core_test_gc" $ \tmp -> do
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'"
50+ handle (\(_ :: IOError) -> return ()) $ unshareNetork -- If we run into an exception with sandboxing, just don't bother
51+ withSandboxedChromeFlags True $ \chromeFlags -> do
52+ mainThread <- myThreadId
53+ browserProcess <- spawnCommand $ mconcat
54+ [ "echo 'Starting Chromium' ; chromium "
55+ , unpack $ T.unwords chromeFlags
56+ , " http://localhost:3911 ; echo 'Chromium exited'"
57+ ]
4958 let finishTest result = do
5059 interruptProcessGroupOf browserProcess
5160 throwTo mainThread result
@@ -63,7 +72,7 @@ main = do
6372 let f (!failures, !n) = liftIO $ if n < 3000
6473 then do performMajorGC
6574 threadDelay 5000 -- Wait a bit to allow requestAnimationFrame to call its callback sometimes; this value was experimentally determined
66− gcStats <- getGCStats
75+ gcStats <- getRTSStats
6776 print $ currentBytesUsed gcStats
6877 when (currentBytesUsed gcStats < minBytesAllowed) $ do
6978 putStrLn "FAILED: currentBytesUsed < minBytesAllowed"
reflex-dom-core/test/hlint.hs+2
@@ -16,9 +16,11 @@ main = do
1616 , "--ignore=Use unless"
1717 , "--ignore=Use if"
1818 , "--ignore=Use fewer imports"
19+ , "--ignore=Use list comprehension"
1920 , "--ignore=Unnecessary hiding" -- Interferes with cross-version compatibility
2021 , "--ignore=Use <$>"
2122 , "--ignore=Reduce duplication" --TODO: Re-enable this test
23+ , "--ignore=Use list comprehension"
2224 , "--cpp-define=USE_TEMPLATE_HASKELL"
2325 ]
2426 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
2828 seed <- randomIO
2929 mainWidgetWithHead' (\_ -> headW, \_ -> bodyW seed)
3030
31−titleW :: MonadWidget t m => m ()
31+titleW :: DomBuilder t m => m ()
3232titleW = text "Reflex-dom keyed"
3333
34−headW :: MonadWidget t m => m ()
34+headW :: DomBuilder t m => m ()
3535headW = do
3636 el "title" titleW
3737 elAttr "link" ("href" =: "/css/currentStyle.css" <> "rel" =: "stylesheet") blank
3838
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 ()
4040bodyW seed = divClass "main" $ divClass "container" $ mdo
4141 buttonEvents <- divClass "jumbotron" $ divClass "row" $ do
4242 divClass "col-md-6" $ el "h1" titleW
@@ -75,14 +75,14 @@ step (m, t) f = ((m', t'), dt)
7575 (m', dt) = f (m, t)
7676 t' = applyAlways dt t
7777
78−buttonW :: MonadWidget t m => Text -> Text -> a -> m (Event t a)
78+buttonW :: DomBuilder t m => Text -> Text -> a -> m (Event t a)
7979buttonW id txt val = divClass "col-sm-6 smallpad" $ buttonW' ("class" =: "btn btn-primary btn-block" <> "id" =: id)
8080 where
8181 buttonW' attrs = do
8282 (b, _) <- elAttr' "button" attrs $ text txt
8383 pure $ val <$ domEvent Click b
8484
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))
8686tableW diff = do
8787 elClass "table" "table table-hover table-striped test-data" $ do
8888 el "tbody" $ do
@@ -90,7 +90,7 @@ tableW diff = do
9090 rowEvents <- holdIncremental v0 v'
9191 return $ mergeIntIncremental rowEvents
9292
93−rowW :: MonadWidget t m => Row -> m (Event t Step)
93+rowW :: DomBuilder t m => Row -> m (Event t Step)
9494rowW row = elClass "tr" (if selected row then "danger" else "") $
9595 do
9696 elClass "td" "col-md-1" $ do
@@ -102,7 +102,7 @@ rowW row = elClass "tr" (if selected row then "danger" else "") $
102102 where
103103 tagClick f el = f row <$ (domEvent Click el)
104104
105−deleteW :: MonadWidget t m => m ()
105+deleteW :: DomBuilder t m => m ()
106106deleteW = el "a" $ elAttr "span" ("aria-hidden" =: "true" <> "class" =: "glyphicon glyphicon-remove") blank
107107
108108
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;
88import android.view.ViewGroup.LayoutParams;
99import android.view.Window;
1010import android.webkit.CookieManager;
11+import android.webkit.GeolocationPermissions;
1112import android.webkit.JavascriptInterface;
1213import android.webkit.MimeTypeMap;
1314import android.webkit.PermissionRequest;
@@ -21,6 +22,8 @@ import android.graphics.Bitmap;
2122import java.io.IOException;
2223import java.io.InputStream;
2324import android.content.Intent;
25+import android.content.ActivityNotFoundException;
26+import java.util.concurrent.atomic.AtomicBoolean;
2427
2528import java.nio.charset.StandardCharsets;
2629
@@ -44,11 +47,17 @@ public class MainWidget {
4447 wv.setWebContentsDebuggingEnabled(true);
4548 // allow video to play without user interaction
4649 wv.getSettings().setMediaPlaybackRequiresUserGesture(false);
50+ final AtomicBoolean jsaddleLoaded = new AtomicBoolean(false);
4751
4852 wv.setWebViewClient(new WebViewClient() {
4953 @Override
5054 public void onPageFinished(WebView _view, String _url) {
51− wv.evaluateJavascript(initialJS, null);
55+ Log.i("reflex", "onPageFinished");
56+ boolean alreadyLoaded = jsaddleLoaded.getAndSet(true);
57+ if(!alreadyLoaded) {
58+ Log.i("reflex", "loading jsaddle");
59+ wv.evaluateJavascript(initialJS, null);
60+ }
5261 }
5362
5463 // Re-route / to /android_asset
@@ -79,7 +88,12 @@ public class MainWidget {
7988 @Override
8089 public boolean shouldOverrideUrlLoading(WebView view, String url) {
8190 if( url != null && !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
82− view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
91+ try {
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+ }
8397 return true;
8498 } else {
8599 return false;
@@ -109,6 +123,11 @@ public class MainWidget {
109123 public Bitmap getDefaultVideoPoster() {
110124 return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
111125 }
126+
127+ @Override
128+ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
129+ callback.invoke(origin, true, false);
130+ }
112131 });
113132
114133 wv.addJavascriptInterface(new JSaddleCallbacks(jsaddleCallbacks), "jsaddle");
reflex-dom/reflex-dom.cabal+24−26
@@ -1,7 +1,10 @@
11Name: reflex-dom
2−Version: 0.4
2+Version: 0.5.2.0
33Synopsis: Functional Reactive Web Apps with Reflex
4−Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
4+Description:
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>.
58License: BSD3
69License-file: LICENSE
710Author: Ryan Trinkle
@@ -10,12 +13,9 @@ Stability: Experimental
1013Category: FRP, Web, GUI, HTML, Javascript, Reactive, Reactivity, User Interfaces, User-interface
1114Build-type: Simple
1215cabal-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
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
16+extra-source-files:
17+ java/org/reflexfrp/reflexdom/MainWidget.java
18+ ChangeLog.md
1919
2020flag use-warp
2121 description: Use jsaddle-warp server
@@ -41,14 +41,9 @@ flag build-examples
4141 default: False
4242 manual: True
4343
44−flag android
45− description: Build an Android app
46− default: False
47− manual: True
48−
4944library
5045 hs-source-dirs: src
51− if flag(android)
46+ if os(android)
5247 hs-source-dirs: src-android
5348 other-modules: Reflex.Dom.Android.MainWidget
5449 build-depends:
@@ -58,31 +53,32 @@ library
5853 jsaddle
5954 c-sources: cbits/MainWidget.c
6055 include-dirs: cbits/include
56+ install-includes: MainWidget.h
6157 cpp-options: -DANDROID
6258 build-tools: hsc2hs
6359 build-depends:
64− base >= 4.7 && < 4.11,
60+ base >= 4.7 && < 4.13,
6561 bytestring == 0.10.*,
66− reflex == 0.5.*,
67− reflex-dom-core >=0.4 && <0.5,
62+ reflex >= 0.6.2,
63+ reflex-dom-core >=0.5.2 && <0.6,
6864 text == 1.2.*
6965 if !impl(ghcjs)
70− if os(osx) || os(ios)
66+ if flag(use-warp) && (os(linux) || os(osx))
7167 build-depends:
72− data-default == 0.7.*,
7368 jsaddle,
74− jsaddle-wkwebview
69+ jsaddle-warp
7570 else
76− if flag(use-warp)
71+ if os(osx) || os(ios)
7772 build-depends:
73+ data-default == 0.7.*,
7874 jsaddle,
79− jsaddle-warp
75+ jsaddle-wkwebview
8076 else
8177 if flag(webkit2gtk)
8278 build-depends:
8379 jsaddle-webkit2gtk
8480 else
85− if !flag(android)
81+ if !os(android)
8682 build-depends:
8783 jsaddle-webkit2gtk
8884
@@ -114,6 +110,7 @@ library
114110 ghc-options: -fexpose-all-unfoldings
115111 if flag(use-reflex-optimizer)
116112 ghc-options: -fplugin=Reflex.Optimizer
113+ default-language: Haskell2010
117114
118115executable sortableList
119116 build-depends: base
@@ -130,13 +127,13 @@ executable sortableList
130127 hs-source-dirs: examples
131128 main-is: sortableList.hs
132129 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans -fspecialise-aggressively
133− ghc-prof-options: -fprof-auto
134130 if !flag(build-examples)
135131 buildable: False
136132 if flag(expose-all-unfoldings)
137133 ghc-options: -fexpose-all-unfoldings
138134 if flag(use-reflex-optimizer)
139135 ghc-options: -fplugin=Reflex.Optimizer
136+ default-language: Haskell2010
140137
141138executable benchmark
142139 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
145142 ghc-options: -O2 -fspecialise-aggressively
146143 if !impl(ghcjs)
147144 buildable: False
145+ default-language: Haskell2010
148146
149147executable krausest
150148 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
152150 main-is: krausest.hs
153151 ghc-options: -O2 -fspecialise-aggressively
154152 if impl(ghcjs)
155− ghc-options: -dedupe
153+ ghcjs-options: -dedupe
156154 cpp-options: -DGHCJS_BROWSER -DGHCJS_GC_INTERVAL=60000
157155 else
158156 buildable: False
159− type: exitcode-stdio-1.0
157+ default-language: Haskell2010
160158
161159source-repository head
162160 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 #-}
15module Reflex.Dom (module X) where
26
37import Foreign.JavaScript.Orphans ()
48import Reflex.Dom.Core as X hiding (mainWidget, mainWidgetInElementById, mainWidgetWithCss,
5− mainWidgetWithHead, mainWidgetWithHead', runApp')
9+ mainWidgetWithHead, mainWidgetWithHead', runApp',
10+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
611import Reflex.Dom.Internal as X
reflex-dom/src/Reflex/Dom/Internal.hs+37−12
@@ -1,21 +1,27 @@
11{-# LANGUAGE CPP #-}
2+{-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE OverloadedStrings #-}
34{-# LANGUAGE RankNTypes #-}
45{-# LANGUAGE TypeFamilies #-}
56module Reflex.Dom.Internal
6− (module Main, run, mainWidget, mainWidgetWithHead, mainWidgetWithCss,
7− mainWidgetWithHead', mainWidgetInElementById, runApp') where
7+ ( module Main
8+ , run
9+ , mainWidget
10+ , mainWidgetWithHead, mainWidgetWithCss, mainWidgetWithHead', mainWidgetInElementById, runApp'
11+ , mainHydrationWidgetWithHead, mainHydrationWidgetWithHead'
12+ ) where
813
914import Data.ByteString (ByteString)
1015import Data.Text (Text)
11−import Reflex.Spider (Spider)
1216import Reflex.Dom.Core (Widget)
1317import Reflex.Dom.Main as Main hiding
1418 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
15− mainWidgetWithHead', mainWidgetInElementById, runApp')
19+ mainWidgetWithHead', mainWidgetInElementById, runApp',
20+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
1621import qualified Reflex.Dom.Main as Main
1722 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
18− mainWidgetWithHead', mainWidgetInElementById, runApp')
23+ mainWidgetWithHead', mainWidgetInElementById, runApp',
24+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
1925
2026#if defined(ghcjs_HOST_OS)
2127run :: a -> a
@@ -33,15 +39,26 @@ run jsm = do
3339 putStrLn $ "Running jsaddle-warp server on port " <> show port
3440 JW.run port jsm
3541#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 ((<>))
3745import 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)
3948
40−--TODO: Eliminate this; it is needed because otherwise the app's base
41−--URL will be set to "about:blank" due to jsaddleMain rather than
42−--jsaddleMainFile being used
49+-- TODO: upstream to jsaddle-wkwebview
4350run :: 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
4562#elif defined(ANDROID)
4663import Android.HaskellActivity
4764import Control.Monad
@@ -87,6 +104,14 @@ mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> IO ()
87104mainWidgetInElementById eid w = run $ Main.mainWidgetInElementById eid w
88105{-# INLINE mainWidgetInElementById #-}
89106
90−runApp' :: (t ~ Spider) => (forall x. AppInput t -> Widget x (AppOutput t)) -> IO ()
107+runApp' :: (forall x. AppInput DomTimeline -> Widget x (AppOutput DomTimeline)) -> IO ()
91108runApp' app = run $ Main.runApp' app
92109{-# 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; }