-
19 |
├── .gitattributes ├── .gitignore ├── LICENSE ├── Setup.hs ├── apps ├── mvc-todo-auto.hs └── mvc-todo.hs ├── index.html ├── mvc-todo.cabal ├── other ├── header.md ├── index.md ├── mvc-todo-auto.html ├── mvc-todo-auto.js ├── mvc-todo.html ├── mvc-todo.js └── node_modules │ ├── todomvc-app-css │ └── index.css │ └── todomvc-common │ ├── base.css │ └── base.js ├── readme.md ├── src └── Todo │ ├── Arbitrary.hs │ ├── Controllers.hs │ ├── Model.hs │ ├── Vanilla.hs │ └── Views.hs └── stack.yaml /.gitattributes: -------------------------------------------------------------------------------- 1 | other/* linguist-documentation 2 | index.html linguist-documentation 3 | *.html linguist-documentation 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.stack-work/ 2 | TAGS 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Tony Day (c) 2017 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Tony Day nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /apps/mvc-todo-auto.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# OPTIONS_GHC -fno-warn-type-defaults #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE TemplateHaskell #-} 5 | 6 | module Main where 7 | 8 | import Control.Concurrent (threadDelay) 9 | import Control.Lens 10 | import Control.Monad.Trans.State.Strict (State, StateT) 11 | import Data.IORef 12 | import MVC hiding ((<>)) 13 | import MVC.Prelude as MVC 14 | import Protolude hiding (State, StateT, on) 15 | import Test.QuickCheck 16 | import Todo.Arbitrary () 17 | import Todo.Controllers (controllers) 18 | import Todo.Model (Todos(..), Action(..), ItemStatus(..), Item(..), ItemId(..), apply) 19 | import Todo.Vanilla 20 | import Todo.Views (render) 21 | import qualified Data.Map as Map 22 | 23 | data Auto = Manual 24 | | Automatic 25 | deriving (Show, Eq) 26 | 27 | toggle :: Auto -> Auto 28 | toggle Manual = Automatic 29 | toggle Automatic = Manual 30 | 31 | data In = AutoIn Auto 32 | | ActionIn Action 33 | deriving (Eq, Show) 34 | 35 | data Out 36 | = TodosOut Todos 37 | | InOut In 38 | | AutoOut Auto 39 | deriving (Show, Eq) 40 | 41 | data StateAuto = StateAuto { _sTodos :: Todos, _sAuto :: Auto } deriving (Show, Eq) 42 | 43 | makeClassy ''StateAuto 44 | 45 | makePrisms ''In 46 | makePrisms ''Out 47 | 48 | initialState :: StateAuto 49 | initialState = StateAuto 50 | (Todos "" Nothing (ItemId 3) Nothing 51 | (Map.fromList $ 52 | zip (ItemId <$> [0 ..]) 53 | [ Item Completed "learn functional programming" 54 | , Item Completed "invent ADTs" 55 | , Item Completed "discover algebra between ADTs" 56 | , Item Active "automate everything else" 57 | ])) 58 | Manual 59 | 60 | ctlAuto :: Output Auto -> IO () 61 | ctlAuto o = do 62 | toggleAuto <- docElement ".toggle-auto" 63 | on toggleAuto "click" (\el _ -> do 64 | ch <- checked el 65 | -- print ch 66 | let auto = if ch then Automatic else Manual 67 | void $ atomically $ send o auto) 68 | 69 | modifyState :: In -> ListT (State StateAuto) Out 70 | modifyState i = case i of 71 | (AutoIn auto) -> lift (modify (set sAuto auto)) >> pure (AutoOut auto) 72 | (ActionIn NoAction) -> mzero 73 | (ActionIn action) -> do 74 | s <- lift get 75 | let tds = view sTodos s 76 | let tds' = apply action tds 77 | lift $ put $ set sTodos tds' s 78 | pure (TodosOut tds') 79 | 80 | model' :: In -> ListT (State StateAuto) Out 81 | model' i = 82 | (modifyState i) `mappend` (InOut <$> pure i) 83 | 84 | run :: IO StateAuto 85 | run = do 86 | print "running auto ..." 87 | (o, i) <- spawn unbounded 88 | (oAuto, iAuto) <- spawn unbounded 89 | doAuto <- newIORef (view sAuto initialState) 90 | controllers o 91 | ctlAuto oAuto 92 | print initialState 93 | runMVC initialState (asPipe $ loop model') 94 | ((,) <$> pure (asSink (render' doAuto)) <*> pure (cAuto iAuto) `mappend` (fmap ActionIn <$> cAction i doAuto)) 95 | 96 | render' :: IORef Auto -> Out -> IO () 97 | render' _ (InOut i) = print i 98 | render' _ (TodosOut tds) = do 99 | print tds 100 | render tds 101 | render' ref (AutoOut auto) = do 102 | modifyIORef ref toggle 103 | renderAuto auto 104 | 105 | renderAuto :: Auto -> IO () 106 | renderAuto auto = do 107 | print auto 108 | el <- docElement ".toggle-auto" 109 | case auto of 110 | Automatic -> setValue el "on" 111 | Manual -> setValue el "off" 112 | 113 | cAuto :: Input Auto -> Controller In 114 | cAuto iAuto = AutoIn <$> asInput iAuto 115 | 116 | cAction :: Input Action -> IORef Auto -> Managed (Controller Action) 117 | cAction iAction doAuto = 118 | (pure (asInput iAction) `mappend` 119 | producer unbounded (yield Refresh) `mappend` 120 | producer unbounded 121 | (forever $ do 122 | lift (threadDelay (1000000 * 3)) 123 | ref <- lift $ readIORef doAuto 124 | when (ref == Automatic) $ do 125 | x <- lift (generate arbitrary) 126 | yield x)) 127 | 128 | main :: IO () 129 | main = onload (void run) 130 | -------------------------------------------------------------------------------- /apps/mvc-todo.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import Control.Monad.Trans.State.Strict (State, StateT) 6 | import MVC hiding ((<>)) 7 | import MVC.Prelude as MVC 8 | import Protolude hiding (State, StateT, loop) 9 | import Todo.Controllers (controllers) 10 | import Todo.Model 11 | import Todo.Vanilla (onload) 12 | import Todo.Views (render) 13 | import qualified Data.Map as Map 14 | 15 | main :: IO () 16 | main = onload (void run) 17 | 18 | initialState :: Todos 19 | initialState = 20 | Todos "" Nothing (ItemId 3) Nothing 21 | (Map.fromList $ 22 | zip (ItemId <$> [0 ..]) 23 | [ Item Active "write view" 24 | , Item Active "write controllers" 25 | , Item Completed "render a todo list" 26 | ]) 27 | 28 | run :: IO Todos 29 | run = do 30 | (o, i) <- spawn unbounded 31 | controllers o 32 | runMVC initialState (asPipe $ MVC.loop model) 33 | ((,) <$> 34 | pure (asSink render_) <*> 35 | ( pure (asInput i) `mappend` 36 | producer unbounded (yield Refresh))) 37 | 38 | render_ :: Out -> IO () 39 | render_ (ActionOut action) = print action 40 | render_ (StateOut tds) = render tds 41 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |
2 |
4 |6 |Haskell is a strongly-typed, lazily-evaluated, functional programming language.
5 |
with automation
This example demonstrates an idiomatic haskell approach to the TodoMVC problem domain involving:
11 |Model
representing the problem domain, consisting ofView
s that consume model outputs, by using vanilla javascript effects.Controller
s that produce model inputs, by listening for Dom events using vanilla javascript.The recipe below handles the bits and bobs you need to do every re-compile. This includes a compression step via closure.
22 |
23 |
24 | stack build --exec "pandoc -f markdown -i other/index.md -t html -o index.html --filter pandoc-include" --exec "java -jar $(stack path --local-bin)/closure-compiler-v20170124.jar --js_output_file=other/mvc-todo-auto.js $(stack path --local-install-root)/bin/mvc-todo-auto.jsexe/all.js" --exec "java -jar $(stack path --local-bin)/closure-compiler-v20170124.jar --js_output_file=other/mvc-todo.js $(stack path --local-install-root)/bin/mvc-todo.jsexe/all.js"
25 |
26 |
27 |
--------------------------------------------------------------------------------
/mvc-todo.cabal:
--------------------------------------------------------------------------------
1 | name:
2 | mvc-todo
3 | version:
4 | 1.0.0
5 | synopsis:
6 | See readme.lhs
7 | description:
8 | See readme.lhs for description.
9 | category:
10 | project
11 | homepage:
12 | https://github.com/tonyday567/mvc-todo
13 | license:
14 | BSD3
15 | license-file:
16 | LICENSE
17 | author:
18 | Tony Day
19 | maintainer:
20 | tonyday567@gmail.com
21 | copyright:
22 | Tony Day
23 | build-type:
24 | Simple
25 | cabal-version:
26 | >=1.14
27 | extra-source-files:
28 | stack.yaml
29 | library
30 | default-language:
31 | Haskell2010
32 | ghc-options:
33 | hs-source-dirs:
34 | src
35 | exposed-modules:
36 | Todo.Vanilla,
37 | Todo.Arbitrary,
38 | Todo.Controllers,
39 | Todo.Model,
40 | Todo.Views
41 | build-depends:
42 | base >= 4.7 && < 5,
43 | protolude,
44 | clay,
45 | text,
46 | lucid,
47 | lens,
48 | mtl,
49 | data-default,
50 | containers,
51 | pipes,
52 | QuickCheck,
53 | errors,
54 | mmorph,
55 | either,
56 | mvc,
57 | ghcjs-base,
58 | transformers
59 | default-extensions:
60 | NoImplicitPrelude,
61 | UnicodeSyntax,
62 | BangPatterns,
63 | BinaryLiterals,
64 | DeriveFoldable,
65 | DeriveFunctor,
66 | DeriveGeneric,
67 | DeriveTraversable,
68 | DisambiguateRecordFields,
69 | EmptyCase,
70 | FlexibleContexts,
71 | FlexibleInstances,
72 | FunctionalDependencies,
73 | GADTSyntax,
74 | InstanceSigs,
75 | KindSignatures,
76 | LambdaCase,
77 | MonadComprehensions,
78 | MultiParamTypeClasses,
79 | MultiWayIf,
80 | NegativeLiterals,
81 | OverloadedStrings,
82 | ParallelListComp,
83 | PartialTypeSignatures,
84 | PatternSynonyms,
85 | RankNTypes,
86 | RecordWildCards,
87 | RecursiveDo,
88 | ScopedTypeVariables,
89 | TupleSections,
90 | TypeFamilies,
91 | TypeOperators
92 |
93 | executable mvc-todo
94 | default-language:
95 | Haskell2010
96 | ghc-options:
97 | -funbox-strict-fields
98 | -fforce-recomp
99 | -threaded
100 | -rtsopts
101 | -with-rtsopts=-N
102 | hs-source-dirs:
103 | apps
104 | main-is:
105 | mvc-todo.hs
106 | build-depends:
107 | base >= 4.7 && < 5,
108 | protolude,
109 | mvc-todo,
110 | ghcjs-base,
111 | containers,
112 | mvc,
113 | transformers
114 | default-extensions:
115 | NoImplicitPrelude,
116 | UnicodeSyntax,
117 | BangPatterns,
118 | BinaryLiterals,
119 | DeriveFoldable,
120 | DeriveFunctor,
121 | DeriveGeneric,
122 | DeriveTraversable,
123 | DisambiguateRecordFields,
124 | EmptyCase,
125 | FlexibleContexts,
126 | FlexibleInstances,
127 | FunctionalDependencies,
128 | GADTSyntax,
129 | InstanceSigs,
130 | KindSignatures,
131 | LambdaCase,
132 | MonadComprehensions,
133 | MultiParamTypeClasses,
134 | MultiWayIf,
135 | NegativeLiterals,
136 | OverloadedStrings,
137 | ParallelListComp,
138 | PartialTypeSignatures,
139 | PatternSynonyms,
140 | RankNTypes,
141 | RecordWildCards,
142 | RecursiveDo,
143 | ScopedTypeVariables,
144 | TupleSections,
145 | TypeFamilies,
146 | TypeOperators
147 |
148 | executable mvc-todo-auto
149 | default-language:
150 | Haskell2010
151 | ghc-options:
152 | -funbox-strict-fields
153 | -fforce-recomp
154 | -threaded
155 | -rtsopts
156 | -with-rtsopts=-N
157 | hs-source-dirs:
158 | apps
159 | main-is:
160 | mvc-todo-auto.hs
161 | build-depends:
162 | base >= 4.7 && < 5,
163 | protolude,
164 | mvc-todo,
165 | ghcjs-base,
166 | containers,
167 | mvc,
168 | transformers,
169 | lens,
170 | QuickCheck
171 | default-extensions:
172 | NoImplicitPrelude,
173 | UnicodeSyntax,
174 | BangPatterns,
175 | BinaryLiterals,
176 | DeriveFoldable,
177 | DeriveFunctor,
178 | DeriveGeneric,
179 | DeriveTraversable,
180 | DisambiguateRecordFields,
181 | EmptyCase,
182 | FlexibleContexts,
183 | FlexibleInstances,
184 | FunctionalDependencies,
185 | GADTSyntax,
186 | InstanceSigs,
187 | KindSignatures,
188 | LambdaCase,
189 | MonadComprehensions,
190 | MultiParamTypeClasses,
191 | MultiWayIf,
192 | NegativeLiterals,
193 | OverloadedStrings,
194 | ParallelListComp,
195 | PartialTypeSignatures,
196 | PatternSynonyms,
197 | RankNTypes,
198 | RecordWildCards,
199 | RecursiveDo,
200 | ScopedTypeVariables,
201 | TupleSections,
202 | TypeFamilies,
203 | TypeOperators
204 |
205 | source-repository head
206 | type:
207 | git
208 | location:
209 | https://github.com/tonyday567/ghcjs-testing
210 |
--------------------------------------------------------------------------------
/other/header.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/other/index.md:
--------------------------------------------------------------------------------
1 | ~~~ include
2 | other/header.md
3 | ~~~
4 |
5 |
6 | Haskell TodoMVC Example
7 | ===
8 |
9 | > Haskell is a strongly-typed, lazily-evaluated, functional programming language.
10 |
11 | - [live demo](other/mvc-todo.html)
12 |
13 | - with [automation](other/mvc-todo-auto.html)
14 |
15 | This example demonstrates an idiomatic haskell approach to the TodoMVC problem domain involving:
16 |
17 | - compilation of haskell to javascript using [ghcjs][ghcjs].
18 | - The specification of a `Model` representing the problem domain, consisting of
19 | - specification of Abstract Data Types (ADTs) for inputs, state and outputs.
20 | - an algebra between state and actions.
21 | - Use of the [mvc][mvc] library for specification, asynchronicity and separation of model, view and controllers.
22 | - The creation of `View`s that consume model outputs, by using vanilla javascript effects.
23 | - The creation of `Controller`s that produce model inputs, by listening for Dom events using vanilla javascript.
24 |
25 | recipe
26 | ===
27 |
28 | The recipe below handles the bits and bobs you need to do every re-compile. This includes a compression step via [closure](http://dl.google.com/closure-compiler).
29 |
30 |
31 |
32 | stack build --exec "pandoc -f markdown -i other/index.md -t html -o index.html --filter pandoc-include" --exec "java -jar $(stack path --local-bin)/closure-compiler-v20170124.jar --js_output_file=other/mvc-todo-auto.js $(stack path --local-install-root)/bin/mvc-todo-auto.jsexe/all.js" --exec "java -jar $(stack path --local-bin)/closure-compiler-v20170124.jar --js_output_file=other/mvc-todo.js $(stack path --local-install-root)/bin/mvc-todo.jsexe/all.js"
33 |
34 |
35 |
36 | [mvc]: https://hackage.haskell.org/package/mvc
37 | [lens]: https://hackage.haskell.org/package/lens
38 | [ghcjs]: https://github.com/ghcjs/ghcjs
39 |
40 |
--------------------------------------------------------------------------------
/other/mvc-todo-auto.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
40 |
41 | stack build --exec "pandoc -f markdown -i other/index.md -t html -o index.html --filter pandoc-include" --exec "java -jar $(stack path --local-bin)/closure-compiler-v20170124.jar --js_output_file=other/mvc-todo-auto.js $(stack path --local-install-root)/bin/mvc-todo-auto.jsexe/all.js" --exec "java -jar $(stack path --local-bin)/closure-compiler-v20170124.jar --js_output_file=other/mvc-todo.js $(stack path --local-install-root)/bin/mvc-todo.jsexe/all.js"
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/Todo/Arbitrary.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | {-# LANGUAGE FlexibleInstances #-}
3 | {-# OPTIONS_GHC -fno-warn-unused-imports #-}
4 | {-# OPTIONS_GHC -fno-warn-type-defaults #-}
5 | {-# OPTIONS_GHC -fno-warn-orphans #-}
6 |
7 | module Todo.Arbitrary where
8 |
9 | import Todo.Model
10 |
11 | import Protolude hiding (Show(..), show)
12 | import Control.Applicative
13 | import Control.Monad
14 | -- import Data.Monoid
15 | import Data.Default (def)
16 | import Test.QuickCheck
17 | import qualified Data.Map as Map
18 | import Data.String
19 | import Data.List (nub)
20 | import Prelude (Show(..))
21 | import Data.Text (pack)
22 |
23 |
24 | instance Arbitrary ItemStatus where
25 | arbitrary =
26 | elements
27 | [ Active
28 | , Completed
29 | ]
30 |
31 | instance Arbitrary ItemId where
32 | arbitrary = do
33 | let maxI = 10
34 | ItemId <$> choose (0,maxI)
35 |
36 | instance Arbitrary Item where
37 | arbitrary = Item <$> arbitrary <*> (pack . show <$> (arbitrary :: Gen TodoStatement))
38 |
39 | instance Arbitrary Todos where
40 | arbitrary =
41 | Todos <$>
42 | (pack . show <$> (arbitrary :: Gen HaskellVerb)) <*>
43 | frequency
44 | [ (8, pure Nothing)
45 | , (2, Just <$> arbitrary)
46 | ] <*>
47 | arbitrary <*>
48 | frequency
49 | [ (6, pure Nothing)
50 | , (4, Just <$> arbitrary)
51 | ] <*>
52 | (Map.fromList <$> (: []) <$> ((,) <$> arbitrary <*> arbitrary))
53 |
54 | instance Arbitrary Action where
55 | arbitrary = frequency
56 | [ (10, Toggle <$> arbitrary)
57 | , (2, pure ToggleAll)
58 | , (6, NewItem <$> (pack . show <$> (arbitrary :: Gen TodoStatement)))
59 | , (6, EditItem <$> arbitrary)
60 | , (6, EditItemCancel <$> arbitrary)
61 | , (6, EditItemDone <$> arbitrary <*> (pack . show <$> (arbitrary :: Gen TodoStatement)))
62 | , (2, Filter <$> arbitrary)
63 | , (4, DeleteItem <$> arbitrary)
64 | , (1, pure ClearCompleted)
65 | ]
66 |
67 | testApply :: IO [Todos]
68 | testApply =
69 | zipWith apply <$>
70 | sample' arbitrary <*>
71 | sample' arbitrary
72 |
73 | data TodoStatement = TodoStatement HaskellVerb HaskellNoun
74 |
75 | instance Show TodoStatement where
76 | show (TodoStatement verb noun) = show verb <> " " <> show noun
77 |
78 | newtype HaskellVerb = HaskellVerb { unVerb :: Text } deriving (Show, Eq)
79 |
80 | instance IsString HaskellVerb where
81 | fromString = HaskellVerb . fromString
82 |
83 | newtype HaskellPrefix = HaskellPrefix { unPrefix :: Text } deriving (Show, Eq)
84 | newtype Haskellism = Haskellism { unHaskellism :: Text }
85 | deriving (Show, Eq)
86 | newtype HaskellSuffix = HaskellSuffix { unSuffix :: Text } deriving (Show, Eq)
87 | data HaskellNoun = HaskellNoun [HaskellPrefix] Haskellism [HaskellSuffix]
88 |
89 | instance Show HaskellNoun where
90 | show (HaskellNoun ps h ss) = show $ mconcat (unPrefix <$> ps) <> unHaskellism h <> mconcat (unSuffix <$> ss)
91 |
92 | instance IsString HaskellNoun where
93 | fromString s = HaskellNoun [] (Haskellism (pack s)) []
94 |
95 | instance IsString Haskellism where
96 | fromString = Haskellism . fromString
97 |
98 | instance Arbitrary (TodoStatement) where
99 | arbitrary = TodoStatement <$> arbitrary <*> arbitrary
100 |
101 | instance Arbitrary (HaskellNoun) where
102 | arbitrary = frequency $
103 | [ (20, HaskellNoun <$> ((take 2 . nub) <$> arbitrary) <*> arbitrary <*> ((take 1) <$> arbitrary))
104 | , (1, pure "cabal hell")
105 | , (1, pure "ADTs")
106 | , (1, pure "everything")
107 | , (5, HaskellNoun <$> pure [] <*> arbitrary <*> pure [])
108 | ]
109 |
110 | instance Arbitrary (HaskellVerb) where
111 | arbitrary = frequency $ (\(x,y) -> (x, pure y)) <$>
112 | [ (3, "invent")
113 | , (3, "ponder")
114 | , (5, "code")
115 | , (1, "beta-reduce")
116 | , (1, "lambdify")
117 | , (3, "refactor")
118 | , (2, "reduce")
119 | , (1, "DeBruijnize")
120 | , (2, "curry")
121 | , (1, "howard-curry")
122 | , (1, "simplify")
123 | , (1, "complexificate")
124 | , (2, "git the")
125 | , (1, "build")
126 | , (1, "prettify")
127 | , (1, "compile")
128 | , (1, "generalize")
129 | , (1, "abstract")
130 | , (1, "ignore")
131 | , (1, "saturate")
132 | -- , (3, show <$> (arbitrary :: Gen Haskellism))
133 | ]
134 |
135 | instance Arbitrary (HaskellPrefix) where
136 | arbitrary = frequency $ (\(x,y) -> (x, pure (HaskellPrefix y))) <$>
137 | [ (1, "homo-")
138 | , (1, "functo-")
139 | , (2, "contra-")
140 | , (2, "bi-")
141 | , (3, "iso-")
142 | , (2, "pro-")
143 | , (4, "co-")
144 | , (4, "free-")
145 | , (1, "endo-")
146 | , (1, "morphic-")
147 | , (10, "")
148 | ]
149 |
150 | instance Arbitrary (HaskellSuffix) where
151 | arbitrary = frequency $ (\(x,y) -> (x, pure (HaskellSuffix y))) <$>
152 | [ (1, "-ism")
153 | , (1, "-orial")
154 | , (1, "-ic")
155 | , (12, "")
156 | ]
157 |
158 | instance Arbitrary (Haskellism) where
159 | arbitrary = frequency $ (\(x,y) -> (x, pure (Haskellism y))) <$>
160 | [ (6, "functor")
161 | , (4, "monoid")
162 | , (1, "dimap")
163 | , (3, "applicative")
164 | , (2, "arrow")
165 | , (3, "monad")
166 | , (1, "something")
167 | ]
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/src/Todo/Controllers.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | {-# LANGUAGE TemplateHaskell #-}
3 | {-# OPTIONS_GHC -fno-warn-type-defaults#-}
4 |
5 | module Todo.Controllers where
6 |
7 | import Todo.Vanilla
8 | ( docElement
9 | , onWindow
10 | , on
11 | , delegate
12 | , Element(..)
13 | , keyCode
14 | , itemId
15 | , getHash
16 | , elementOf
17 | , getValue
18 | , JSVal
19 | , toJSVal)
20 |
21 | import MVC hiding ((<>))
22 | import Todo.Model
23 | import Protolude hiding (on)
24 |
25 | data Keys = Escape | Enter | SomethingElse deriving (Eq)
26 |
27 | toKeys :: Int -> Keys
28 | toKeys n = case n of
29 | 13 -> Enter
30 | 27 -> Escape
31 | _ -> SomethingElse
32 |
33 | sendAction :: a -> Output a -> IO ()
34 | sendAction action o = void $ atomically $ send o action
35 |
36 | controllers :: Output Action -> IO ()
37 | controllers o = do
38 | toggleAll <- docElement ".toggle-all"
39 | clearCompleted <- docElement ".clear-completed"
40 | newTodo <- docElement ".new-todo"
41 | todos' <- docElement ".todo-list"
42 | onWindow "load" (const $ sendAction Refresh o)
43 | onWindow "hashchange" (const $ ctlHash o)
44 | on toggleAll "click" (const $ const $ sendAction ToggleAll o)
45 | on clearCompleted "click" (const $ const $ sendAction ClearCompleted o)
46 | on newTodo "keyup" (ctlNewItem o)
47 | delegate todos' ".destroy" "click" (\el _ -> ctlId DeleteItem o el)
48 | delegate todos' ".toggle" "change" (\el _ -> ctlId Toggle o el)
49 | delegate todos' "label" "dblclick" (\el _ -> ctlId EditItem o el)
50 | delegate todos' ".edit" "blur" (ctlEditItemDone o)
51 | delegate todos' ".edit" "keyup" (ctlEditKeyup o)
52 |
53 | ctlId :: (ItemId -> Action) -> Output Action -> Element -> IO ()
54 | ctlId action o el = do
55 | a_ <- idTagged action el
56 | sendAction a_ o
57 |
58 | idTagged :: (ItemId -> Action) -> Element -> IO Action
59 | idTagged action el = do
60 | id' <- itemId el
61 | case id' of
62 | Nothing -> pure NoAction
63 | Just x -> pure (action (ItemId x))
64 |
65 | ctlEditKeyup :: Output Action -> Element -> JSVal -> IO ()
66 | ctlEditKeyup o el ev = do
67 | code <- keyCode ev
68 | case toKeys code of
69 | Enter -> ctlEditItemDone o el ev
70 | Escape -> ctlId EditItemCancel o el
71 | _ -> pure ()
72 |
73 | ctlEditItemDone :: Output Action -> Element -> JSVal -> IO ()
74 | ctlEditItemDone o el _ = do
75 | box <- elementOf el ".edit"
76 | value <- getValue (Element box)
77 | a_ <- idTagged (\x -> EditItemDone x value) el
78 | sendAction a_ o
79 |
80 | ctlHash :: Output Action -> IO ()
81 | ctlHash o = do
82 | hash <- getHash
83 | let path = takeWhile (not . (== '/')) . drop 1 . dropWhile (not . (== '/')) $ (show hash)
84 | let path_ =
85 | case path of
86 | "completed" -> Just Completed
87 | "active" -> Just Active
88 | _ -> Nothing
89 | sendAction (Filter path_) o
90 |
91 | ctlNewItem :: Output Action -> Element -> JSVal -> IO ()
92 | ctlNewItem o el ev = do
93 | code <- keyCode ev
94 | value <- getValue el
95 | if toKeys code == Enter && value /= mempty
96 | then (sendAction (NewItem value) o)
97 | else pure ()
98 |
--------------------------------------------------------------------------------
/src/Todo/Model.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE DeriveDataTypeable #-}
2 | {-# LANGUAGE DeriveFoldable #-}
3 | {-# LANGUAGE DeriveGeneric #-}
4 | {-# LANGUAGE DeriveTraversable #-}
5 | {-# LANGUAGE FlexibleContexts #-}
6 | {-# LANGUAGE FlexibleInstances #-}
7 | {-# LANGUAGE FunctionalDependencies #-}
8 | {-# LANGUAGE MultiParamTypeClasses #-}
9 | {-# LANGUAGE OverloadedStrings #-}
10 | {-# LANGUAGE TemplateHaskell #-}
11 | {-# OPTIONS_GHC-fno-warn-type-defaults#-}
12 |
13 | module Todo.Model
14 | ( Todos(..)
15 | , HasTodos(..)
16 | , Action(..)
17 | , Out(..)
18 | , _StateOut
19 | , _ActionOut
20 | , Item(..)
21 | , HasItem(..)
22 | , ItemId(..)
23 | , ItemStatus(..)
24 | , toggleStatus
25 | , apply
26 | , model
27 | ) where
28 |
29 | import Control.Lens
30 | import Control.Monad.Trans.State.Strict (State, StateT)
31 | import Data.Default
32 | import Pipes
33 | import Protolude hiding (State, StateT, loop)
34 |
35 | import qualified Data.Map as Map
36 |
37 | -- * ADTs
38 | data ItemStatus
39 | = Active
40 | | Completed
41 | deriving (Show, Eq)
42 |
43 | toggleStatus :: ItemStatus -> ItemStatus
44 | toggleStatus Active = Completed
45 | toggleStatus Completed = Active
46 |
47 | newtype ItemId = ItemId { unItemId :: Int }
48 | deriving (Show, Eq, Ord)
49 |
50 | data Item = Item { _itemStatus :: ItemStatus, _itemText :: Text }
51 | deriving (Show, Eq)
52 |
53 | data Todos =
54 | Todos
55 | { _todosNewItem :: Text -- drafted new todo item
56 | , _todosEditing :: Maybe ItemId -- a todo item is being edited
57 | , _todosNextId :: ItemId -- an item id source
58 | , _todosFilter :: Maybe ItemStatus -- maybe a filter is in place
59 | , _todosItems :: Map.Map ItemId Item -- the todo items
60 | }
61 | deriving (Show, Eq)
62 |
63 | makeClassy ''Item
64 |
65 | makeClassy ''Todos
66 |
67 | instance Default Todos where
68 | def = Todos mempty Nothing (ItemId 0) Nothing Map.empty
69 |
70 | data Action
71 | = ClearCompleted
72 | | DeleteItem ItemId
73 | | EditItem ItemId
74 | | EditItemCancel ItemId
75 | | EditItemDone ItemId Text
76 | | Filter (Maybe ItemStatus)
77 | | NewItem Text
78 | | NoAction
79 | | Refresh
80 | | Toggle ItemId
81 | | ToggleAll
82 | deriving (Show, Eq)
83 |
84 | -- * algebra | apply an action to the todo model
85 | apply :: Action -> Todos -> Todos
86 | apply ClearCompleted tds =
87 | over todosItems (Map.filter (\x -> view itemStatus x /= Completed)) tds
88 | apply (DeleteItem x) tds = over todosItems (Map.delete x) tds
89 | apply (EditItem x) tds = set todosEditing (Just x) tds
90 | apply (EditItemCancel _) tds = set todosEditing Nothing tds
91 | apply (EditItemDone x t) tds = over todosItems adjustOrDelete $ set todosEditing Nothing tds
92 | where
93 | adjustOrDelete =
94 | if t == mempty
95 | then Map.delete x
96 | else Map.adjust (set itemText t) x
97 | apply (Filter f) tds = set todosFilter f tds
98 | apply (NewItem t) tds =
99 | if t == mempty
100 | then tds
101 | else
102 | over todosItems
103 | (Map.insert (view todosNextId tds) (Item Active t)) $
104 | over todosNextId (\(ItemId x) -> (ItemId (x + 1))) $
105 | set todosNewItem mempty tds
106 | apply NoAction tds = tds
107 | apply Refresh tds = tds
108 | apply (Toggle x) tds =
109 | over todosItems (Map.adjust (over itemStatus toggleStatus) x) tds
110 | apply ToggleAll tds =
111 | over todosItems (over (traverse . itemStatus) toggleStatus) tds
112 |
113 | modifyState :: Action -> ListT (State Todos) Todos
114 | modifyState action = case action of
115 | NoAction -> mzero
116 | _ -> do
117 | tds <- lift get
118 | let tds' = apply action tds
119 | lift $ put tds'
120 | pure tds'
121 |
122 | data Out
123 | = StateOut Todos
124 | | ActionOut Action
125 | deriving (Show, Eq)
126 |
127 | makePrisms ''Out
128 |
129 | -- | apply the incoming action to state and pass through the action (just so it can be console logged)
130 | model :: Action -> ListT (State Todos) Out
131 | model action =
132 | (StateOut <$> modifyState action) `mappend` (ActionOut <$> pure action)
133 |
134 |
--------------------------------------------------------------------------------
/src/Todo/Vanilla.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE CPP #-}
2 | {-# LANGUAGE TypeSynonymInstances #-}
3 | {-# LANGUAGE FlexibleInstances #-}
4 | {-# OPTIONS_GHC -fno-warn-orphans #-}
5 | {-# LANGUAGE OverloadedStrings #-}
6 | {-# OPTIONS_GHC -fno-warn-type-defaults #-}
7 |
8 | module Todo.Vanilla
9 | ( Element(..)
10 | , Selector
11 | , consoleLog
12 | , alert
13 | , onload
14 | , addClass
15 | , removeClass
16 | , setHtml
17 | , docElement
18 | , elementsOf
19 | , elementOf
20 | , getValue
21 | , setValue
22 | , itemId
23 | , focus
24 | , checked
25 | , keyCode
26 | , getHash
27 | , on
28 | , onWindow
29 | , delegate
30 | , JSVal
31 | , toJSVal
32 | ) where
33 |
34 | import Clay.Render (renderSelector)
35 | import Clay.Selector (Selector)
36 | import Data.Char
37 | import Data.Foldable
38 | import Data.Maybe (fromJust)
39 | import Data.Typeable
40 | import Lucid
41 | import Protolude hiding (Selector, on)
42 | import Unsafe.Coerce (unsafeCoerce)
43 |
44 | import qualified Data.Text.Lazy as LText
45 | import qualified Text.Read as Text
46 |
47 | import GHCJS.Types
48 | import GHCJS.Marshal
49 | import GHCJS.Foreign
50 | import GHCJS.Foreign.Callback
51 | import GHCJS.Nullable (maybeToNullable, nullableToMaybe, Nullable)
52 | import Data.JSString.Text (textToJSString, textFromJSString, lazyTextToJSString, lazyTextFromJSString)
53 |
54 | data Element = Element JSVal
55 |
56 | fromHtml = lazyTextToJSString . renderText
57 |
58 | fromSel = lazyTextToJSString . renderSelector
59 |
60 | -- * the big 3
61 | -- | console.log
62 | foreign import javascript unsafe
63 | "console.log($1)"
64 | js_consoleLog :: JSString -> IO ()
65 |
66 | consoleLog :: Text -> IO ()
67 | consoleLog a = js_consoleLog (textToJSString a)
68 |
69 | foreign import javascript unsafe
70 | "alert($1)" js_alert :: JSString -> IO ()
71 |
72 | alert :: Text -> IO ()
73 | alert t = js_alert (textToJSString t)
74 |
75 | foreign import javascript unsafe "window.onload = $1"
76 | js_onload :: Callback (IO ()) -> IO ()
77 |
78 | onload :: IO () -> IO ()
79 | onload f = do
80 | f_ <- asyncCallback f
81 | js_onload f_
82 |
83 |
84 | -- * element manipulation
85 | foreign import javascript unsafe
86 | "$1.classList.add($2)"
87 | js_classListAdd :: JSVal -> JSString -> IO ()
88 |
89 | addClass :: Element -> Selector -> IO ()
90 | addClass (Element el) sel =
91 | js_classListAdd el (fromSel sel)
92 |
93 | foreign import javascript unsafe
94 | "$1.classList.remove($2)"
95 | js_classListRemove :: JSVal -> JSString -> IO ()
96 |
97 | removeClass :: Element -> Selector -> IO ()
98 | removeClass (Element el) sel =
99 | js_classListRemove el (fromSel sel)
100 |
101 | foreign import javascript unsafe
102 | "$1.innerHTML = $2"
103 | js_innerHtml :: JSVal -> JSString -> IO ()
104 |
105 | setHtml :: Element -> Html () -> IO ()
106 | setHtml (Element el) html =
107 | js_innerHtml el (fromHtml html)
108 |
109 | foreign import javascript unsafe
110 | "document.querySelector($1)"
111 | js_docQuerySelector :: JSString -> IO JSVal
112 |
113 | -- | element selection
114 | docElement :: Selector -> IO Element
115 | docElement sel =
116 | Element <$> js_docQuerySelector (fromSel sel)
117 |
118 | foreign import javascript unsafe
119 | "$1.querySelectorAll($2)"
120 | js_querySelectorAll :: JSVal -> JSString -> IO JSVal
121 |
122 | elementsOf :: Element -> Selector -> IO JSVal
123 | elementsOf (Element el) sel = js_querySelectorAll el (fromSel sel)
124 |
125 | foreign import javascript unsafe
126 | "$1.querySelector($2)"
127 | js_querySelector :: JSVal -> JSString -> IO JSVal
128 |
129 | elementOf :: Element -> Selector -> IO JSVal
130 | elementOf (Element el) sel = js_querySelector el (fromSel sel)
131 |
132 | foreign import javascript unsafe
133 | "$1.value"
134 | js_getValue :: JSVal -> IO JSString
135 |
136 | getValue :: Element -> IO Text
137 | getValue (Element el) = textFromJSString <$> js_getValue el
138 |
139 | foreign import javascript unsafe
140 | "$1.value = $2"
141 | js_setValue :: JSVal -> JSString -> IO ()
142 |
143 | setValue :: Element -> Text -> IO ()
144 | setValue (Element el) val = js_setValue el (textToJSString val)
145 |
146 | foreign import javascript unsafe
147 | "$1.dataset.id"
148 | js_getDatasetId :: JSVal -> IO JSString
149 |
150 | itemId :: Element -> IO (Maybe Int)
151 | itemId (Element el) = do
152 | i <- js_getDatasetId el
153 | pure $ readMaybe (LText.unpack $ lazyTextFromJSString i)
154 |
155 | foreign import javascript unsafe
156 | "$1.focus()"
157 | js_focus :: JSVal -> IO ()
158 |
159 | focus :: Element -> IO ()
160 | focus (Element el) = js_focus el
161 |
162 | foreign import javascript unsafe
163 | "$1.item($2)"
164 | js_getItem :: JSVal -> Int -> IO (Nullable JSVal)
165 |
166 | -- getItem :: NodeList -> Int -> IO (Maybe Node)
167 | getItem :: JSVal -> Int -> IO (Maybe JSVal)
168 | getItem list n =
169 | nullableToMaybe <$> js_getItem list n
170 |
171 | foreign import javascript unsafe
172 | "$1.keyCode"
173 | js_keyCode :: JSVal -> IO Int
174 |
175 | keyCode :: JSVal -> IO Int
176 | keyCode ev = js_keyCode ev
177 |
178 | foreign import javascript unsafe
179 | "document.location.hash"
180 | js_getHash :: IO JSString
181 |
182 | getHash :: IO JSString
183 | getHash = js_getHash
184 |
185 | foreign import javascript unsafe
186 | "$1.checked"
187 | js_checked :: JSVal -> IO Bool
188 |
189 | checked :: Element -> IO Bool
190 | checked (Element el) = js_checked el
191 |
192 | -- * event listening
193 | foreign import javascript unsafe
194 | "$1.addEventListener($2,$3,$4)"
195 | js_addEventListener :: JSVal -> JSString -> Callback (JSVal -> IO ()) -> Bool -> IO ()
196 |
197 | on :: Element -> Text -> (Element -> JSVal -> IO ()) -> IO ()
198 | on (Element el) uiAction handler = do
199 | handler' <- asyncCallback1 (handler (Element el))
200 | js_addEventListener el (textToJSString uiAction) handler' False
201 |
202 | foreign import javascript unsafe
203 | "window.addEventListener($1,$2,$3)"
204 | jsWindowAddEventListener :: JSString -> Callback (JSVal -> IO ()) -> Bool -> IO ()
205 |
206 | -- null = maybeToNullable Nothing
207 |
208 | onWindow :: LText.Text -> (JSVal -> IO ()) -> IO ()
209 | onWindow uiAction handler = do
210 | handler' <- asyncCallback1 handler
211 | jsWindowAddEventListener
212 | (lazyTextToJSString uiAction)
213 | handler'
214 | False
215 |
216 | delegate :: Element -> Selector -> Text -> (Element -> JSVal -> IO ()) -> IO ()
217 | delegate (Element base) pattern0 ev action = do
218 | let useCapture = ev == "blur" || ev == "focus"
219 | dispatch <- asyncCallback1 $ applyIn (Element base) pattern0 action
220 | js_addEventListener base (textToJSString ev) dispatch useCapture
221 |
222 | foreign import javascript unsafe
223 | "$1.target"
224 | js_target :: JSVal -> IO JSVal
225 |
226 | foreign import javascript unsafe
227 | "$1===$2"
228 | js_eq :: JSVal -> JSVal -> IO Bool
229 |
230 | foreign import javascript unsafe
231 | "$1.length"
232 | js_length :: JSVal -> IO Int
233 |
234 | applyIn :: Element -> Selector -> (Element -> JSVal -> IO ()) -> JSVal -> IO ()
235 | applyIn base pattern0 action ev = do
236 | t <- js_target ev
237 | p <- elementsOf base pattern0
238 | n <- js_length p
239 | elem' <- findM
240 | (\x -> do
241 | item <- getItem p x
242 | case item of
243 | Nothing -> pure False
244 | Just item' -> js_eq t item') [0..n]
245 | case elem' of
246 | Nothing -> pure ()
247 | Just iElem -> do
248 | eitem <- getItem p iElem
249 | case eitem of
250 | Nothing -> pure ()
251 | Just eitem' -> do
252 | li <- findUp (Element eitem') "li"
253 | case li of
254 | Nothing -> print "no li parent???"
255 | Just li' -> action li' ev
256 |
257 | foreign import javascript unsafe
258 | "$1.parentElement"
259 | js_parentElement :: JSVal -> IO (Nullable JSVal)
260 |
261 | foreign import javascript unsafe
262 | "$1.tagName"
263 | js_tagName :: JSVal -> IO JSString
264 |
265 | -- | go up the tree and find an element, if it exists
266 | findUp :: Element -> LText.Text -> IO (Maybe Element)
267 | findUp (Element base) sel = do
268 | parent <- nullableToMaybe <$> js_parentElement base
269 | case parent of
270 | Nothing -> pure Nothing
271 | Just parent' -> do
272 | name <- js_tagName parent'
273 | if (LText.toLower $ lazyTextFromJSString name) == (LText.toLower sel)
274 | then pure $ Just (Element parent')
275 | else findUp (Element parent') sel
276 |
277 | findM :: (Monad m, Traversable t) => (a -> m Bool) -> t a -> m (Maybe a)
278 | findM p = fmap (getFirst . fold) . mapM
279 | (fmap First . (\x -> do
280 | p' <- p x
281 | pure $ if p' then Just x else Nothing))
282 |
--------------------------------------------------------------------------------
/src/Todo/Views.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | {-# OPTIONS_GHC-fno-warn-type-defaults#-}
3 |
4 | module Todo.Views
5 | ( render
6 | ) where
7 |
8 | import Todo.Vanilla
9 | ( Element(..)
10 | , docElement
11 | , addClass
12 | , removeClass
13 | , Selector
14 | , setValue
15 | , setHtml
16 | , focus
17 | , elementOf
18 | )
19 |
20 | import Control.Lens hiding (element, elementOf, itemText, itemStatus)
21 | import Control.Monad (join)
22 | import Data.Foldable (sequenceA_)
23 | import Data.Text (pack)
24 | import Lucid hiding (for_)
25 | import Protolude hiding (on, Selector)
26 | import Todo.Model
27 | import qualified Clay.Selector as Clay
28 | import qualified Data.Map as Map
29 |
30 | render :: Todos -> IO ()
31 | render = sequenceRenderers renderers
32 |
33 | sequenceRenderers :: [Todos -> IO ()] -> Todos -> IO ()
34 | sequenceRenderers rs tds = sequenceA_ $ ($) <$> rs <*> pure tds
35 |
36 | renderers :: [Todos -> IO ()]
37 | renderers =
38 | [ renderClearCompleted
39 | , renderFilter
40 | , renderHideStuff
41 | , renderNewItem
42 | , renderTodoCount
43 | , renderTodoList
44 | , renderFocus
45 | ]
46 |
47 | renderClearCompleted :: Todos -> IO ()
48 | renderClearCompleted tds = do
49 | el <- docElement ".clear-completed"
50 | action el hidden
51 | where
52 | hidden = "hidden" :: Selector
53 | numCompleted =
54 | getSum $
55 | foldMap
56 | (\x -> Sum (if view itemStatus x == Completed then 1 else 0))
57 | (view todosItems tds)
58 | action = if numCompleted == 0
59 | then addClass
60 | else removeClass
61 |
62 | renderFilter :: Todos -> IO ()
63 | renderFilter tds = do
64 | el <- docElement ".filters .selected"
65 | removeClass el selected
66 | newSelection <- docElement
67 | (Clay.element $ ".filters [href='#/" <> currentPage <> "']")
68 | addClass newSelection selected
69 | where
70 | selected = "selected" :: Selector
71 | currentPage =
72 | case view todosFilter tds of
73 | Nothing -> ""
74 | Just Active -> "active"
75 | Just Completed -> "completed"
76 |
77 | renderHideStuff :: Todos -> IO ()
78 | renderHideStuff tds = do
79 | main <- docElement ".main"
80 | foot <- docElement ".footer"
81 | action main hidden
82 | action foot hidden
83 | where
84 | hidden = "hidden" :: Selector
85 | action =
86 | if null (view todosItems tds)
87 | then addClass
88 | else removeClass
89 |
90 | renderNewItem :: Todos -> IO ()
91 | renderNewItem tds = do
92 | el <- docElement ".new-todo"
93 | setValue el (tds^.todosNewItem)
94 |
95 | renderTodoCount :: Todos -> IO ()
96 | renderTodoCount tds = do
97 | el <- docElement ".todo-count"
98 | setHtml el itemsLeft
99 | where
100 | itemsLeft = strong_ (toHtml $ (show n :: Text)) <> " item" <> s <> " left"
101 | n = getSum $
102 | foldMap
103 | (\x -> Sum (if view itemStatus x == Active then 1 else 0))
104 | (view todosItems tds) :: Integer
105 | s = if n == 1
106 | then ""
107 | else "s"
108 |
109 | renderTodoList :: Todos -> IO ()
110 | renderTodoList tds = do
111 | el <- docElement ".todo-list"
112 | setHtml el (htmlItems tds)
113 |
114 | renderFocus :: Todos -> IO ()
115 | renderFocus tds =
116 | case view todosEditing tds of
117 | Nothing -> pure ()
118 | Just _ -> do
119 | el <- docElement ".todo-list"
120 | elEditing <- elementOf el ".editing .edit"
121 | focus (Element elEditing)
122 |
123 | htmlItems :: Todos -> Html ()
124 | htmlItems tds =
125 | mconcat $
126 | fmap
127 | (\(ItemId id', item') ->
128 | htmlItem item' id' (editing == Just (ItemId id')) (visible item'))
129 | items
130 | where
131 | items = Map.toAscList (view todosItems tds)
132 | editing = view todosEditing tds
133 | visible item' =
134 | case view todosFilter tds of
135 | Nothing -> True
136 | Just Active -> view itemStatus item' == Active
137 | Just Completed -> view itemStatus item' == Completed
138 |
139 | htmlItem :: Item -> Int -> Bool -> Bool -> Html ()
140 | htmlItem item' id' editing visible =
141 | li_ (liClass <> [data_ "id" (pack $ show id')])
142 | (div_ itemClass
143 | (input_ ([class_ "toggle", type_ "checkbox"] ++ checked)
144 | <> label_ (toHtml (view itemText item'))
145 | <> button_ [class_ "destroy"] mempty)
146 | <> input_ [class_ "edit", value_ (view itemText item')])
147 | where
148 | liClass
149 | | editing && (view itemStatus item' == Completed) =
150 | [class_ "editing completed"]
151 | | editing = [class_ "editing"]
152 | | view itemStatus item' == Completed = [class_ "completed"]
153 | | otherwise = []
154 | itemClass =
155 | if visible
156 | then [class_ "view"]
157 | else [class_ "hidden"]
158 | checked =
159 | case view itemStatus item' of
160 | Completed -> [checked_]
161 | Active -> []
162 |
--------------------------------------------------------------------------------
/stack.yaml:
--------------------------------------------------------------------------------
1 | resolver: lts-7.15
2 |
3 | packages:
4 | - '.'
5 |
6 | extra-deps:
7 | - ghcjs-dom-0.7.0.4
8 | - ghcjs-dom-jsffi-0.7.0.4
9 | - mvc-1.1.3
10 |
11 | flags: {}
12 | extra-package-dbs: []
13 |
14 | # compiler: ghcjs-0.1.0.20150924_ghc-7.10.2
15 | # compiler: ghcjs
16 |
17 | compiler: ghcjs-0.2.1.9007015_ghc-8.0.1
18 | compiler-check: match-exact
19 |
20 | setup-info:
21 | ghcjs:
22 | source:
23 | ghcjs-0.2.1.9007015_ghc-8.0.1:
24 | url: http://ghcjs.tolysz.org/ghc-8.0-2017-01-11-lts-7.15-9007015.tar.gz
25 | sha1: 30d34e9d704bdb799066387dfa1ba98b8884d932
26 |
--------------------------------------------------------------------------------