packages feed

miso 0.21.2.0 → 1.0.0.0

raw patch · 17 files changed

+599/−651 lines, 17 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Miso.Html: data_ :: MisoString -> MisoString -> Attribute action
+ Miso.Html.Property: data_ :: MisoString -> MisoString -> Attribute action
+ Miso.Router: instance Miso.Router.HasRouter sublayout => Miso.Router.HasRouter (Servant.API.Header.Header sym x Servant.API.Sub.:> sublayout)
+ Miso.Router: type family RouteT layout a :: *
+ Miso.String: instance Miso.String.ToMisoString GHC.Types.Double
+ Miso.String: instance Miso.String.ToMisoString GHC.Types.Float
+ Miso.String: instance Miso.String.ToMisoString GHC.Types.Int
+ Miso.String: instance Miso.String.ToMisoString GHC.Types.Word
+ Miso.TypeLevel: type family ToServerRoutes (layout :: k) (wrapper :: * -> *) (action :: *) :: k
- Miso.Event.Decoder: Decoder :: Value -> Parser a -> DecodeTarget -> Decoder a
+ Miso.Event.Decoder: Decoder :: (Value -> Parser a) -> DecodeTarget -> Decoder a
- Miso.Html: [VNode] :: {vType :: Text Element type (i.e. "div", "a", "p"), vNs :: NS HTML or SVG, vProps :: Props Fields present on DOM Node, vKey :: Maybe Key Key used for child swap patch, vChildren :: Vector (VTree action) Child nodes} -> VTree action
+ Miso.Html: [VNode] :: {vType :: Text " Element type (i.e. "div", "a", "p")", vNs :: NS " HTML or SVG", vProps :: Props " Fields present on DOM Node", vKey :: Maybe Key " Key used for child swap patch", vChildren :: Vector (VTree action) " Child nodes"} -> VTree action
- Miso.Html: [VText] :: {vText :: Text TextNode content} -> VTree action
+ Miso.Html: [VText] :: {vText :: Text " TextNode content"} -> VTree action

Files

README.md view
@@ -1,5 +1,6 @@ <h1 align="center">miso</h1> <p align="center">+ <a href="https://haskell-miso.org">   <img width=10% src="https://emojipedia-us.s3.amazonaws.com/thumbs/240/apple/96/steaming-bowl_1f35c.png">    </a>@@ -27,21 +28,12 @@   </a> </p> -<p align="center">-  <a href="https://saucelabs.com/u/dmjio">-    <img src="https://saucelabs.com/browser-matrix/dmjio.svg" alt="Sauce Test Status"/>-  </a>-</p>- **Miso** is a small "[isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)" [Haskell](https://www.haskell.org/) front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events, Websockets, type-safe [servant](https://haskell-servant.github.io/)-style routing and an extensible Subscription-based subsystem. Inspired by [Elm](http://elm-lang.org/), [Redux](http://redux.js.org/) and [Bobril](http://github.com/bobris/bobril). **Miso** is pure by default, but side effects (like `XHR`) can be introduced into the system via the `Effect` data type. **Miso** makes heavy use of the [GHCJS](https://github.com/ghcjs/ghcjs) FFI and therefore has minimal dependencies. **Miso** can be considered a shallow [embedded domain-specific language](https://wiki.haskell.org/Embedded_domain_specific_language) for modern web programming.  ## Table of Contents - [Quick Start](#quick-start)   - [Begin](#begin)-  - [Stack](#stack)   - [Nix](#nix)-  - [Cabal](#cabal)-  - [GHCJSi Caveats](#ghcjsi-caveats)   - [Architecture](#architecture) - [Examples](#examples)   - [TodoMVC](#todomvc)@@ -49,6 +41,7 @@   - [2048](#2048)   - [Snake](#snake)   - [Mario](#mario)+  - [Miso Plane (Flappy Birds)](#miso-plane-flappy-birds)   - [Websocket](#websocket)   - [SSE](#sse)   - [XHR](#xhr)@@ -74,152 +67,35 @@ - [License](#license)  ## Quick start-To get started quickly building applications, we recommend using the [`stack`](https://docs.haskellstack.org/en/stable/README/) or [`nix`](https://nixos.org/nix) package managers. Obtaining [`GHCJS`](https://github.com/ghcjs/ghcjs) is required as a prerequisite. `stack` and `nix` make this process easy, if you're using `cabal` we assume you have [obtained `GHCJS`](https://github.com/ghcjs/ghcjs#installation) by other means.--All source code depicted below for the quick start app is available [here](https://github.com/dmjio/miso/tree/master/sample-app).+To get started quickly building applications, we recommend using the [`nix`](https://nixos.org/nix) package manager with miso's binary cache provided by [`cachix`](https://haskell-miso.cachix.org/). It is possible to use [`stack`](https://docs.haskellstack.org/en/stable/README/) to build GHCJS projects, but support for procuring `GHCJS` has been removed [as of stack 2.0](https://github.com/commercialhaskell/stack/issues/4086). `nix` is used to procure a working version of `GHCJS`. If you're using `cabal` we assume you have [obtained `GHCJS`](https://github.com/ghcjs/ghcjs#installation) by other means. All source code depicted below for the quick start app is available [here](https://github.com/dmjio/miso/tree/master/sample-app).  ### Begin-We recommend using `nix` when working with `miso`, but it is just as fine to use `stack`.-To build the sample-app with `nix`, execute the command below:--```bash-git clone https://github.com/dmjio/miso && cd miso/sample-app && nix-build-```--To develop with `nix` run the below command (this will put you into a shell where you can iteratively develop the project with `cabal build`).--```bash-git clone https://github.com/dmjio/miso && cd miso/sample-app && nix-shell -A env-```--For more information on using `nix` w/ `miso`, see the [`nix` section below](#nix)--To build the sample-app with `stack`, execute the command below:-```bash-git clone https://github.com/dmjio/miso && cd miso/sample-app && stack setup && stack build-```--Note: It's important to ensure that you don't have a global `cabal-install` present on your system. This could cause build problems. If you see an error like this (below), try deleting your global `cabal-install`.+To build the sample-app with `nix`, execute the commands below:  ```bash-exit status: 1-stderr: solver must be one of: modular-CallStack (from HasCallStack):-  error, called at libraries/Cabal/Cabal/Distribution/ReadE.hs:46:24 in Cabal-2.0.1.0:Distribution.ReadE-```---For more information on using `stack` w/ `miso`, see the [`stack` section below](#stack)---### Stack-In the `miso` repository there is a [folder named `stack`](https://github.com/dmjio/miso/tree/master/stack) with "known to work" configurations for `GHCJS`. One stack file exists for both the `7.10.3` and `8.0.1` versions of `GHCJS`. In general, we recommend developing with the `7.10.3` version since it currently supports `GHCJSi` (a REPL that connects to the browser by way of a [`nodejs`](https://nodejs.org/en/) web server using [`socket.io`](https://socket.io/)) and building with the `8.0.1` version (if possible). For more information on using `stack` with `GHCJS`, please consult the [GHCJS section of the `stack` docs](https://docs.haskellstack.org/en/stable/ghcjs/).--To begin, create the following directory layout-```bash-➜  mkdir app && touch app/{Main.hs,app.cabal,stack.yaml} && tree app-app-|-- Main.hs-|-- app.cabal-`-- stack.yaml-```--Add a `stack.yaml` file that uses a recent version of `miso`.-```bash-➜  cat app/stack.yaml-resolver: lts-6.30-compiler: ghcjs-0.2.0.9006030_ghc-7.10.3-compiler-check: match-exact--packages:- - '.'-extra-deps:- - miso-0.18.0.0--setup-info:-  ghcjs:-	source:-	  ghcjs-0.2.0.9006030_ghc-7.10.3:-		 url: http://ghcjs.tolysz.org/lts-6.30-9006030.tar.gz-		 sha1: 2371e2ffe9e8781808b7a04313e6a0065b64ee51-```--Add a `cabal` file-```bash-➜  cat app/*.cabal-name:                app-version:             0.1.0.0-synopsis:            First miso app-category:            Web-build-type:          Simple-cabal-version:       >=1.10--executable app-  main-is:             Main.hs-  build-depends:       base, miso-  default-language:    Haskell2010-```--Add the source from [Sample Application](#sample-application) to `app/Main.hs`--Run `stack setup`. This might take a long time, since it will have to build `GHCJS`.-```-stack setup-```--Run `stack build` to get the static assets-```-stack build-```--See the result-```-open $(stack path --local-install-root)/bin/app.jsexe/index.html-```--Using GHCJSi-```-stack ghci-```--If that warns with `socket.io not found, browser session not available`, you'll need to install `socket.io`-```-npm install socket.io-```--and update your `NODE_PATH`-```-export NODE_PATH=$(pwd)/node_modules+(optional) nix-env -iA cachix -f https://cachix.org/api/v1/install+(optional) cachix use haskell-miso+git clone https://github.com/dmjio/miso+cd miso/sample-app+nix-build+open ./result/bin/app.jsexe/index.html ``` -Now you should be connected, and the app viewable in `GHCJSi` (open http://localhost:6400).-```bash-➜  stack ghci-app-0.1.0.0: initial-build-steps (exe)-Configuring GHCi with the following packages: app-GHCJSi, version 0.2.0.9006020-7.10.3: http://www.github.com/ghcjs/ghcjs/  :? for help-[1 of 1] Compiling Main             ( /Users/david/Desktop/miso/sample-app/Main.hs, interpreted )-socket.io found, browser session available at http://localhost:6400-Ok, modules loaded: Main.-*Main> main-browser connected, code runs in browser from now on-```+The above commands will add miso's binary cache to your nix installation (support for both Linux and OSX).+`nix-build` will fetch the dependencies from miso's cache and build the sample application.  ### Nix `Nix` is a more powerful option for building web applications with `miso` since it encompasses development workflow, configuration management, and deployment. The source code for [`haskell-miso.org`](https://github.com/dmjio/miso/tree/master/examples/haskell-miso.org) is an example of this.  If unfamiliar with `nix`, we recommend [@Gabriel439](https://github.com/Gabriel439)'s ["Nix and Haskell in production"](https://github.com/Gabriel439/haskell-nix) guide. -To get started, we will use the [`cabal2nix`](https://github.com/NixOS/cabal2nix) tool to convert our `Cabal` file into a `nix` derivation (named `app.nix`). We'll then write a file named `default.nix`, which is used for building our project (via `nix-build`) and development (via `nix-shell`).- To begin, make the following directory layout: ```bash-➜  mkdir app && touch app/{Main.hs,app.cabal,default.nix,app.nix} && tree app+➜  mkdir app && touch app/{Main.hs,app.cabal,default.nix} && tree app app |-- Main.hs |-- app.cabal-|-- default.nix-`-- app.nix+`-- default.nix ```  Add a `cabal` file@@ -234,50 +110,21 @@  executable app   main-is:             Main.hs+  ghcjs-options:+    -dedupe   build-depends:       base, miso   default-language:    Haskell2010 ``` -Use [`cabal2nix`](https://github.com/NixOS/cabal2nix) to generate a file named `app.nix`-that looks like below.-```bash-➜  cabal2nix . --compiler ghcjs > app.nix-➜  cat app.nix-```+Write a `default.nix` (this will fetch a recent version of `miso`). `miso` will provide you with a working `nixpkgs` named `pkgs`. `callCabal2nix` will automatically produce a nix expression that builds your cabal file.  ```nix-{ mkDerivation, base, miso, stdenv }:-mkDerivation {-  pname = "app";-  version = "0.1.0.0";-  src = ./.;-  isLibrary = false;-  isExecutable = true;-  executableHaskellDepends = [ base miso ];-  description = "First miso app";-  license = stdenv.lib.licenses.bsd3;-}-```--Write a `default.nix` (which calls `app.nix`), this fetches a recent version of `miso`.-```nix-{ pkgs ? import ((import <nixpkgs> {}).fetchFromGitHub {-	owner = "NixOS";-	repo = "nixpkgs";-	rev = "a0aeb23";-	sha256 = "04dgg0f2839c1kvlhc45hcksmjzr8a22q1bgfnrx71935ilxl33d";-  }){}-}:-let-  result = import (pkgs.fetchFromGitHub {-	owner = "dmjio";-	repo = "miso";-	sha256 = "1l1gwzzqlvvcmg70jjrwc5ijv1vb6y5ljqkh7rxxq7hkyxpjyx9q";-	rev = "95f6bc9b1ae6230b110358a82b6a573806f272c2";-  }) {};-in pkgs.haskell.packages.ghcjs.callPackage ./app.nix {-  miso = result.miso-ghcjs;-}+with (import (builtins.fetchTarball {+  url = "https://github.com/dmjio/miso/archive/b4c473f3ed6d6251ea7b8b489fc50076ac8d9b70.tar.gz";+  sha256 = "11rby2s0hxbl28a4fcwdm9lcbjfysv862xd6b9jy0rgl63dh51i3";+}) {});+with pkgs.haskell.packages;+ghcjs.callCabal2nix "app" ./. {} ```  Add the source from [Sample Application](#sample-application) to `app/Main.hs`@@ -292,7 +139,7 @@ open ./result/bin/app.jsexe/index.html ``` -For development with `nix`, it's important to have `cabal` present for building. This command will make it available in your `PATH`.+For development with `nix`, it can be nice to have `cabal` present for building. This command will make it available in your `PATH`. ``` nix-env -iA cabal-install -f '<nixpkgs>' ```@@ -302,37 +149,20 @@ nix-shell -A env ``` -To open `GHCJSi` (`NODE_PATH` should already be set properly)+To view the dependencies for your project, call `ghcjs-pkg list` when inside the shell. ```-$ cabal configure --ghcjs-$ cabal repl-Package has never been configured. Configuring with default flags. If this-fails, please run configure manually.-Resolving dependencies...-Configuring app-0.1.0.0...-Preprocessing executable 'app' for app-0.1.0.0...-GHCJSi, version 0.2.0-7.10.3: http://www.github.com/ghcjs/ghcjs/  :? for help-[1 of 1] Compiling Main             ( Main.hs, interpreted )-Ok, modules loaded: Main.-*Main>-browser connected, code runs in browser from now on+nix-shell -A env --run 'ghcjs-pkg list' ``` -### Cabal-The latest stable version of `miso` will be available on Hackage.-To build with cabal, we assume `ghcjs` is in your `PATH` and `ghcjs-base` is present in your `ghcjs-pkg` list.-```bash-cabal sandbox init-cabal install --ghcjs-cabal build-open dist/build/app/app.jsexe/index.html+To build the project with `cabal` after entering the `nix-shell` ```+nix-shell -A env --run 'cabal configure --ghcjs && cabal build`+``` -### GHCJSi Caveats-If you run `main` in `GHCJSi`, interrupt it and then run it again, you-will end up with two copies of your app displayed above each other. As-a workaround, you can use `clearBody >> main` which will completely-clear the document body before rendering your application.+For incremental development inside of the `nix-shell` we recommend using a tool like [`entr`](http://eradman.com/entrproject/) to automatically rebuild on file changes, or roll your own solution with `inotify`.+```+ag -l | entr 'cabal build'+```  ### Architecture For constructing client and server applications, we recommend using one `cabal` file with two executable sections, where the `buildable` attribute set is contingent on the compiler. An example of this layout is [here](https://github.com/dmjio/miso/blob/master/examples/haskell-miso.org/haskell-miso.cabal#L16-L60). For more info on how to use `stack` with a `client`/`server` setup, see this [link](https://docs.haskellstack.org/en/stable/ghcjs/#project-with-both-client-and-server). For more information on how to use `nix` with a `client`/`server` setup, see the [nix scripts](https://github.com/dmjio/miso/blob/master/examples/haskell-miso.org/default.nix) for [https://haskell-miso.org](https://haskell-miso.org).@@ -354,6 +184,9 @@ ### Mario   - [Link](https://mario.haskell-miso.org/) / [Source](https://github.com/dmjio/miso/blob/master/examples/mario/Main.hs) +### Miso Plane (Flappy Birds)+  - [Link](https://miso-plane.lermex.net/) / [Source](https://github.com/Lermex/miso-plane)+ ### Websocket   - [Link](https://websocket.haskell-miso.org/) / [Source](https://github.com/dmjio/miso/blob/master/examples/websocket/Main.hs) @@ -449,39 +282,33 @@  The easiest way to build the examples is with the [`nix`](https://nixos.org/nix/) package manager ```-git clone https://github.com/dmjio/miso && cd miso && nix-build+git clone https://github.com/dmjio/miso && cd miso && nix-build -A miso-ghcjs ```  This will build all examples and documentation into a folder named `result` ```-➜  miso git:(master) ✗ tree result -d-result-|-- doc-|   |-- x86_64-osx-ghc-8.0.2-|   |   `-- miso-0.2.0.0-|   |       `-- html-|   |           `-- src-|   `-- x86_64-osx-ghcjs-0.2.0-ghc7_10_3-|       `-- miso-0.2.0.0-|           `-- html-|               `-- src-|-- examples-|   |-- mario.jsexe-|   |   `-- imgs-|   |       |-- jump-|   |       |-- stand-|   |       `-- walk-|   |-- router.jsexe-|   |-- simple.jsexe-|   |-- tests.jsexe-|   |-- todo-mvc.jsexe-|   `-- websocket.jsexe+➜  miso git:(master) ✗ tree -d ./result/bin+./result/bin+|-- canvas2d.jsexe+|-- compose-update.jsexe+|-- file-reader.jsexe+|-- mario.jsexe+|   `-- imgs+|-- mathml.jsexe+|-- router.jsexe+|-- simple.jsexe+|-- svg.jsexe+|-- tests.jsexe+|-- threejs.jsexe+|-- todo-mvc.jsexe+|-- websocket.jsexe+`-- xhr.jsexe ```  To see examples, we recommend hosting them with a webserver  ```-cd result/examples/todo-mvc.jsexe && python -m SimpleHTTPServer+cd result/bin/todo-mvc.jsexe && nix-shell -p python --run 'python -m SimpleHTTPServer' Serving HTTP on 0.0.0.0 port 8000 ... ``` @@ -510,25 +337,14 @@  ## Pinning nixpkgs -By default `miso` uses a known-to-work, pinned version of [`nixpkgs`](https://github.com/dmjio/miso/blob/master/default.nix#L1-L6).-To override this to your system's version of `nixpkgs` write:--```-nix-build --arg nixpkgs 'import <nixpkgs> {}'-```+By default `miso` uses a known-to-work, pinned version of [`nixpkgs`](https://github.com/dmjio/miso/blob/master/nixpkgs.json).  ## Binary cache -`nix` users on a Linux distro can take advantage of a [binary cache](https://hydra.dmj.io/nix-cache-info) for faster builds. To use the binary cache simply append `https://hydra.dmj.io/nix-cache-info` during all `nix-shell` and/or `nix-build` invocations.+`nix` users on a Linux or OSX distro can take advantage of a [binary cache](https://hydra.dmj.io/nix-cache-info) for faster builds. To use the binary cache follow the instructions on `https://haskell-miso.cachix.org/`.  ```bash-nix-build --option extra-binary-caches https://hydra.dmj.io-```--Alternatively, add `https://hydra.dmj.io` to your list of local binary caches in `nix.conf` (usually found in `/etc/nix/nix.conf`), and it will automatically be used on all invocations of `nix-build` and/or `nix-shell`.--```-binary-caches = https://hydra.dmj.io/ https://cache.nixos.org/+cachix use haskell-miso ```  ## Benchmarks
frontend-src/Miso.hs view
@@ -34,6 +34,7 @@ import           Data.IORef import           Data.List import           Data.Sequence ((|>))+import           System.Mem.StableName import qualified Data.Sequence as S import qualified JavaScript.Object.Internal as OI @@ -102,7 +103,9 @@         let (Acc newModel effects) = foldl' (foldEffects writeEvent update)                                             (Acc oldModel (pure ())) actions         effects-        when (oldModel /= newModel) $ do+        oldName <- liftIO $ oldModel `seq` makeStableName oldModel+        newName <- liftIO $ newModel `seq` makeStableName newModel+        when (oldName /= newName && oldModel /= newModel) $ do           swapCallbacks           newVTree <- runView (view newModel) writeEvent           oldVTree <- liftIO (readIORef viewRef)@@ -122,8 +125,9 @@   common app model $ \writeEvent -> do     let initialView = view model     VTree (OI.Object iv) <- flip runView writeEvent initialView+    mountEl <- mountElement mountPoint     -- Initial diff can be bypassed, just copy DOM into VTree-    copyDOMIntoVTree iv+    copyDOMIntoVTree mountEl iv     let initialVTree = VTree (OI.Object iv)     -- Create virtual dom, perform initial diff     liftIO (newIORef initialVTree)
frontend-src/Miso/Html/Internal.hs view
@@ -258,6 +258,9 @@  -- | @onCreated action@ is an event that gets called after the actual DOM -- element is created.+--+-- Important note: Any node that uses this event MUST have a unique @Key@,+-- otherwise the event may not be reliably called! onCreated :: action -> Attribute action onCreated action =   Attribute $ \sink n -> do@@ -268,6 +271,9 @@ -- | @onDestroyed action@ is an event that gets called after the DOM element -- is removed from the DOM. The @action@ is given the DOM element that was -- removed from the DOM tree.+--+-- Important note: Any node that uses this event MUST have a unique @Key@,+-- otherwise the event may not be reliably called! onDestroyed :: action -> Attribute action onDestroyed action =   Attribute $ \sink n -> do
frontend-src/Miso/String.hs view
@@ -84,7 +84,7 @@   fromMisoString = realToFrac . jsStringToDouble instance ToMisoString Double where   toMisoString = realFloatToJSString-  fromMisoString =  jsStringToDouble+  fromMisoString = jsStringToDouble instance ToMisoString Int where   toMisoString = integralToJSString   fromMisoString = round . jsStringToDouble
frontend-src/Miso/Subscription/Keyboard.hs view
@@ -81,6 +81,7 @@   keySetRef <- liftIO (newIORef mempty)   windowAddEventListener "keyup" $ keyUpCallback keySetRef   windowAddEventListener "keydown" $ keyDownCallback keySetRef+  windowAddEventListener "blur" $ blurCallback keySetRef     where       keyDownCallback keySetRef = \keyDownEvent -> do           Just key <- fromJSVal =<< getProp "keyCode" (Object keyDownEvent)@@ -94,4 +95,12 @@           newKeys <- liftIO $ atomicModifyIORef' keySetRef $ \keys ->              let !new = S.delete key keys              in (new, new)+          liftIO (sink (f newKeys))++      -- Assume keys are released the moment focus is lost. Otherwise going+      -- back and forth to the app can cause keys to get stuck.+      blurCallback keySetRef = \_ -> do+          newKeys <- liftIO $ atomicModifyIORef' keySetRef $ \_ ->+            let !new = S.empty+            in (new, new)           liftIO (sink (f newKeys))
ghc-src/Miso/String.hs view
@@ -54,3 +54,17 @@ instance ToMisoString BL.ByteString where   toMisoString = toMisoString . LT.decodeUtf8   fromMisoString = LT.encodeUtf8 . fromMisoString+instance ToMisoString Float where+  toMisoString = T.pack . show+  fromMisoString = read . T.unpack+instance ToMisoString Double where+  toMisoString = T.pack . show+  fromMisoString = read . T.unpack+instance ToMisoString Int where+  toMisoString = T.pack . show+  -- Replicate frontend behavior+  fromMisoString = round . (read :: String -> Double) . T.unpack+instance ToMisoString Word where+  toMisoString = T.pack . show+  -- Replicate frontend behavior+  fromMisoString = round . (read :: String -> Double) . T.unpack
ghcjs-ffi/Miso/FFI.hs view
@@ -119,11 +119,11 @@   -- | Retrieves inner height-foreign import javascript unsafe "$r = window.innerHeight;"+foreign import javascript unsafe "$r = window['innerHeight'];"   windowInnerHeight :: IO Int  -- | Retrieves outer height-foreign import javascript unsafe "$r = window.innerWidth;"+foreign import javascript unsafe "$r = window['innerWidth'];"   windowInnerWidth :: IO Int  -- | Retrieve high performance time stamp@@ -162,7 +162,7 @@   clearBody :: IO ()  -foreign import javascript unsafe "$r = objectToJSON($1,$2);"+foreign import javascript unsafe "$r = window['objectToJSON']($1,$2);"   objectToJSON     :: JSVal -- ^ decodeAt :: [JSString]     -> JSVal -- ^ object with impure references to the DOM@@ -201,7 +201,7 @@     callFunction continuation res   delegateEvent' mountPoint events cb' -foreign import javascript unsafe "delegate($1, $2, $3);"+foreign import javascript unsafe "window['delegate']($1, $2, $3);"   delegateEvent'      :: JSVal               -- ^ mountPoint element      -> JSVal               -- ^ Events@@ -213,35 +213,38 @@  -- | Copies DOM pointers into virtual dom -- entry point into isomorphic javascript-foreign import javascript unsafe "copyDOMIntoVTree($1);"-  copyDOMIntoVTree :: JSVal -> IO ()+foreign import javascript unsafe "window['copyDOMIntoVTree']($1, $2);"+  copyDOMIntoVTree+    :: JSVal -- ^ mountPoint element of the isomorphic app+    -> JSVal -- ^ VDom object+    -> IO ()  -- | Pins down the current callbacks for clearing later-foreign import javascript unsafe "swapCallbacks();"+foreign import javascript unsafe "window['swapCallbacks']();"   swapCallbacks :: IO ()  -- | Releases callbacks registered by the virtual DOM.-foreign import javascript unsafe "releaseCallbacks();"+foreign import javascript unsafe "window['releaseCallbacks']();"   releaseCallbacks :: IO () -foreign import javascript unsafe "registerCallback($1);"+foreign import javascript unsafe "window['registerCallback']($1);"   registerCallback :: JSVal -> IO ()  -- | Fails silently if the element is not found. -- -- Analogous to @document.getElementById(id).focus()@.-foreign import javascript unsafe "callFocus($1);"+foreign import javascript unsafe "window['callFocus']($1);"   focus :: JSString -> JSM ()  -- | Fails silently if the element is not found. -- -- Analogous to @document.getElementById(id).blur()@-foreign import javascript unsafe "callBlur($1);"+foreign import javascript unsafe "window['callBlur']($1);"   blur :: JSString -> JSM ()  -- | Calls @document.getElementById(id).scrollIntoView()@ foreign import javascript unsafe-  "document.getElementById($1).scrollIntoView();"+  "document.getElementById($1)['scrollIntoView']();"   scrollIntoView :: JSString -> IO ()  -- | Calls the @alert()@ function.
jsaddle-ffi/Miso/FFI.hs view
@@ -199,8 +199,8 @@  -- | Copies DOM pointers into virtual dom -- entry point into isomorphic javascript-copyDOMIntoVTree :: JSVal -> JSM ()-copyDOMIntoVTree a = () <$ jsg1 "copyDOMIntoVTree" a+copyDOMIntoVTree :: JSVal -> JSVal -> JSM ()+copyDOMIntoVTree mountPoint a = () <$ jsg1 "copyDOMIntoVTree" mountPoint a  -- TODO For now, we do not free callbacks when compiling with JSaddle 
jsbits/delegate.js view
@@ -1,111 +1,107 @@-window.oldCallbacks = [];-window.currentCallbacks = [];+window['oldCallbacks'] = [];+window['currentCallbacks'] = [];  /* Callbacks in ghcjs need to be released. With this function one can register    callbacks that should be released right before diffing. */-window.registerCallback = function registerCallback(cb) {-    currentCallbacks.push(cb);+window['registerCallback'] = function registerCallback(cb) {+  window['currentCallbacks'].push(cb); };  /* Swaps out the new calbacks for old callbacks. The old callbacks should be cleared once the new callbacks have replaced them. */-window.swapCallbacks = function swapCallbacks() {-  oldCallbacks = currentCallbacks;-  currentCallbacks = [];+window['swapCallbacks'] = function swapCallbacks() {+  window['oldCallbacks'] = window['currentCallbacks'];+  window['currentCallbacks'] = []; };  /* This releases the old callbacks. */-window.releaseCallbacks = function releaseCallbacks() {-    for (var i in oldCallbacks)-      h$release(oldCallbacks[i]);+window['releaseCallbacks'] = function releaseCallbacks() {+  for (var i in window['oldCallbacks'])+    h$release(window['oldCallbacks'][i]); -    oldCallbacks = [];+  window['oldCallbacks'] = []; };  /* event delegation algorithm */-window.delegate = function delegate(mountPointElement, events, getVTree) {-    for (var event in events) {-	  mountPointElement.addEventListener(events[event][0], function(e) {-        getVTree(function (obj) {-          delegateEvent ( e-                          , obj-                          , buildTargetToElement(mountPointElement, e.target)-                          , []-                        );-        });-	     }, events[event][1]);-    }+window['delegate'] = function delegate(mountPointElement, events, getVTree) {+  for (var event in events) {+    mountPointElement.addEventListener(events[event][0], function(e) {+      getVTree(function (obj) {+        window['delegateEvent'](e, obj, window['buildTargetToElement'](mountPointElement, e.target), []);+      });+    }, events[event][1]);+  } };  /* Accumulate parent stack as well for propagation */-window.delegateEvent = function delegateEvent (event, obj, stack, parentStack) {+window['delegateEvent'] = function delegateEvent (event, obj, stack, parentStack) { -    /* base case, not found */-    if (!stack.length) return;+  /* base case, not found */+  if (!stack.length) return; -    /* stack not length 1, recurse */-    else if (stack.length > 1) {-      if (obj.domRef === stack[0]) parentStack.unshift(obj);-	for (var o = 0; o < obj.children.length; o++) {-          if (obj.children[o].type === "vtext") continue;-          delegateEvent ( event-                        , obj.children[o]-                        , stack.slice(1)-                        , parentStack-			                  );-       }-    }+  /* stack not length 1, recurse */+  else if (stack.length > 1) {+    if (obj['domRef'] === stack[0]) parentStack.unshift(obj);+    for (var o = 0; o < obj.children.length; o++) {+      if (obj.children[o]['type'] === "vtext") continue;+      delegateEvent ( event+                    , obj.children[o]+                    , stack.slice(1)+                    , parentStack+                    );+     }+  } -    /* stack.length == 1 */-    else {-	if (obj.domRef === stack[0]) {-	    var eventObj = obj.events[event.type];-	    if (eventObj) {-		var options = eventObj.options;-		if (options.preventDefault)-		    event.preventDefault();-		eventObj.runEvent(event);-		if (!options.stopPropagation)-		    propogateWhileAble (parentStack, event);-	    } else {-		 /* still propagate to parent handlers even if event not defined */- 		 propogateWhileAble (parentStack, event);-	      }-	}+  /* stack.length == 1 */+  else {+    if (obj['domRef'] === stack[0]) {+      var eventObj = obj['events'][event.type];+      if (eventObj) {+        var options = eventObj.options;+      if (options['preventDefault'])+        event.preventDefault();+      eventObj['runEvent'](event);+      if (!options['stopPropagation'])+        window['propogateWhileAble'] (parentStack, event);+      } else {+        /* still propagate to parent handlers even if event not defined */+        window['propogateWhileAble'] (parentStack, event);+      }     }+  } }; -window.buildTargetToElement = function buildTargetToElement (element, target) {-    var stack = [];-    while (element !== target) {-      stack.unshift (target);-      target = target.parentNode;-    }-    return stack;+window['buildTargetToElement'] = function buildTargetToElement (element, target) {+  var stack = [];+  while (element !== target) {+    stack.unshift (target);+    target = target.parentNode;+  }+  return stack; }; -window.propogateWhileAble = function propogateWhileAble (parentStack, event) {+window['propogateWhileAble'] = function propogateWhileAble (parentStack, event) {   for (var i = 0; i < parentStack.length; i++) {-    if (parentStack[i].events[event.type]) {-      var eventObj = parentStack[i].events[event.type],-          options = eventObj.options;-        if (options.preventDefault) event.preventDefault();-        eventObj.runEvent(event);-  	if (options.stopPropagation) break;+    if (parentStack[i]['events'][event.type]) {+      var eventObj = parentStack[i]['events'][event.type],+          options = eventObj['options'];+      if (options['preventDefault']) event.preventDefault();+      eventObj['runEvent'](event);+      if (options['stopPropagation']) break;     }   } };  /* Walks down obj following the path described by `at`, then filters primitive  values (string, numbers and booleans)*/-window.objectToJSON = function objectToJSON (at, obj) {+window['objectToJSON'] = function objectToJSON (at, obj) {   /* If at is of type [[MisoString]] */   if (typeof at[0] == "object") {     var ret = [];     for (var i = 0; i < at.length; i++)-      ret.push(objectToJSON(at[i], obj));+      ret.push(window['objectToJSON'](at[i], obj));     return (ret);   } @@ -115,13 +111,13 @@   if (obj instanceof Array) {     var newObj = [];     for (var i = 0; i < obj.length; i++)-      newObj.push(objectToJSON([], obj[i]));+      newObj.push(window['objectToJSON']([], obj[i]));     return (newObj);   }    /* If obj is a non-list-like object */   var newObj = {};-    for (var i in obj){+  for (var i in obj){     /* bug in safari, throws TypeError if the following fields are referenced on a checkbox */     /* https://stackoverflow.com/a/25569117/453261 */     /* https://html.spec.whatwg.org/multipage/input.html#do-not-apply */@@ -129,6 +125,6 @@       continue;     if (typeof obj[i] == "string" || typeof obj[i] == "number" || typeof obj[i] == "boolean")       newObj[i] = obj[i];-    }-    return newObj;+  }+  return newObj; };
jsbits/diff.js view
@@ -1,347 +1,346 @@ /* virtual-dom diffing algorithm, applies patches as detected */-window.diff = function diff(currentObj, newObj, parent, doc) {-    if (!currentObj && !newObj) return;-    else if (!currentObj && newObj) createNode(newObj, parent, doc);-    else if (currentObj && !newObj) destroyNode(currentObj, parent);-    else {-	if (currentObj.type === "vtext") {-	    if (newObj.type === "vnode") replaceTextWithElement(currentObj, newObj, parent, doc);-	    else diffTextNodes(currentObj, newObj);-	} else {-	    if (newObj.type === "vnode") diffVNodes(currentObj, newObj, parent, doc);-	    else replaceElementWithText(currentObj, newObj, parent, doc);-	}+window['diff'] = function diff(currentObj, newObj, parent, doc) {+  if (!currentObj && !newObj) return;+  else if (!currentObj && newObj) window['createNode'](newObj, parent, doc);+  else if (currentObj && !newObj) window['destroyNode'](currentObj, parent);+  else {+    if (currentObj.type === "vtext") {+      if (newObj.type === "vnode") window['replaceTextWithElement'](currentObj, newObj, parent, doc);+      else window['diffTextNodes'](currentObj, newObj);+    } else {+      if (newObj.type === "vnode") window['diffVNodes'](currentObj, newObj, parent, doc);+      else window['replaceElementWithText'](currentObj, newObj, parent, doc);     }+  } }; -window.destroyNode = function destroyNode(obj, parent) {-    parent.removeChild(obj.domRef);-    callDestroyedRecursive(obj);+window['destroyNode'] = function destroyNode(obj, parent) {+  parent.removeChild(obj['domRef']);+  window['callDestroyedRecursive'](obj); }; -window.callDestroyedRecursive = function callDestroyedRecursive(obj) {-    callDestroyed(obj);-    for (var i in obj.children)-	callDestroyedRecursive(obj.children[i]);+window['callDestroyedRecursive'] = function callDestroyedRecursive(obj) {+  window['callDestroyed'](obj);+  for (var i in obj.children)+    window['callDestroyedRecursive'](obj.children[i]); }; -window.callDestroyed = function callDestroyed(obj) {-    if (obj.onDestroyed) obj.onDestroyed();+window['callDestroyed'] = function callDestroyed(obj) {+  if (obj['onDestroyed']) obj['onDestroyed'](); }; -window.diffTextNodes = function diffTextNodes(c, n) {-    if (c.text !== n.text) c.domRef.textContent = n.text;-    n.domRef = c.domRef;+window['diffTextNodes'] = function diffTextNodes(c, n) {+  if (c['text'] !== n['text']) c['domRef'].textContent = n['text'];+  n['domRef'] = c['domRef']; }; -window.replaceElementWithText = function replaceElementWithText(c, n, parent, doc) {-    n.domRef = doc.createTextNode(n.text);-    parent.replaceChild(n.domRef, c.domRef);-    callDestroyedRecursive(c);+window['replaceElementWithText'] = function replaceElementWithText(c, n, parent, doc) {+  n['domRef'] = doc.createTextNode(n['text']);+  parent.replaceChild(n['domRef'], c['domRef']);+  window['callDestroyedRecursive'](c); }; -window.replaceTextWithElement = function replaceTextWithElement(c, n, parent, doc) {-    createElement(n, doc);-    parent.replaceChild(n.domRef, c.domRef);-    callCreated(n);+window['replaceTextWithElement'] = function replaceTextWithElement(c, n, parent, doc) {+  window['createElement'](n, doc);+  parent.replaceChild(n['domRef'], c['domRef']);+  window['callCreated'](n); }; -window.callCreated = function callCreated(obj) {-    if (obj.onCreated) obj.onCreated();+window['callCreated'] = function callCreated(obj) {+  if (obj['onCreated']) obj['onCreated'](); }; -window.populate = function populate(c, n, doc) {-    if (!c) c = {-	props: null,-	css: null,-	children: []-    }-    diffProps(c.props, n.props, n.domRef, n.ns === "svg");-    diffCss(c.css, n.css, n.domRef);-    diffChildren(c.children, n.children, n.domRef, doc);+window['populate'] = function populate(c, n, doc) {+  if (!c) c = {+              props: null,+              css: null,+              children: []+              }+  window['diffProps'](c['props'], n['props'], n['domRef'], n['ns'] === "svg");+  window['diffCss'](c['css'], n['css'], n['domRef']);+  window['diffChildren'](c['children'], n['children'], n['domRef'], doc); }; -window.diffVNodes = function diffVNodes(c, n, parent, doc) {-    if (c.tag === n.tag && n.key === c.key) {-	n.domRef = c.domRef;-	populate(c, n, doc);-    } else {-	createElement(n, doc);-	parent.replaceChild(n.domRef, c.domRef);-	callDestroyedRecursive(c);-	callCreated(n);-    }+window['diffVNodes'] = function diffVNodes(c, n, parent, doc) {+  if (c['tag'] === n['tag'] && n['key'] === c['key']) {+    n['domRef'] = c['domRef'];+    window['populate'](c, n, doc);+  } else {+    window['createElement'](n, doc);+    parent.replaceChild(n['domRef'], c['domRef']);+    window['callDestroyedRecursive'](c);+    window['callCreated'](n);+  } }; -window.diffProps = function diffProps(cProps, nProps, node, isSvg) {-    var result, newProp, domProp;-    /* Is current prop in new prop list? */-    for (var c in cProps) {-	newProp = nProps[c];-	/* If current property no longer exists, remove it */-	if (!newProp) {-	    /* current key is not in node, remove it from DOM, if SVG, remove attribute */-	    if (isSvg || !(c in node))-		node.removeAttribute(c, cProps[c]);-	    else-		node[c] = '';-	} else {-	    /* Already on DOM from previous diff, continue */-	    if (newProp === cProps[c]) continue;-	    domProp = node[c];-	    if (isSvg) {-		if (c === "href")-		    node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);-		else-		    node.setAttribute(c, newProp);-	    } else if (c in node && !(c === "list" || c === "form")) {-		node[c] = newProp;-	    } else {-		node.setAttribute(c, newProp);-	    }-	}+window['diffProps'] = function diffProps(cProps, nProps, node, isSvg) {+  var result, newProp;+  /* Is current prop in new prop list? */+  for (var c in cProps) {+    newProp = nProps[c];+    /* If current property no longer exists, remove it */+    if (!newProp) {+      /* current key is not in node, remove it from DOM, if SVG, remove attribute */+      if (isSvg || !(c in node))+        node.removeAttribute(c, cProps[c]);+      else+        node[c] = '';+    } else {+      /* Already on DOM from previous diff, continue */+      if (newProp === cProps[c]) continue;+      if (isSvg) {+        if (c === "href")+          node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);+        else+          node.setAttribute(c, newProp);+      } else if (c in node && !(c === "list" || c === "form")) {+        node[c] = newProp;+      } else {+        node.setAttribute(c, newProp);+      }     }-    /* add remaining */-    for (var n in nProps) {-	if (cProps && cProps[n]) continue;-	newProp = nProps[n];-	/* Only add new properties, skip (continue) if they already exist in current property map */-	if (isSvg) {-	    if (n === "href")-		node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);-	    else-		node.setAttribute(n, newProp);-	} else if (n in node && !(n === "list" || n === "form")) {-	    node[n] = nProps[n];-	} else {-	    node.setAttribute(n, newProp);-	}+  }+  /* add remaining */+  for (var n in nProps) {+    if (cProps && cProps[n]) continue;+    newProp = nProps[n];+    /* Only add new properties, skip (continue) if they already exist in current property map */+    if (isSvg) {+      if (n === "href")+        node.setAttributeNS("http://www.w3.org/1999/xlink", "href", newProp);+      else+        node.setAttribute(n, newProp);+    } else if (n in node && !(n === "list" || n === "form")) {+      node[n] = nProps[n];+    } else {+      node.setAttribute(n, newProp);     }+  } }; -window.diffCss = function diffCss(cCss, nCss, node) {-    var result;-    /* is current attribute in new attribute list? */-    for (var c in cCss) {-	result = nCss[c];-	if (!result) {-	    /* current key is not in node */-	    node.style[c] = null;-	} else if (result !== cCss[c]) {-	    node.style[c] = result;-	}-    }-    /* add remaining */-    for (var n in nCss) {-	if (cCss && cCss[n]) continue;-	node.style[n] = nCss[n];+window['diffCss'] = function diffCss(cCss, nCss, node) {+  var result;+  /* is current attribute in new attribute list? */+  for (var c in cCss) {+    result = nCss[c];+    if (!result) {+      /* current key is not in node */+      node.style[c] = null;+    } else if (result !== cCss[c]) {+      node.style[c] = result;     }+  }+  /* add remaining */+  for (var n in nCss) {+    if (cCss && cCss[n]) continue;+    node.style[n] = nCss[n];+  } }; -window.hasKeys = function hasKeys(ns, cs) {-    return ns.length > 0 && cs.length > 0 && ns[0].key != null && cs[0].key != null;+window['hasKeys'] = function hasKeys(ns, cs) {+  return ns.length > 0 && cs.length > 0 && ns[0]['key'] != null && cs[0]['key'] != null; }; -window.diffChildren = function diffChildren(cs, ns, parent, doc) {-    var longest = ns.length > cs.length ? ns.length : cs.length;-    if (hasKeys(ns, cs)) {-	syncChildren(cs, ns, parent, doc);-    } else {-	for (var i = 0; i < longest; i++)-	    diff(cs[i], ns[i], parent, doc);-    }+window['diffChildren'] = function diffChildren(cs, ns, parent, doc) {+  var longest = ns.length > cs.length ? ns.length : cs.length;+  if (window['hasKeys'](ns, cs)) {+    window['syncChildren'](cs, ns, parent, doc);+  } else {+    for (var i = 0; i < longest; i++)+      window['diff'](cs[i], ns[i], parent, doc);+  } }; -window.createElement = function createElement(obj, doc) {-	if (obj.ns === "svg") {-		obj.domRef = doc.createElementNS("http://www.w3.org/2000/svg", obj.tag);-	} else if (obj.ns === "mathml") {-		obj.domRef = doc.createElementNS("http://www.w3.org/1998/Math/MathML", obj.tag);-	} else {-		obj.domRef = doc.createElement(obj.tag);-	}-    populate(null, obj, doc);+window['createElement'] = function createElement(obj, doc) {+  if (obj['ns'] === "svg") {+    obj['domRef'] = doc.createElementNS("http://www.w3.org/2000/svg", obj['tag']);+  } else if (obj['ns'] === "mathml") {+    obj['domRef'] = doc.createElementNS("http://www.w3.org/1998/Math/MathML", obj['tag']);+  } else {+    obj['domRef'] = doc.createElement(obj['tag']);+  }+  window['populate'](null, obj, doc); }; -window.createNode = function createNode(obj, parent, doc) {-    if (obj.type === "vnode") createElement(obj, doc);-    else obj.domRef = doc.createTextNode(obj.text);-    parent.appendChild(obj.domRef);-    callCreated(obj);+window['createNode'] = function createNode(obj, parent, doc) {+  if (obj.type === "vnode") window['createElement'](obj, doc);+  else obj['domRef'] = doc.createTextNode(obj['text']);+  parent.appendChild(obj['domRef']);+  window['callCreated'](obj); };  /* Child reconciliation algorithm, inspired by kivi and Bobril */-window.syncChildren = function syncChildren(os, ns, parent, doc) {-    var oldFirstIndex = 0,-	newFirstIndex = 0,-	oldLastIndex = os.length - 1,-	newLastIndex = ns.length - 1,-	nFirst, nLast, oLast, oFirst, tmp, found, node;-    for (;;) {-	/* check base case, first > last for both new and old-	  [ ] -- old children empty (fully-swapped)-	  [ ] -- new children empty (fully-swapped)-	*/-	if (newFirstIndex > newLastIndex && oldFirstIndex > oldLastIndex) {-	    break;-	}+window['syncChildren'] = function syncChildren(os, ns, parent, doc) {+  var oldFirstIndex = 0,+  newFirstIndex = 0,+  oldLastIndex = os.length - 1,+  newLastIndex = ns.length - 1,+  nFirst, nLast, oLast, oFirst, tmp, found, node;+  for (;;) {+    /* check base case, first > last for both new and old+      [ ] -- old children empty (fully-swapped)+      [ ] -- new children empty (fully-swapped)+    */+    if (newFirstIndex > newLastIndex && oldFirstIndex > oldLastIndex) {+      break;+    } -	/* Initialize */-	nFirst = ns[newFirstIndex];-	nLast = ns[newLastIndex];-	oFirst = os[oldFirstIndex];-	oLast = os[oldLastIndex];-	/* No more old nodes, create and insert all remaining nodes-	   -> [ ] <- old children-	   -> [ a b c ] <- new children-	*/-	if (oldFirstIndex > oldLastIndex) {-	    diff(null, nFirst, parent, doc);-	    /* insertBefore's semantics will append a node if the second argument provided is `null` or `undefined`.-	       Otherwise, it will insert node.domRef before oLast.domRef. */-	    parent.insertBefore(oLast.domRef, nFirst.domRef);-	    os.splice(newFirstIndex, 0, nFirst);-	    newFirstIndex++;-	}-	/* No more new nodes, delete all remaining nodes in old list-	   -> [ a b c ] <- old children-	   -> [ ] <- new children-	*/-	else if (newFirstIndex > newLastIndex) {-	    tmp = oldLastIndex - oldFirstIndex;-	    while (tmp >= 0) {-		parent.removeChild(os[oldFirstIndex].domRef);-		os.splice(oldFirstIndex, 1);-		tmp--;-	    }-	    break;-	}-	/* happy path, everything aligns, we continue-	   -> oldFirstIndex -> [ a b c ] <- oldLastIndex-	   -> newFirstIndex -> [ a b c ] <- newLastIndex-	   check if nFirst and oFirst align, if so, check nLast and oLast-	*/-	else if (oFirst.key === nFirst.key) {-	    diff(os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);-	} else if (oLast.key === nLast.key) {-	    diff(os[oldLastIndex--], ns[newLastIndex--], parent, doc);-	}-	/* flip-flop case, nodes have been swapped, in some way or another-	   both could have been swapped.-	   -> [ a b c ] <- old children-	   -> [ c b a ] <- new children-	*/-	else if (oFirst.key === nLast.key && nFirst.key === oLast.key) {-	    swapDomRefs(node, oFirst.domRef, oLast.domRef, parent);-	    swap(os, oldFirstIndex, oldLastIndex);-	    diff(os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);-	    diff(os[oldLastIndex--], ns[newLastIndex--], parent, doc);-	}-	/* Or just one could be swapped (d's align here)-	       This is top left and bottom right match case.-	       We move d to end of list, mutate old vdom to reflect the change-	       We then continue without affecting indexes, hoping to land in a better case-	       -> [ d a b ] <- old children-	       -> [ a b d ] <- new children-	       becomes-	       -> [ a b d ] <- old children-	       -> [ a b d ] <- new children-	       and now we happy path-	   */-	else if (oFirst.key === nLast.key) {-	    /* insertAfter */-	    parent.insertBefore(oFirst.domRef, oLast.domRef.nextSibling);-	    /* swap positions in old vdom */-	    os.splice(oldLastIndex,0,os.splice(oldFirstIndex,1)[0]);-	    diff(os[oldLastIndex--], ns[newLastIndex--], parent, doc);-	}-	/* This is top right and bottom lefts match case.-	   We move d to end of list, mutate old vdom to reflect the change-	   -> [ b a d ] <- old children-	   -> [ d b a ] <- new children-	   becomes-	   -> [ d b a ] <- old children-	   -> [ d b a ] <- new children-	   and now we happy path-	*/-	else if (oLast.key === nFirst.key) {-	    /* insertAfter */-	    parent.insertBefore(oLast.domRef, oFirst.domRef);-	    /* swap positions in old vdom */-	    os.splice(oldFirstIndex,0, os.splice(oldLastIndex,1)[0]);-	    diff(os[oldFirstIndex++], nFirst, parent, doc);-	    newFirstIndex++;-	}+    /* Initialize */+    nFirst = ns[newFirstIndex];+    nLast = ns[newLastIndex];+    oFirst = os[oldFirstIndex];+    oLast = os[oldLastIndex];+    /* No more old nodes, create and insert all remaining nodes+       -> [ ] <- old children+       -> [ a b c ] <- new children+    */+    if (oldFirstIndex > oldLastIndex) {+      window['diff'](null, nFirst, parent, doc);+      /* insertBefore's semantics will append a node if the second argument provided is `null` or `undefined`.+         Otherwise, it will insert node['domRef'] before oLast['domRef']. */+      parent.insertBefore(oLast['domRef'], nFirst['domRef']);+      os.splice(newFirstIndex, 0, nFirst);+      newFirstIndex++;+    }+    /* No more new nodes, delete all remaining nodes in old list+       -> [ a b c ] <- old children+       -> [ ] <- new children+    */+    else if (newFirstIndex > newLastIndex) {+      tmp = oldLastIndex - oldFirstIndex;+      while (tmp >= 0) {+        parent.removeChild(os[oldFirstIndex]['domRef']);+        os.splice(oldFirstIndex, 1);+        tmp--;+      }+      break;+    }+    /* happy path, everything aligns, we continue+       -> oldFirstIndex -> [ a b c ] <- oldLastIndex+       -> newFirstIndex -> [ a b c ] <- newLastIndex+       check if nFirst and oFirst align, if so, check nLast and oLast+    */+    else if (oFirst['key'] === nFirst['key']) {+      window['diff'](os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);+    } else if (oLast['key'] === nLast['key']) {+      window['diff'](os[oldLastIndex--], ns[newLastIndex--], parent, doc);+    }+    /* flip-flop case, nodes have been swapped, in some way or another+       both could have been swapped.+       -> [ a b c ] <- old children+       -> [ c b a ] <- new children+    */+    else if (oFirst['key'] === nLast['key'] && nFirst['key'] === oLast['key']) {+      window['swapDomRefs'](node, oFirst['domRef'], oLast['domRef'], parent);+      window['swap'](os, oldFirstIndex, oldLastIndex);+      window['diff'](os[oldFirstIndex++], ns[newFirstIndex++], parent, doc);+      window['diff'](os[oldLastIndex--], ns[newLastIndex--], parent, doc);+    }+    /* Or just one could be swapped (d's align here)+           This is top left and bottom right match case.+           We move d to end of list, mutate old vdom to reflect the change+           We then continue without affecting indexes, hoping to land in a better case+           -> [ d a b ] <- old children+           -> [ a b d ] <- new children+           becomes+           -> [ a b d ] <- old children+           -> [ a b d ] <- new children+           and now we happy path+       */+    else if (oFirst['key'] === nLast['key']) {+      /* insertAfter */+      parent.insertBefore(oFirst['domRef'], oLast['domRef'].nextSibling);+      /* swap positions in old vdom */+      os.splice(oldLastIndex,0,os.splice(oldFirstIndex,1)[0]);+      window['diff'](os[oldLastIndex--], ns[newLastIndex--], parent, doc);+    }+    /* This is top right and bottom lefts match case.+       We move d to end of list, mutate old vdom to reflect the change+       -> [ b a d ] <- old children+       -> [ d b a ] <- new children+       becomes+       -> [ d b a ] <- old children+       -> [ d b a ] <- new children+       and now we happy path+    */+    else if (oLast['key'] === nFirst['key']) {+      /* insertAfter */+      parent.insertBefore(oLast['domRef'], oFirst['domRef']);+      /* swap positions in old vdom */+      os.splice(oldFirstIndex,0, os.splice(oldLastIndex,1)[0]);+      window['diff'](os[oldFirstIndex++], nFirst, parent, doc);+      newFirstIndex++;+    } -	/* The "you're screwed" case, nothing aligns, pull the ripcord, do something more fancy-	   This can happen when the list is sorted, for example.-	   -> [ a e c ] <- old children-	   -> [ b e d ] <- new children-	*/-	else {-	    /* final case, perform linear search to check if new key exists in old map, decide what to do from there */-	    found = false;-	    tmp = oldFirstIndex;-	    while (tmp <= oldLastIndex) {-		if (os[tmp].key === nFirst.key) {-		    found = true;-		    node = os[tmp];-		    break;-		}-		tmp++;-	    }-   	        /* If new key was found in old map this means it was moved, hypothetically as below-		   -> [ a e b c ] <- old children-		   -> [ b e a j ] <- new children-			^-		   In the above case 'b' has been moved, so we need to insert 'b' before 'a' in both vDOM and DOM-		   We also increase oldFirstIndex and newFirstIndex.+    /* The "you're screwed" case, nothing aligns, pull the ripcord, do something more fancy+       This can happen when the list is sorted, for example.+       -> [ a e c ] <- old children+       -> [ b e d ] <- new children+    */+    else {+      /* final case, perform linear search to check if new key exists in old map, decide what to do from there */+      found = false;+      tmp = oldFirstIndex;+      while (tmp <= oldLastIndex) {+        if (os[tmp]['key'] === nFirst['key']) {+          found = true;+          node = os[tmp];+          break;+        }+        tmp++;+      }+          /* If new key was found in old map this means it was moved, hypothetically as below+         -> [ a e b c ] <- old children+         -> [ b e a j ] <- new children+          ^+         In the above case 'b' has been moved, so we need to insert 'b' before 'a' in both vDOM and DOM+         We also increase oldFirstIndex and newFirstIndex. -		   This results in new list below w/ updated index position-		   -> [ b a e c ] <- old children-		   -> [ b e a j ] <- new children-			  ^-		*/-	    if (found) {-		/* Move item to correct position */-		os.splice(oldFirstIndex,0, os.splice(tmp,1)[0]);-  		/* Swap DOM references */-		parent.insertBefore(node.domRef, os[oldFirstIndex].domRef);-		/* optionally perform `diff` here */-		diff(os[oldFirstIndex++], nFirst, parent, doc);-		/* increment counters */-		newFirstIndex++;-	    }-	    /* If new key was *not* found in the old map this means it must now be created, example below-		   -> [ a e d c ] <- old children-		   -> [ b e a j ] <- new children-			^+         This results in new list below w/ updated index position+         -> [ b a e c ] <- old children+         -> [ b e a j ] <- new children+            ^+      */+      if (found) {+        /* Move item to correct position */+        os.splice(oldFirstIndex,0, os.splice(tmp,1)[0]);+        /* Swap DOM references */+        parent.insertBefore(node['domRef'], os[oldFirstIndex]['domRef']);+        /* optionally perform `diff` here */+        window['diff'](os[oldFirstIndex++], nFirst, parent, doc);+        /* increment counters */+        newFirstIndex++;+      }+        /* If new key was *not* found in the old map this means it must now be created, example below+           -> [ a e d c ] <- old children+           -> [ b e a j ] <- new children+            ^ -		   In the above case 'b' does not exist in the old map, so we create a new element and DOM reference.-		   We then insertBefore in both vDOM and DOM.+           In the above case 'b' does not exist in the old map, so we create a new element and DOM reference.+           We then insertBefore in both vDOM and DOM. -		   -> [ b a e d c ] <- old children-		   -> [ b e a j   ] <- new children-			  ^-	       */-	    else {-		createElement(nFirst, doc);-		parent.insertBefore(nFirst.domRef, oFirst.domRef);-		os.splice(oldFirstIndex++, 0, nFirst);-		newFirstIndex++;-		oldLastIndex++;-	    }-	}+           -> [ b a e d c ] <- old children+           -> [ b e a j   ] <- new children+              ^+           */+      else {+        window['createElement'](nFirst, doc);+        parent.insertBefore(nFirst['domRef'], oFirst['domRef']);+        os.splice(oldFirstIndex++, 0, nFirst);+        newFirstIndex++;+        oldLastIndex++;+      }     }+  } }; -window.swapDomRefs = function swapDomRefs(tmp,a,b,p) {+window['swapDomRefs'] = function swapDomRefs(tmp,a,b,p) {   tmp = a.nextSibling;   p.insertBefore(a,b);   p.insertBefore(b,tmp); }; -window.swap= function swap(os,l,r) {+window['swap']= function swap(os,l,r) {   var k = os[l];   os[l] = os[r];   os[r] = k;
jsbits/isomorphic.js view
@@ -1,23 +1,57 @@-window.copyDOMIntoVTree = function copyDOMIntoVTree (vtree) {-    walk (vtree, document.body.firstChild);-};+window['copyDOMIntoVTree'] = function copyDOMIntoVTree(mountPoint, vtree, doc = window.document) {+  var node = mountPoint ? mountPoint.firstChild : doc.body.firstChild;+  if (!window['walk'](vtree, node, doc)) {+    console.warn('Could not copy DOM into virtual DOM, falling back to diff');+    // Remove all children before rebuilding DOM+    while (node.firstChild)+      node.removeChild(node.lastChild);+    window['diff'](null, vtree, node, doc);+    return false;+  }+  return true;+} -window.walk = function walk (vtree, node) {-    var i = 0, vdomChild, domChild;-    vtree.domRef = node;+window['walk'] = function walk(vtree, node, doc) {+  // This is slightly more complicated than one might expect since+  // browsers will collapse consecutive text nodes into a single text node.+  // There can thus be fewer DOM nodes than VDOM nodes.+  var vdomChild,+      domChild; -    // Fire onCreated events as though the elements had just been created.-    callCreated(vtree);+  vtree['domRef'] = node; -    while (i < vtree.children.length) {-      vdomChild = vtree.children[i];-      domChild = node.childNodes[i];-      if (vdomChild.type === "vtext") {-	  vdomChild.domRef = domChild;-	  i++;-	  continue;-      }-      walk(vdomChild, domChild);-      i++;-   }-};+  // Fire onCreated events as though the elements had just been created.+  window['callCreated'](vtree);++  for (var i = 0; i < vtree.children.length; i++) {+    vdomChild = vtree['children'][i];+    domChild = node.childNodes[i];+    if (vdomChild.type === "vtext") {+        if (domChild.nodeType !== Node.TEXT_NODE) return false;++        if (vdomChild['text'] === domChild.textContent) {+          vdomChild['domRef'] = domChild;+        } else {+          var len = vdomChild.text.length,+              domNodeText = domChild.textContent.substring(0, len);+          if (domNodeText !== vdomChild.text) return false;++          // There are more VDOM nodes than DOM nodes+          // Create new DOM node to ensure synchrony between VDom and DOM+          var partialTxt = doc.createTextNode(domNodeText);+          node.insertBefore(partialTxt, domChild);+          vdomChild['domRef'] = partialTxt;+          domChild.textContent = domChild.textContent.substring(len);+        }+    } else {+      if (domChild.nodeType !== Node.ELEMENT_NODE) return false;+      window['walk'](vdomChild, domChild, doc);+    }++  }+  // After walking the sizes of VDom and DOM should be equal+  // Otherwise there are DOM nodes unaccounted for+  if (vtree.children.length !== node.childNodes.length) return false;++  return true;+}
jsbits/util.js view
@@ -1,11 +1,11 @@-window.callFocus = function callFocus(id) {+window['callFocus'] = function callFocus(id) {   setTimeout(function(){     var ele = document.getElementById(id);     if (ele && ele.focus) ele.focus()   }, 50); } -window.callBlur = function callBlur(id) {+window['callBlur'] = function callBlur(id) {   setTimeout(function(){     var ele = document.getElementById(id);     if (ele && ele.blur) ele.blur()
miso.cabal view
@@ -1,5 +1,5 @@ name:                miso-version:             0.21.2.0+version:             1.0.0.0 category:            Web, Miso, Data Structures license:             BSD3 license-file:        LICENSE@@ -44,6 +44,10 @@   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/todo-mvc     build-depends:@@ -62,6 +66,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/three     build-depends:@@ -79,6 +87,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/file-reader     build-depends:@@ -96,6 +108,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/xhr     build-depends:@@ -113,6 +129,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/canvas2d     build-depends:@@ -129,6 +149,10 @@   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/router     build-depends:@@ -148,6 +172,10 @@   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/websocket     build-depends:@@ -166,6 +194,10 @@   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/mario     build-depends:@@ -188,6 +220,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/svg     other-modules:@@ -206,6 +242,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/compose-update     build-depends:@@ -220,6 +260,10 @@   if !impl(ghcjs) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       examples/mathml     build-depends:@@ -234,6 +278,10 @@   if (!impl(ghcjs) && !flag(jsaddle)) || !flag(examples)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       exe     build-depends:@@ -252,6 +300,10 @@   if !impl(ghcjs) || !flag(tests)     buildable: False   else+    ghcjs-options:+      -dedupe+    cpp-options:+      -DGHCJS_BROWSER     hs-source-dirs:       tests     build-depends:
src/Miso/Html/Element.hs view
@@ -126,7 +126,7 @@     , bdi_     , bdo_     , wbr_-    -- * Interactive elemnts+    -- * Interactive elements     , details_     , summary_     , menuitem_
src/Miso/Html/Property.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module      :  Miso.Html.Property@@ -114,11 +115,17 @@    , httpEquiv_    , language_    , scoped_+   -- * Data+   , data_    ) where  import           Miso.Html.Internal import           Miso.String (MisoString, intercalate) +#if __GLASGOW_HASKELL__ < 841+import           Data.Monoid ((<>))+#endif+ -- | Set field to `Bool` value boolProp :: MisoString -> Bool -> Attribute action boolProp = prop@@ -383,3 +390,7 @@ -- <https://developer.mozilla.org/en-US/docs/Web/API/Element/className> class_ ::  MisoString -> Attribute action class_ = textProp "class"+-- | Set "data-*" property+-- https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*+data_ ::  MisoString -> MisoString -> Attribute action+data_ k v = textProp ("data-" <> k) v
src/Miso/Router.hs view
@@ -36,7 +36,7 @@ import qualified Data.Text             as T import           Data.Text.Encoding import           GHC.TypeLits-import           Network.HTTP.Types+import           Network.HTTP.Types    hiding (Header) import           Network.URI import           Servant.API import           Web.HttpApiData@@ -117,6 +117,11 @@   mkRouter _ a f = RQueryFlag     (Proxy :: Proxy sym)     (\x -> mkRouter (Proxy :: Proxy sublayout) a (f x))++-- | Header+instance HasRouter sublayout => HasRouter (Header sym (x :: *) :> sublayout) where+    type RouteT (Header sym x :> sublayout) a = Maybe x -> RouteT sublayout a+    mkRouter _ a f = mkRouter (Proxy :: Proxy sublayout) a (f Nothing)  -- | Path instance (HasRouter sublayout, KnownSymbol path)
tests/Main.hs view
@@ -95,7 +95,6 @@     GaveUp { output = o } -> (False, o)     Failure { output = o } -> (False, o)     NoExpectedFailure { output = o } -> (False, o)-    InsufficientCoverage { output = o } -> (False, o)  foreign import javascript unsafe "window.global_test_results = $1;"   writeToGlobalObject :: JSVal -> IO ()