miso 1.2.0.0 → 1.3.0.0
raw patch · 9 files changed
+187/−23 lines, 9 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Miso.Html: onBeforeDestroyed :: action -> Attribute action
Files
- README.md +136/−17
- frontend-src/Miso/Html/Internal.hs +14/−0
- ghc-src/Miso/Html/Internal.hs +7/−0
- ghcjs-ffi/Miso/FFI.hs +6/−2
- jsaddle-ffi/Miso/FFI.hs +8/−1
- jsbits/diff.js +13/−0
- jsbits/isomorphic.js +1/−1
- miso.cabal +1/−1
- tests/Main.hs +1/−1
README.md view
@@ -9,12 +9,12 @@ <p align="center"> <a href="https://haskell-miso-slack.herokuapp.com">- <img src="https://img.shields.io/badge/slack-miso-E01563.svg?style=flat-square" alt="Miso Slack">+<img src="https://img.shields.io/badge/slack-miso-E01563.svg?style=flat-square" alt="Miso Slack"> </a> <a href="https://haskell.org"> <img src="https://img.shields.io/badge/language-Haskell-orange.svg?style=flat-square" alt="Haskell"> </a>- <a href="https://haskell-miso.cachix.org">+ <a href="https://miso-haskell.cachix.org"> <img src="https://img.shields.io/badge/build-cachix-yellow.svg?style=flat-square" alt="Cachix"> </a> <a href="https://travis-ci.org/dmjio/miso">@@ -59,6 +59,8 @@ - [GHC](#ghc) - [GHCJS](#ghcjs) - [Sample Application](#sample-application)+- [Transition Application](#transition-application)+- [Live reload with JSaddle](#live-reload-with-jsaddle) - [Building examples](#building-examples) - [Coverage](#coverage) - [Isomorphic](#isomorphic)@@ -66,11 +68,13 @@ - [Binary cache](#binary-cache) - [Benchmarks](#benchmarks) - [Maintainers](#maintainers)+- [Commercial Users](#commercial-users) - [Contributing](#contributing)+- [Contributors](#contributors) - [License](#license) ## Quick start-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).+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://miso-haskell.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 To build the sample-app with `nix`, execute the commands below:@@ -79,7 +83,7 @@ # optional use of cache nix-env -iA cachix -f https://cachix.org/api/v1/install # optional use of cache-cachix use haskell-miso+cachix use miso-haskell git clone https://github.com/dmjio/miso cd miso/sample-app nix-build@@ -125,11 +129,10 @@ ```nix with (import (builtins.fetchTarball {- url = "https://github.com/dmjio/miso/archive/39b9e26ff41d6aab3b9d13a9d102ac56017f6a1f.tar.gz";- sha256 = "1lwr35p9074b7wgz0jh4f2pjc7ls8isgzmn9xl86vb6cvsm035kf";+ url = "https://github.com/dmjio/miso/archive/425de4eb87e876428df4872a595ac1a0717dd165.tar.gz";+ sha256 = "1mc44vnx8qmvxkrvxhzlnrza8shnk8a0ad7hfk8hlblzrd9ha5id"; }) {});-with pkgs.haskell.packages;-ghcjs.callCabal2nix "app" ./. {}+pkgs.haskell.packages.ghcjs.callCabal2nix "app" ./. {} ``` Add the source from [Sample Application](#sample-application) to `app/Main.hs`@@ -166,7 +169,7 @@ 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'+ag -l | entr cabal build ``` ### Architecture@@ -268,11 +271,16 @@ -- | Updates model, optionally introduces side effects updateModel :: Action -> Model -> Effect Action Model-updateModel AddOne m = noEff (m + 1)-updateModel SubtractOne m = noEff (m - 1)-updateModel NoOp m = noEff m-updateModel SayHelloWorld m = m <# do- putStrLn "Hello World" >> pure NoOp+updateModel action m =+ case action of+ AddOne+ -> noEff (m + 1)+ SubtractOne+ -> noEff (m - 1)+ NoOp+ -> noEff m+ SayHelloWorld+ -> m <# do consoleLog "Hello World" >> pure NoOp -- | Constructs a virtual DOM from a model viewModel :: Model -> View Action@@ -283,6 +291,83 @@ ] ``` +## Transition application++An alternative, more powerful interface for constructing `miso` applications is using the `Transition` interface.+`Transition` is based on the `StateT` monad transformer, and can be used to construct components. It also works+very nicely with lenses based on `MonadState` (i.e. `(.=)`, `(%=)`,`(+=)`,`(-=)`).+++```haskell+-- | Haskell language pragma+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++-- | Haskell module declaration+module Main where++-- | Miso framework import+import Miso+import Miso.String++-- | Lens import+import Control.Lens++-- | Type synonym for an application model+data Model+ = Model+ { _counter :: Int+ } deriving (Show, Eq)++counter :: Lens' Model Int+counter = lens _counter $ \record field -> record { _counter = field }++-- | Sum type for application events+data Action+ = AddOne+ | SubtractOne+ | NoOp+ | SayHelloWorld+ deriving (Show, Eq)++-- | Entry point for a miso application+main :: IO ()+main = startApp App {..}+ where+ initialAction = SayHelloWorld -- initial action to be executed on application load+ model = 0 -- initial model+ update = fromTransition . updateModel -- update function+ view = viewModel -- view function+ events = defaultEvents -- default delegated events+ subs = [] -- empty subscription list+ mountPoint = Nothing -- mount point for application (Nothing defaults to 'body')++-- | Updates model, optionally introduces side effects+updateModel :: Action -> Transition Action Model ()+updateModel action =+ case action of+ AddOne+ -> counter += 1+ SubtractOne+ -> counter -= 1+ NoOp+ -> pure ()+ SayHelloWorld+ -> scheduleIO_ (consoleLog "Hello World")++-- | Constructs a virtual DOM from a model+viewModel :: Model -> View Action+viewModel x = div_ [] [+ button_ [ onClick AddOne ] [ text "+" ]+ , text (ms x)+ , button_ [ onClick SubtractOne ] [ text "-" ]+ ]+```++## Live reload with JSaddle++It is possible to build `miso` applications with `ghcid`, `jsaddle` that allow live reloading of your application in reponse to changes in application code. See the [README](https://github.com/dmjio/miso/blob/master/sample-app-jsaddle/README.md) in the `sample-app-jsaddle` folder for more information.+ ## Building examples The easiest way to build the examples is with the [`nix`](https://nixos.org/nix/) package manager@@ -346,27 +431,61 @@ ## Binary cache -`nix` users on a Linux or OSX distro can take advantage of a [binary cache](https://haskell-miso.cachix.org) for faster builds. To use the binary cache follow the instructions on [cachix](https://haskell-miso.cachix.org/).+`nix` users on a Linux or OSX distro can take advantage of a [binary cache](https://miso-haskell.cachix.org) for faster builds. To use the binary cache follow the instructions on [cachix](https://miso-haskell.cachix.org/). ```bash-cachix use haskell-miso+cachix use miso-haskell ``` ## Benchmarks [According to benchmarks](https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html), `miso` is among the fastest functional programming web frameworks, second only to [Elm](http://elm-lang.org). -<img src="https://cdn-images-1.medium.com/max/1600/1*6EjJTf1mhlTxd4QWsygCwA.png" width="500" height="600" />+<a target="_blank" href="https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html"><img src="https://cdn-images-1.medium.com/max/1600/1*6EjJTf1mhlTxd4QWsygCwA.png" width="500" height="600" /></a> ## Maintainers [@dmjio](https://github.com/dmjio) +## Commercial Users+ - [Polimorphic](https://www.polimorphic.com)+ - [LumiGuide](https://lumi.guide/en/)+ ## Contributing Feel free to dive in! [Open an issue](https://github.com/dmjio/miso/issues/new) or submit [PRs](https://github.com/dmjio/miso/pulls). See [CONTRIBUTING](https://github.com/dmjio/miso/blob/master/CONTRIBUTING.md) for more info.++## Contributors++### Code Contributors++This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].+<a href="https://github.com/dmjio/miso/graphs/contributors"><img src="https://opencollective.com/miso/contributors.svg?width=890&button=false" /></a>++### Financial Contributors++Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/miso/contribute)]++#### Individuals++<a href="https://opencollective.com/miso"><img src="https://opencollective.com/miso/individuals.svg?width=890"></a>++#### Organizations++Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/miso/contribute)]++<a href="https://opencollective.com/miso/organization/0/website"><img src="https://opencollective.com/miso/organization/0/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/1/website"><img src="https://opencollective.com/miso/organization/1/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/2/website"><img src="https://opencollective.com/miso/organization/2/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/3/website"><img src="https://opencollective.com/miso/organization/3/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/4/website"><img src="https://opencollective.com/miso/organization/4/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/5/website"><img src="https://opencollective.com/miso/organization/5/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/6/website"><img src="https://opencollective.com/miso/organization/6/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/7/website"><img src="https://opencollective.com/miso/organization/7/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/8/website"><img src="https://opencollective.com/miso/organization/8/avatar.svg"></a>+<a href="https://opencollective.com/miso/organization/9/website"><img src="https://opencollective.com/miso/organization/9/avatar.svg"></a> ## License
frontend-src/Miso/Html/Internal.hs view
@@ -49,6 +49,7 @@ -- * Life cycle events , onCreated , onDestroyed+ , onBeforeDestroyed -- * Events , defaultEvents -- * Subscription type@@ -279,6 +280,19 @@ Attribute $ \sink n -> do cb <- callbackToJSVal =<< asyncCallback (liftIO (sink action)) set "onDestroyed" cb n+ registerCallback cb++-- | @onBeforeDestroyed action@ is an event that gets called before 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!+onBeforeDestroyed :: action -> Attribute action+onBeforeDestroyed action =+ Attribute $ \sink n -> do+ cb <- callbackToJSVal =<< asyncCallback (liftIO (sink action))+ set "onBeforeDestroyed" cb n registerCallback cb -- | @style_ attrs@ is an attribute that will set the @style@
ghc-src/Miso/Html/Internal.hs view
@@ -44,6 +44,7 @@ -- * Life cycle events , onCreated , onDestroyed+ , onBeforeDestroyed ) where import Data.Aeson (Value(..), ToJSON(..))@@ -241,6 +242,12 @@ -- removed from the DOM tree. onDestroyed :: action -> Attribute action onDestroyed _ = E ()++-- | @onBeforeDestroyed action@ is an event that gets called before the DOM element+-- is removed from the DOM. The @action@ is given the DOM element that was+-- removed from the DOM tree.+onBeforeDestroyed :: action -> Attribute action+onBeforeDestroyed _ = E () -- | Constructs CSS for a DOM Element --
ghcjs-ffi/Miso/FFI.hs view
@@ -32,6 +32,7 @@ , now , consoleLog+ , consoleLogJSVal , stringify , parse , clearBody@@ -61,7 +62,7 @@ ) where import Control.Concurrent-import Data.Aeson hiding (Object)+import Data.Aeson hiding (Object) import Data.JSString import Data.JSString.Int import Data.JSString.RealFloat@@ -132,7 +133,10 @@ -- | Console-logging foreign import javascript unsafe "console.log($1);"- consoleLog :: JSVal -> IO ()+ consoleLog :: JSString -> IO ()++foreign import javascript unsafe "console.log($1);"+ consoleLogJSVal :: JSVal -> IO () -- | Converts a JS object into a JSON string foreign import javascript unsafe "$r = JSON.stringify($1);"
jsaddle-ffi/Miso/FFI.hs view
@@ -30,6 +30,7 @@ , now , consoleLog+ , consoleLogJSVal , stringify , parse , clearBody@@ -127,8 +128,14 @@ now = fromJSValUnchecked =<< (jsg "performance" # "now" $ ()) -- | Console-logging-consoleLog :: JSVal -> JSM ()+consoleLog :: JSString -> JSM () consoleLog v = do+ _ <- jsg "console" # "log" $ [toJSString v]+ pure ()++-- | Console-logging+consoleLogJSVal :: JSVal -> JSM ()+consoleLogJSVal v = do _ <- jsg "console" # "log" $ [v] pure ()
jsbits/diff.js view
@@ -16,6 +16,7 @@ }; window['destroyNode'] = function destroyNode(obj, parent) {+ window['callBeforeDestroyedRecursive'](obj); parent.removeChild(obj['domRef']); window['callDestroyedRecursive'](obj); };@@ -30,6 +31,16 @@ if (obj['onDestroyed']) obj['onDestroyed'](); }; +window['callBeforeDestroyed'] = function callBeforeDestroyed(obj) {+ if (obj['onBeforeDestroyed']) obj['onBeforeDestroyed']();+};++window['callBeforeDestroyedRecursive'] = function callBeforeDestroyedRecursive(obj) {+ window['callBeforeDestroyed'](obj);+ for (var i in obj.children)+ window['callBeforeDestroyedRecursive'](obj.children[i]);+};+ window['diffTextNodes'] = function diffTextNodes(c, n) { if (c['text'] !== n['text']) c['domRef'].textContent = n['text']; n['domRef'] = c['domRef'];@@ -37,6 +48,7 @@ window['replaceElementWithText'] = function replaceElementWithText(c, n, parent, doc) { n['domRef'] = doc.createTextNode(n['text']);+ window['callBeforeDestroyedRecursive'](c); parent.replaceChild(n['domRef'], c['domRef']); window['callDestroyedRecursive'](c); };@@ -68,6 +80,7 @@ window['populate'](c, n, doc); } else { window['createElement'](n, doc);+ window['callBeforeDestroyedRecursive'](c); parent.replaceChild(n['domRef'], c['domRef']); window['callDestroyedRecursive'](c); window['callCreated'](n);
jsbits/isomorphic.js view
@@ -1,5 +1,5 @@ window = typeof window === 'undefined' ? {} : window;-window['copyDOMIntoVTree'] = function copyDOMIntoVTree(mountPoint, vtree, doc) {+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');
miso.cabal view
@@ -1,5 +1,5 @@ name: miso-version: 1.2.0.0+version: 1.3.0.0 category: Web, Miso, Data Structures license: BSD3 license-file: LICENSE
tests/Main.hs view
@@ -102,7 +102,7 @@ runTests :: TestM () -> IO () runTests t = do results <- toJSVal =<< toResult <$> execStateT t []- consoleLog results+ consoleLogJSVal results writeToGlobalObject results where toResult :: [Test] -> TestResult