Commit 6773ba88 authored by Addy Osmani's avatar Addy Osmani

Merge pull request #1041 from tastejs/elm

Elm Example
parents 3a7d4390 20385de2
node_modules/todomvc-app-css/*
!node_modules/todomvc-app-css/index.css
node_modules/todomvc-common/*
!node_modules/todomvc-common/base.css
!node_modules/todomvc-common/base.js
module Task where
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import Json.Decode as Json
import LocalChannel as LC
import Maybe
import Signal
import String
-- MODEL
type alias Model =
{ description : String
, completed : Bool
, edits : Maybe String
, id : Int
}
init : String -> Int -> Model
init desc id =
{ description = desc
, completed = False
, edits = Nothing
, id = id
}
-- UPDATE
type Action
= Focus
| Edit String
| Cancel
| Commit
| Completed Bool
| Delete
update : Action -> Model -> Maybe Model
update update task =
case update of
Focus ->
Just { task | edits <- Just task.description }
Edit description ->
Just { task | edits <- Just description }
Cancel ->
Just { task | edits <- Nothing }
Commit ->
case task.edits of
Nothing ->
Just task
Just rawDescription ->
let description = String.trim rawDescription in
if String.isEmpty description then Nothing else
Just
{ task |
edits <- Nothing,
description <- description
}
Completed bool ->
Just { task | completed <- bool }
Delete ->
Nothing
-- VIEW
view : LC.LocalChannel (Int, Action) -> Model -> Html
view channel task =
let className =
(if task.completed then "completed " else "") ++
case task.edits of
Just _ -> "editing"
Nothing -> ""
description =
Maybe.withDefault task.description task.edits
in
li
[ class className ]
[ div
[ class "view" ]
[ input
[ class "toggle"
, type' "checkbox"
, checked task.completed
, onClick (LC.send channel (task.id, Completed (not task.completed)))
]
[]
, label
[ onDoubleClick (LC.send channel (task.id, Focus)) ]
[ text description ]
, button
[ class "destroy"
, onClick (LC.send channel (task.id, Delete))
]
[]
]
, input
[ class "edit"
, value description
, name "title"
, id ("todo-" ++ toString task.id)
, on "input" targetValue (\desc -> LC.send channel (task.id, Edit desc))
, onBlur (LC.send channel (task.id, Commit))
, onFinish
(LC.send channel (task.id, Commit))
(LC.send channel (task.id, Cancel))
]
[]
]
onFinish : Signal.Message -> Signal.Message -> Attribute
onFinish enterMessage escapeMessage =
let select key =
case key of
13 -> Ok enterMessage
27 -> Ok escapeMessage
_ -> Err "Not a 'finish' key, such as ENTER or ESCAPE"
in
on "keydown" (Json.customDecoder keyCode select) identity
module Todo where
{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
This application is broken up into four distinct parts:
1. Model - a full description of the application as data
2. Update - a way to update the model based on user actions
3. View - a way to visualize our model with HTML
4. Inputs - the signals necessary to manage events
This clean division of concerns is a core part of Elm. You can read more about
this in the Pong tutorial: http://elm-lang.org/blog/Pong.elm
This program is not particularly large, so definitely see the following
document for notes on structuring more complex GUIs with Elm:
http://elm-lang.org/learn/Architecture.elm
-}
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import Html.Lazy (lazy, lazy2)
import List
import LocalChannel as LC
import Maybe
import Signal
import String
import Task
import Window
-- MODEL
-- The full application state of our todo app.
type alias Model =
{ tasks : List Task.Model
, field : String
, uid : Int
, visibility : String
}
emptyModel : Model
emptyModel =
{ tasks = []
, visibility = "All"
, field = ""
, uid = 0
}
-- UPDATE
-- A description of the kinds of actions that can be performed on the model of
-- our application. See the following post for more info on this pattern and
-- some alternatives: http://elm-lang.org/learn/Architecture.elm
type Action
= NoOp
| UpdateField String
| Add
| UpdateTask (Int, Task.Action)
| DeleteComplete
| CheckAll Bool
| ChangeVisibility String
-- How we update our Model on any given Action
update : Action -> Model -> Model
update action model =
case action of
NoOp -> model
UpdateField str ->
{ model | field <- str }
Add ->
let description = String.trim model.field in
if String.isEmpty description then model else
{ model |
uid <- model.uid + 1,
field <- "",
tasks <- model.tasks ++ [Task.init description model.uid]
}
UpdateTask (id, taskAction) ->
let updateTask t =
if t.id == id then Task.update taskAction t else Just t
in
{ model | tasks <- List.filterMap updateTask model.tasks }
DeleteComplete ->
{ model | tasks <- List.filter (not << .completed) model.tasks }
CheckAll bool ->
let updateTask t = { t | completed <- bool }
in { model | tasks <- List.map updateTask model.tasks }
ChangeVisibility visibility ->
{ model | visibility <- visibility }
-- VIEW
view : Model -> Html
view model =
div
[ class "todomvc-wrapper"
, style [ ("visibility", "hidden") ]
]
[ section
[ id "todoapp" ]
[ lazy taskEntry model.field
, lazy2 taskList model.visibility model.tasks
, lazy2 controls model.visibility model.tasks
]
, infoFooter
]
taskEntry : String -> Html
taskEntry task =
header
[ id "header" ]
[ h1 [] [ text "todos" ]
, input
[ id "new-todo"
, placeholder "What needs to be done?"
, autofocus True
, value task
, name "newTodo"
, on "input" targetValue (Signal.send actions << UpdateField)
, Task.onFinish (Signal.send actions Add) (Signal.send actions NoOp)
]
[]
]
taskList : String -> List Task.Model -> Html
taskList visibility tasks =
let isVisible todo =
case visibility of
"Completed" -> todo.completed
"Active" -> not todo.completed
"All" -> True
allCompleted = List.all .completed tasks
cssVisibility = if List.isEmpty tasks then "hidden" else "visible"
in
section
[ id "main"
, style [ ("visibility", cssVisibility) ]
]
[ input
[ id "toggle-all"
, type' "checkbox"
, name "toggle"
, checked allCompleted
, onClick (Signal.send actions (CheckAll (not allCompleted)))
]
[]
, label
[ for "toggle-all" ]
[ text "Mark all as complete" ]
, ul
[ id "todo-list" ]
(List.map (Task.view taskActions) (List.filter isVisible tasks))
]
controls : String -> List Task.Model -> Html
controls visibility tasks =
let tasksCompleted = List.length (List.filter .completed tasks)
tasksLeft = List.length tasks - tasksCompleted
item_ = if tasksLeft == 1 then " item" else " items"
in
footer
[ id "footer"
, hidden (List.isEmpty tasks)
]
[ span
[ id "todo-count" ]
[ strong [] [ text (toString tasksLeft) ]
, text (item_ ++ " left")
]
, ul
[ id "filters" ]
[ visibilitySwap "#/" "All" visibility
, text " "
, visibilitySwap "#/active" "Active" visibility
, text " "
, visibilitySwap "#/completed" "Completed" visibility
]
, button
[ class "clear-completed"
, id "clear-completed"
, hidden (tasksCompleted == 0)
, onClick (Signal.send actions DeleteComplete)
]
[ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
]
visibilitySwap : String -> String -> String -> Html
visibilitySwap uri visibility actualVisibility =
let className = if visibility == actualVisibility then "selected" else "" in
li
[ onClick (Signal.send actions (ChangeVisibility visibility)) ]
[ a [ class className, href uri ] [ text visibility ] ]
infoFooter : Html
infoFooter =
footer [ id "info" ]
[ p [] [ text "Double-click to edit a todo" ]
, p [] [ text "Written by "
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
]
, p [] [ text "Part of "
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
]
]
-- SIGNALS
-- wire the entire application together
main : Signal Html
main =
Signal.map view model
-- manage the model of our application over time
model : Signal Model
model =
Signal.foldp update initialModel allActions
initialModel : Model
initialModel =
Maybe.withDefault emptyModel savedModel
allActions : Signal Action
allActions =
Signal.merge
(Signal.subscribe actions)
(Signal.map ChangeVisibility route)
-- interactions with localStorage
port savedModel : Maybe Model
port save : Signal Model
port save = model
-- routing
port route : Signal String
-- actions from user input
actions : Signal.Channel Action
actions = Signal.channel NoOp
taskActions : LC.LocalChannel (Int, Task.Action)
taskActions = LC.create UpdateTask actions
port focus : Signal (Maybe Int)
port focus =
let toSelector action =
case action of
UpdateTask (id, Task.Focus) -> Just id
_ -> Nothing
in
Signal.map toSelector (Signal.subscribe actions)
{
"version": "1.0.0",
"summary": "TodoMVC created with Elm and elm-html",
"repository": "https://github.com/evancz/elm-todomvc.git",
"license": "BSD3",
"source-directories": [
"."
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "1.0.0 <= v < 2.0.0",
"evancz/local-channel": "1.0.0 <= v < 2.0.0"
}
}
\ No newline at end of file
This diff is collapsed.
<!doctype html>
<html lang="en" data-framework="elm">
<head>
<meta charset="utf-8">
<title>Elm • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<style>
body {
width: auto;
}
.todomvc-wrapper {
visibility: visible !important;
}
</style>
</head>
<body>
<script src="elm.js"></script>
<script>
(function () {
var result = localStorage.getItem('elm-todo-model');
var savedModel = result ? JSON.parse(result) : null;
var todomvc = Elm.fullscreen(Elm.Todo, {
savedModel: savedModel,
route: getRoute()
});
todomvc.ports.save.subscribe(function (model) {
localStorage.setItem('elm-todo-model', JSON.stringify(model));
});
// Routing
window.addEventListener('popstate', function () {
todomvc.ports.route.send(getRoute());
}, false);
function getRoute() {
var hash = location.href.split('#')[1] || '';
var route = hash.replace('/', '');
if (['all', 'active', 'completed'].indexOf(route) >= 0) {
return route[0].toUpperCase() + route.substr(1);
}
return 'All';
}
// Setting focus manually
todomvc.ports.focus.subscribe(function (id) {
setTimeout(function () {
if (id === null) {
return;
}
var node = document.getElementById('todo-' + id);
if (document.activeElement !== node) {
node.focus();
}
}, 50);
});
}());
</script>
<script async src="node_modules/todomvc-common/base.js"></script>
</body>
</html>
{
"private": true,
"dependencies": {
"todomvc-app-css": "^1.0.0",
"todomvc-common": "^1.0.1"
}
}
# Elm TodoMVC Example
> A functional reactive language for interactive applications
> _[Elm](http://elm-lang.org/)_
## Learning Elm
The [Elm website](http://elm-lang.org/) is a great resource for getting
started.
Here are some links you may find helpful:
* [Try Elm](http://elm-lang.org/try)
* [Learn Elm](http://elm-lang.org/Learn.elm)
* [Elm Snippets](http://www.share-elm.com/)
Get help from other Elm users:
* [elm-discuss mailing list](https://groups.google.com/forum/?fromgroups#!forum/elm-discuss)
* [@elmlang on Twitter](https://twitter.com/elmlang)
* [@czaplic on Twitter](https://twitter.com/czaplic)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Project Structure
All of the Elm code lives in `Todo.elm` and `Task.elm` and relies
on the [elm-html][] library.
[elm-html]: http://library.elm-lang.org/catalog/evancz-elm-html/latest
There also is a port handler set up in `index.html` to set the focus on
particular text fields when necessary.
## Build Instructions
You need to install
[elm](https://github.com/elm-lang/elm-platform/blob/master/README.md#elm-platform)
on your machine first.
Run the following commands from the root of this project:
```bash
elm-package install
elm-make Todo.elm --output build/Todo.js
```
Then open `index.html` in your browser!
## Credit
This TodoMVC application was created by [@evancz](https://github.com/evancz), and imported into TasteJS by [@passy](https://twitter.com/passy).
......@@ -149,6 +149,9 @@
<li class="routing">
<a href="examples/closure/" data-source="http://code.google.com/closure/library/" data-content="The Closure Library is a broad, well-tested, modular, and cross-browser JavaScript library. You can pull just what you need from a large set of reusable UI widgets and controls, and from lower-level utilities for DOM manipulation, server communication, animation, data structures, unit testing, rich-text editing, and more.">Closure</a>
</li>
<li class="routing">
<a href="examples/elm/" data-source="http://elm-lang.org" data-content="A functional reactive language for interactive applications">Elm</a>
</li>
<li>
<a href="examples/angular-dart/web/" data-source="https://github.com/angular/angular.dart" data-content="Dart firstly targets the development of modern and large scale browser-side web apps. It's an object oriented language with a C-style syntax. AngularDart is a port of Angular to Dart.">AngularDart</a>
</li>
......
......@@ -292,6 +292,34 @@
}]
}]
},
"elm": {
"name": "Elm",
"description": "A functional reactive language for interactive applications",
"homepage": "http://elm-lang.org/",
"examples": [{
"name": "Example",
"url": "examples/elm"
}],
"link_groups": [{
"heading": "Official Resources",
"links": [{
"name": "Project Site",
"url": "http://elm-lang.org/"
}, {
"name": "Guides",
"url": "http://elm-lang.org/Learn.elm"
}, {
"name": "Syntax Reference",
"url": "http://elm-lang.org/learn/Syntax.elm"
}, {
"name": "Libraries",
"url": "http://elm-lang.org/Libraries.elm"
}, {
"name": "#elm IRC Channel on Freenode",
"url": "https://botbot.me/freenode/elm/"
}]
}]
},
"batman": {
"name": "Batman.js",
"description": "A client-side framework for Rails developers. Batman.js is a framework for building rich web applications with CoffeeScript.",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment