Commit 7cdcf5a8 authored by Pascal Hartig's avatar Pascal Hartig

[WIP] Elm 0.15 Update

parent f183e30a
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
......@@ -3,9 +3,9 @@ module Todo where
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
1. Model - a full definition of the application's state
2. Update - a way to step the application state forward
3. View - a way to visualize our application state 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
......@@ -16,30 +16,43 @@ 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 Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Html.Lazy exposing (lazy, lazy2, lazy3)
import Json.Decode as Json
import Signal exposing (Signal, Address)
import String
import Task
import Window
-- MODEL
---- MODEL ----
-- The full application state of our todo app.
type alias Model =
{ tasks : List Task.Model
, field : String
, uid : Int
{ tasks : List Task
, field : String
, uid : Int
, visibility : String
}
type alias Task =
{ description : String
, completed : Bool
, editing : Bool
, id : Int
}
newTask : String -> Int -> Task
newTask desc id =
{ description = desc
, completed = False
, editing = False
, id = id
}
emptyModel : Model
emptyModel =
{ tasks = []
......@@ -49,7 +62,7 @@ emptyModel =
}
-- UPDATE
---- 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
......@@ -57,84 +70,115 @@ emptyModel =
type Action
= NoOp
| UpdateField String
| EditingTask Int Bool
| UpdateTask Int String
| Add
| UpdateTask (Int, Task.Action)
| Delete Int
| DeleteComplete
| Check Int Bool
| CheckAll Bool
| ChangeVisibility String
-- How we update our Model on any given Action
-- How we update our Model on a given Action?
update : Action -> Model -> Model
update action model =
case action of
NoOp -> model
Add ->
{ model |
uid <- model.uid + 1,
field <- "",
tasks <-
if String.isEmpty model.field
then model.tasks
else model.tasks ++ [newTask model.field model.uid]
}
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
EditingTask id isEditing ->
let updateTask t = if t.id == id then { t | editing <- isEditing } else t
in
{ model | tasks <- List.filterMap updateTask model.tasks }
{ model | tasks <- List.map updateTask model.tasks }
UpdateTask id task ->
let updateTask t = if t.id == id then { t | description <- task } else t
in
{ model | tasks <- List.map updateTask model.tasks }
Delete id ->
{ model | tasks <- List.filter (\t -> t.id /= id) 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 }
Check id isCompleted ->
let updateTask t = if t.id == id then { t | completed <- isCompleted } else t
in
{ model | tasks <- List.map updateTask model.tasks }
CheckAll isCompleted ->
let updateTask t = { t | completed <- isCompleted }
in
{ model | tasks <- List.map updateTask model.tasks }
ChangeVisibility visibility ->
{ model | visibility <- visibility }
-- VIEW
---- VIEW ----
view : Model -> Html
view model =
view : Address Action -> Model -> Html
view address 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
[ class "todoapp" ]
[ lazy2 taskEntry address model.field
, lazy3 taskList address model.visibility model.tasks
, lazy3 controls address model.visibility model.tasks
]
, infoFooter
]
taskEntry : String -> Html
taskEntry task =
onEnter : Address a -> a -> Attribute
onEnter address value =
on "keydown"
(Json.customDecoder keyCode is13)
(\_ -> Signal.message address value)
is13 : Int -> Result String ()
is13 code =
if code == 13 then Ok () else Err "not the right key code"
taskEntry : Address Action -> String -> Html
taskEntry address task =
header
[ id "header" ]
[ class "header" ]
[ h1 [] [ text "todos" ]
, input
[ id "new-todo"
[ class "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)
, on "input" targetValue (Signal.message address << UpdateField)
, onEnter address Add
]
[]
]
taskList : String -> List Task.Model -> Html
taskList visibility tasks =
taskList : Address Action -> String -> List Task -> Html
taskList address visibility tasks =
let isVisible todo =
case visibility of
"Completed" -> todo.completed
......@@ -146,124 +190,157 @@ taskList visibility tasks =
cssVisibility = if List.isEmpty tasks then "hidden" else "visible"
in
section
[ id "main"
[ class "main"
, style [ ("visibility", cssVisibility) ]
]
[ input
[ id "toggle-all"
[ class "toggle-all"
, type' "checkbox"
, name "toggle"
, checked allCompleted
, onClick (Signal.send actions (CheckAll (not allCompleted)))
, onClick address (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))
[ class "todo-list" ]
(List.map (todoItem address) (List.filter isVisible tasks))
]
controls : String -> List Task.Model -> Html
controls visibility tasks =
todoItem : Address Action -> Task -> Html
todoItem address todo =
li
[ classList [ ("completed", todo.completed), ("editing", todo.editing) ] ]
[ div
[ class "view" ]
[ input
[ class "toggle"
, type' "checkbox"
, checked todo.completed
, onClick address (Check todo.id (not todo.completed))
]
[]
, label
[ onDoubleClick address (EditingTask todo.id True) ]
[ text todo.description ]
, button
[ class "destroy"
, onClick address (Delete todo.id)
]
[]
]
, input
[ class "edit"
, value todo.description
, name "title"
, class ("todo-" ++ toString todo.id)
, on "input" targetValue (Signal.message address << UpdateTask todo.id)
, onBlur address (EditingTask todo.id False)
, onEnter address (EditingTask todo.id False)
]
[]
]
controls : Address Action -> String -> List Task -> Html
controls address 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"
[ class "footer"
, hidden (List.isEmpty tasks)
]
[ span
[ id "todo-count" ]
[ class "todo-count" ]
[ strong [] [ text (toString tasksLeft) ]
, text (item_ ++ " left")
]
, ul
[ id "filters" ]
[ visibilitySwap "#/" "All" visibility
[ class "filters" ]
[ visibilitySwap address "#/" "All" visibility
, text " "
, visibilitySwap "#/active" "Active" visibility
, visibilitySwap address "#/active" "Active" visibility
, text " "
, visibilitySwap "#/completed" "Completed" visibility
, visibilitySwap address "#/completed" "Completed" visibility
]
, button
[ class "clear-completed"
, id "clear-completed"
, hidden (tasksCompleted == 0)
, onClick (Signal.send actions DeleteComplete)
, onClick address DeleteComplete
]
[ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
]
visibilitySwap : String -> String -> String -> Html
visibilitySwap uri visibility actualVisibility =
let className = if visibility == actualVisibility then "selected" else "" in
visibilitySwap : Address Action -> String -> String -> String -> Html
visibilitySwap address uri visibility actualVisibility =
li
[ onClick (Signal.send actions (ChangeVisibility visibility)) ]
[ a [ class className, href uri ] [ text visibility ] ]
[ onClick address (ChangeVisibility visibility) ]
[ a [ href uri, classList [("selected", visibility == actualVisibility)] ] [ text visibility ] ]
infoFooter : Html
infoFooter =
footer [ id "info" ]
footer [ class "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" ]
]
, 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
---- INPUTS ----
-- wire the entire application together
main : Signal Html
main =
Signal.map view model
Signal.map (view actions.address) model
-- manage the model of our application over time
model : Signal Model
model =
Signal.foldp update initialModel allActions
Signal.foldp update initialModel actions.signal
initialModel : Model
initialModel =
Maybe.withDefault emptyModel savedModel
Maybe.withDefault emptyModel getStorage
allActions : Signal Action
allActions =
Signal.merge
(Signal.subscribe actions)
(Signal.map ChangeVisibility route)
-- actions from user input
actions : Signal.Mailbox Action
actions =
Signal.mailbox NoOp
-- interactions with localStorage
port savedModel : Maybe Model
port save : Signal Model
port save = model
port focus : Signal String
port focus =
let needsFocus act =
case act of
EditingTask id bool -> bool
_ -> False
-- routing
port route : Signal String
toSelector (EditingTask id _) = ("#todo-" ++ toString id)
in
actions.signal
|> Signal.filter needsFocus (EditingTask 0 True)
|> Signal.map toSelector
-- actions from user input
actions : Signal.Channel Action
taskActions : LC.LocalChannel (Int, Task.Action)
-- interactions with localStorage to save the model
port getStorage : Maybe Model
port focus : Signal (Maybe Int)
port focus =
let toSelector action =
case action of
UpdateTask (id, Task.Focus) -> Just id
_ -> Nothing
in
port setStorage : Signal Model
port setStorage = model
......@@ -8,8 +8,8 @@
],
"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
"elm-lang/core": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "3.0.0 <= v < 4.0.0"
},
"elm-version": "0.15.0 <= v < 0.16.0"
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -19,44 +19,22 @@
<script src="elm.js"></script>
<script>
(function () {
var result = localStorage.getItem('elm-todo-model');
var savedModel = result ? JSON.parse(result) : null;
var storedState = localStorage.getItem('elm-todo-state');
var startingState = storedState ? JSON.parse(storedState) : 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) {
var todomvc = Elm.fullscreen(Elm.Todo, { getStorage: startingState });
todomvc.ports.focus.subscribe(function (selector) {
setTimeout(function () {
if (id === null) {
return;
}
var node = document.getElementById('todo-' + id);
if (document.activeElement !== node) {
node.focus();
var nodes = document.querySelectorAll(selector);
if (nodes.length === 1 && document.activeElement !== nodes[0]) {
nodes[0].focus()
}
}, 50);
});
todomvc.ports.setStorage.subscribe(function (state) {
localStorage.setItem('elm-todo-state', JSON.stringify(state));
});
}());
</script>
<script async src="node_modules/todomvc-common/base.js"></script>
......
......@@ -15,11 +15,9 @@ button {
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased;
}
......@@ -33,7 +31,6 @@ body {
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
......@@ -47,7 +44,7 @@ input[type="checkbox"] {
display: none;
}
#todoapp {
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
......@@ -55,25 +52,25 @@ input[type="checkbox"] {
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
#todoapp input::-webkit-input-placeholder {
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp input::-moz-placeholder {
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp input::input-placeholder {
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp h1 {
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
......@@ -83,11 +80,10 @@ input[type="checkbox"] {
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#new-todo,
.new-todo,
.edit {
position: relative;
margin: 0;
......@@ -102,22 +98,20 @@ input[type="checkbox"] {
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-ms-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
#main {
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
......@@ -127,7 +121,7 @@ label[for='toggle-all'] {
display: none;
}
#toggle-all {
.toggle-all {
position: absolute;
top: -55px;
left: -12px;
......@@ -137,50 +131,50 @@ label[for='toggle-all'] {
border: none; /* Mobile Safari */
}
#toggle-all:before {
.toggle-all:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
#toggle-all:checked:before {
.toggle-all:checked:before {
color: #737373;
}
#todo-list {
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
#todo-list li:last-child {
.todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
.todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
......@@ -191,19 +185,18 @@ label[for='toggle-all'] {
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
#todo-list li .toggle:checked:after {
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
#todo-list li label {
.todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
......@@ -213,12 +206,12 @@ label[for='toggle-all'] {
transition: color 0.4s;
}
#todo-list li.completed label {
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
#todo-list li .destroy {
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
......@@ -233,27 +226,27 @@ label[for='toggle-all'] {
transition: color 0.2s ease-out;
}
#todo-list li .destroy:hover {
.todo-list li .destroy:hover {
color: #af5b5e;
}
#todo-list li .destroy:after {
.todo-list li .destroy:after {
content: '×';
}
#todo-list li:hover .destroy {
.todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
.todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
......@@ -261,7 +254,7 @@ label[for='toggle-all'] {
border-top: 1px solid #e6e6e6;
}
#footer:before {
.footer:before {
content: '';
position: absolute;
right: 0;
......@@ -276,16 +269,16 @@ label[for='toggle-all'] {
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
.todo-count {
float: left;
text-align: left;
}
#todo-count strong {
.todo-count strong {
font-weight: 300;
}
#filters {
.filters {
margin: 0;
padding: 0;
list-style: none;
......@@ -294,11 +287,11 @@ label[for='toggle-all'] {
left: 0;
}
#filters li {
.filters li {
display: inline;
}
#filters li a {
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
......@@ -307,39 +300,30 @@ label[for='toggle-all'] {
border-radius: 3px;
}
#filters li a.selected,
#filters li a:hover {
.filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
#filters li a.selected {
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
#clear-completed,
html #clear-completed:active {
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
visibility: hidden;
position: relative;
}
#clear-completed::after {
visibility: visible;
content: 'Clear completed';
position: absolute;
right: 0;
white-space: nowrap;
}
#clear-completed:hover::after {
.clear-completed:hover {
text-decoration: underline;
}
#info {
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
......@@ -347,17 +331,17 @@ html #clear-completed:active {
text-align: center;
}
#info p {
.info p {
line-height: 1;
}
#info a {
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
#info a:hover {
.info a:hover {
text-decoration: underline;
}
......@@ -366,16 +350,16 @@ html #clear-completed:active {
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
.toggle-all,
.todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
.todo-list li .toggle {
height: 40px;
}
#toggle-all {
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
......@@ -384,11 +368,11 @@ html #clear-completed:active {
}
@media (max-width: 430px) {
#footer {
.footer {
height: 50px;
}
#filters {
.filters {
bottom: 10px;
}
}
......@@ -114,7 +114,12 @@
})({});
if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
}
/* jshint ignore:end */
......@@ -228,7 +233,7 @@
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
......
{
"private": true,
"dependencies": {
"todomvc-app-css": "^1.0.0",
"todomvc-app-css": "^2.0.1",
"todomvc-common": "^1.0.1"
}
}
......@@ -46,7 +46,7 @@ Run the following commands from the root of this project:
```bash
elm-package install
elm-make Todo.elm --output build/Todo.js
elm-make Todo.elm
```
Then open `index.html` in your browser!
......
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