Skip to content

Instantly share code, notes, and snippets.

@pete-murphy
Created May 27, 2020 04:30
Show Gist options
  • Save pete-murphy/3026cb54316492b714e3fc0c7b7fa7ce to your computer and use it in GitHub Desktop.
Save pete-murphy/3026cb54316492b714e3fc0c7b7fa7ce to your computer and use it in GitHub Desktop.
module Main where
import Prelude
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)
import Data.Array as Array
import Data.Foldable (traverse_)
import React.Basic (keyed)
import React.Basic as RB
import React.Basic.DOM as R
import React.Basic.DOM.Events (preventDefault, stopPropagation, targetValue)
import React.Basic.Events (handler, handler_)
import React.Basic.Events as Events
import React.Basic.Hooks (Component, component, empty, useReducer, useState, (/\))
import React.Basic.Hooks as React
main :: Effect Unit
main = do
container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
case container of
Nothing -> throw "Container element not found."
Just c -> do
ex <- mkExample
R.render (ex unit) c
data Action
= CreateTodo String
| ToggleTodo Int
| DeleteTodo Int
| SetFilter TodoFilter
data TodoFilter
= All
| Complete
| Incomplete
derive instance eqTodoFilter :: Eq TodoFilter
type Todo
= { task :: String
, isComplete :: Boolean
}
type State
= { todos :: Array Todo
, filter :: TodoFilter
}
reducer :: State -> Action -> State
reducer state = case _ of
CreateTodo task -> state { todos = Array.cons { task, isComplete: false } state.todos }
ToggleTodo index -> case Array.modifyAt index (\todo -> todo { isComplete = not todo.isComplete }) state.todos of
Just todos -> state { todos = todos }
Nothing -> state
DeleteTodo index -> case Array.deleteAt index state.todos of
Just todos -> state { todos = todos }
Nothing -> state
SetFilter filter -> state { filter = filter }
mkExample :: Component Unit
mkExample = do
let
initialState = { todos: [], filter: All }
todoInput <- mkTodoInput
todoRow <- mkTodoRow
todoFilters <- mkTodoFilters
component "TodoApp" \_ -> React.do
state /\ dispatch <- useReducer initialState reducer
pure
$ R.div
{ children:
[ todoInput { dispatch }
, R.div_
$ flip Array.mapWithIndex state.todos \id todo ->
if state.filter == All
|| (todo.isComplete && state.filter == Complete)
|| (not todo.isComplete && state.filter == Incomplete) then
keyed (show id) $ todoRow { id, todo, dispatch }
else
empty
, todoFilters { filter: state.filter, dispatch }
]
, style:
R.css
{ maxWidth: "600px"
, margin: "auto"
, padding: "16px"
, fontFamily: "sans-serif"
, fontSize: "16px"
}
}
where
todoAppEl = RB.element $ R.unsafeCreateDOMComponent "todo-app"
mkTodoInput :: Component { dispatch :: Action -> Effect Unit }
mkTodoInput = do
component "TodoInput" \props -> React.do
value /\ setValue <- useState ""
pure
$ R.form
{ onSubmit:
handler (preventDefault >>> stopPropagation) \_ -> do
props.dispatch $ CreateTodo value
setValue $ const ""
, children:
[ R.input
{ value
, onChange:
handler (preventDefault >>> stopPropagation >>> targetValue)
$ traverse_ (setValue <<< const)
, style: R.css { lineHeight: "32px", width: "100%", boxSizing: "border-box" }
, placeholder: "Enter a task"
}
]
, style: R.css { marginBottom: "16px", width: "100%" }
}
mkTodoRow :: Component { id :: Int, todo :: Todo, dispatch :: Action -> Effect Unit }
mkTodoRow =
component "Todo" \props -> React.do
pure
$ R.div
{ children:
[ R.label
{ children:
[ R.input
{ type: "checkbox"
, checked: props.todo.isComplete
, onChange: Events.handler_ $ props.dispatch $ ToggleTodo props.id
, tabIndex: 0
}
, R.text props.todo.task
]
, style: R.css { lineHeight: "32px", fontSize: "24px", flex: "1 0 auto" }
}
, R.a
{ children: [ R.text "❌" ]
, onClick: handler_ $ props.dispatch $ DeleteTodo props.id
, style: R.css { cursor: "pointer" }
}
]
, style:
R.css
{ display: "flex"
, flexFlow: "row"
, alignItems: "center"
}
}
mkTodoFilters :: Component { filter :: TodoFilter, dispatch :: Action -> Effect Unit }
mkTodoFilters =
component "TodoFilters" \props -> React.do
let
filterLink f label =
R.a
{ children: [ R.text label ]
, onClick: handler_ $ props.dispatch $ SetFilter f
, style:
if props.filter == f then
R.css { cursor: "pointer", fontWeight: "bold" }
else
R.css { cursor: "pointer" }
}
pure
$ R.div
{ children:
[ R.hr { style: R.css { color: "lightgrey" } }
, filterLink All "All"
, R.text " / "
, filterLink Complete "Complete"
, R.text " / "
, filterLink Incomplete "Incomplete"
]
, style: R.css { marginTop: "16px" }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment