├── .gitignore ├── src ├── texture.png ├── Model.elm ├── Todo.elm ├── Update.elm ├── Inputs.elm └── Display.elm └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | build 3 | -------------------------------------------------------------------------------- /src/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evancz/TodoFRP/HEAD/src/texture.png -------------------------------------------------------------------------------- /src/Model.elm: -------------------------------------------------------------------------------- 1 | module Model where 2 | 3 | {-| All of the information needed to represent a Task -} 4 | type Task = { done:Bool, description:String, id:Int } 5 | 6 | {-| The state of the application is a list of tasks and an ID for naming tasks 7 | uniquely. 8 | -} 9 | type TodoState = { tasks:[Task], uid:Int } 10 | 11 | initialState : TodoState 12 | initialState = { tasks=[], uid=0 } 13 | 14 | {-| Actions the user can take to modify the TodoState -} 15 | data Action = Add String | Remove Int 16 | -------------------------------------------------------------------------------- /src/Todo.elm: -------------------------------------------------------------------------------- 1 | module Todo where 2 | 3 | import Window 4 | import Model (initialState, TodoState) 5 | import Update (update) 6 | import Display (display) 7 | import Inputs (fieldContent, actions) 8 | 9 | 10 | -- Use the UI inputs to update starting from the initial state. 11 | currentState : Signal TodoState 12 | currentState = foldp update initialState actions 13 | 14 | -- Show everything on screen. 15 | main : Signal Element 16 | main = display <~ Window.dimensions 17 | ~ fieldContent 18 | ~ lift .tasks currentState 19 | -------------------------------------------------------------------------------- /src/Update.elm: -------------------------------------------------------------------------------- 1 | module Update where 2 | 3 | import Model (..) 4 | 5 | -- Update the TodoState based on a user Action. 6 | update : Action -> TodoState -> TodoState 7 | update action state = 8 | case action of 9 | -- ignore if the user tries to add an empty task 10 | Add "" -> state 11 | 12 | -- add a task with a unique ID 13 | Add taskDescription -> 14 | { tasks = (Task False taskDescription state.uid) :: state.tasks 15 | , uid = state.uid + 1 } 16 | 17 | -- keep tasks that do not have the removed ID 18 | Remove id -> 19 | { state | 20 | tasks <- filter (\task -> task.id /= id) state.tasks 21 | } 22 | -------------------------------------------------------------------------------- /src/Inputs.elm: -------------------------------------------------------------------------------- 1 | module Inputs (field, remove, actions, fieldContent) where 2 | {-| Create the Signals and Inputs needed to model interaction in this program. 3 | The Inputs we create will provide handles for our display to give us new 4 | events, and from there, it will provide data to update our program. 5 | -} 6 | 7 | import Graphics.Input (Input, input) 8 | import Graphics.Input.Field as Field 9 | import Keyboard 10 | import Model (Action(Add, Remove)) 11 | 12 | ---- Inputs ---- 13 | 14 | {-| An Input to keep track of the primary text field. -} 15 | field : Input Field.Content 16 | field = input Field.noContent 17 | 18 | {-| An Input to keep track of requests to remove tasks. -} 19 | remove : Input Int 20 | remove = input 0 21 | 22 | 23 | ---- Useful Signals ---- 24 | 25 | {-| Current content of the primary input field. Normally uses whatever the user 26 | types in, but if the user presses enter it clears the field. 27 | -} 28 | fieldContent : Signal Field.Content 29 | fieldContent = 30 | merge field.signal (always Field.noContent <~ entered) 31 | 32 | {-| Merge all UI inputs into Actions. -} 33 | actions : Signal Action 34 | actions = merge (Add <~ sampleOn entered (.string <~ field.signal)) 35 | (Remove <~ remove.signal) 36 | 37 | {-| Signal that updates when the enter key is pressed. We will use it to sample 38 | other signals. Actual value of this signal is not important. 39 | -} 40 | entered : Signal () 41 | entered = always () <~ keepIf identity True Keyboard.enter 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TodoFRP – [live demo](http://evancz.github.io/TodoFRP) 2 | 3 | This basic todo list demonstrates how FRP and Elm 4 | can make writing traditional web apps easier. 5 | Currently it is quite simple, but that makes it a 6 | nice resource for learning more about making "traditional 7 | webapps" with Elm. 8 | 9 | ### Build Locally 10 | 11 | After installing the Elm compiler, follow these steps: 12 | 13 | ```bash 14 | git clone https://github.com/evancz/TodoFRP.git 15 | cd TodoFRP/src/ 16 | elm --make Todo.elm 17 | ``` 18 | 19 | Then open `build/Todo.html` in your browser. 20 | 21 | If you want to be fancier, you can run `elm-server` in the `src/` directory. 22 | Then navigate to [localhost:8000/Todo.elm](http://localhost:8000/Todo.elm). 23 | The project will be recompiled whenever you refresh that page in your browser. 24 | 25 | ### Project Layout 26 | 27 | All of the code for this project lives in the `src/` directory. 28 | 29 | * [Model.elm](https://github.com/evancz/TodoFRP/blob/master/src/Model.elm): 30 | Representation of the todo list application. 31 | * [Update.elm](https://github.com/evancz/TodoFRP/blob/master/src/Update.elm): 32 | Describes how to update the todo list based on user's actions. 33 | * [Inputs.elm](https://github.com/evancz/TodoFRP/blob/master/src/Inputs.elm): 34 | Describe the UI input elements and the actions the user's actions. 35 | * [Display.elm](https://github.com/evancz/TodoFRP/blob/master/src/Display.elm): 36 | How to display our model and inputs on screen. 37 | * [Todo.elm](https://github.com/evancz/TodoFRP/blob/master/src/Todo.elm): 38 | Bring together the model, update, inputs, and display to create the todo list. 39 | -------------------------------------------------------------------------------- /src/Display.elm: -------------------------------------------------------------------------------- 1 | module Display (display) where 2 | 3 | import Model (Task) 4 | import Inputs (field, remove) 5 | import Graphics.Input as Input 6 | import Graphics.Input.Field as Field 7 | import Text as Text 8 | 9 | -- Constants for easy tweaking 10 | todoWidth = 500 11 | leftPadding = 60 12 | buttonWidth = 30 13 | taskHeight = 30 14 | 15 | titleColor = rgb 179 179 179 16 | spacerColor1 = rgb 141 125 119 17 | spacerColor2 = rgb 108 125 119 18 | inputColor = rgb 247 247 247 19 | taskColor = rgba 255 255 255 0.9 20 | 21 | 22 | -- Actually display the entire Todo list. 23 | display : (Int,Int) -> Field.Content -> [Task] -> Element 24 | display (w,h) fieldContent tasks = 25 | let pos = midTopAt (relative 0.5) (absolute 40) in 26 | layers [ tiledImage w h "/texture.png" 27 | , container w h pos <| flow down [ header 28 | , inputBar fieldContent 29 | , flow down (map displayTask tasks) ] 30 | ] 31 | 32 | header : Element 33 | header = 34 | flow down 35 | [ width todoWidth << centered << Text.color titleColor << Text.height 48 <| toText "todos" 36 | , color spacerColor1 (spacer todoWidth 15) 37 | , color spacerColor2 (spacer todoWidth 1 ) ] 38 | 39 | inputBar : Field.Content -> Element 40 | inputBar fieldContent = 41 | color inputColor << container todoWidth 45 midRight << 42 | color inputColor << size (todoWidth - leftPadding) 45 <| 43 | Field.field todoStyle field.handle identity "What needs to be done?" fieldContent 44 | 45 | todoStyle : Field.Style 46 | todoStyle = 47 | { padding = Field.uniformly 0 48 | , outline = Field.noOutline 49 | , highlight = Field.noHighlight 50 | , style = Text.defaultStyle 51 | } 52 | 53 | -- Display a specific task. 54 | displayTask : Task -> Element 55 | displayTask task = 56 | let btn clr = 57 | let x = leftAligned << Text.height (taskHeight-4) << Text.color clr <| toText "X" 58 | in container buttonWidth taskHeight middle x 59 | taskWidth = todoWidth - leftPadding - buttonWidth 60 | in 61 | color taskColor << container todoWidth taskHeight midRight <| 62 | flow right [ container taskWidth taskHeight midLeft << leftAligned <| toText task.description 63 | , Input.customButton remove.handle task.id 64 | (btn inputColor) (btn titleColor) (btn red) 65 | ] 66 | --------------------------------------------------------------------------------