Skip to content

Instantly share code, notes, and snippets.

@staydecent
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save staydecent/bf9ce390338ea73486d3 to your computer and use it in GitHub Desktop.
Save staydecent/bf9ce390338ea73486d3 to your computer and use it in GitHub Desktop.
LiveScript, mercury proof attempting to mimic Elm TodoMVC example.
document = require 'global/document'
nextTick = require 'next-tick'
HashRouter = require 'hash-router'
Event = require 'geval'
cuid = require 'cuid'
extend = require 'xtend'
hg = require 'mercury'
h = hg.h
{map, filter, reject, keys, join, obj-to-pairs} = require 'prelude-ls'
const ESCAPE = 27
### STATE ###
EmptyState = (inputs, savedState={}) ->
defaults =
tasks: []
route: 'all'
field: ''
state = extend defaults, savedState
hg.struct do
tasks: hg.array state.tasks.map NewTask
route: hg.value state.route
field: hg.value state.field
inputs: inputs
NewTask = (task) ->
defaults =
id: null,
title: "",
editing: false,
completed: false
newTask = extend defaults, task
hg.struct do
title: hg.value newTask.title
completed: hg.value newTask.completed
editing: hg.value newTask.editing
id: hg.value cuid!
### UPDATE ###
# Filter helpers
completed = (t) -> t.completed
function doMutableFocus
unless this instanceof doMutableFocus
new doMutableFocus
doMutableFocus.prototype.hook = (node, property) ->
nextTick -> node.focus! if document.activeElement != node
# A description of the kinds of actions that can be performed on
# the state of the application.
Actions =
setRoute: (state, data) ->
console.debug 'setRoute', data
state.route.set route.substr(2) || 'all'
toggleAll: (state, data) ->
console.debug 'toggleAll', data
for t, i in state.tasks!
state.tasks.get i .completed.set value.toggle
addTask: (state, data) ->
unless data.NewTask.trim() == ''
state.tasks.push NewTask {title: data.NewTask.trim!}
state.field.set ''
updateField: (state, data) ->
state.field.set data.NewTask
toggle: (state, data) ->
for t, i in state.tasks! when t.id == data.id
state.tasks.get i .completed.set data.completed
startEdit: (state, id) ->
console.debug 'startEdit', id
for t, i in state.tasks! when t.id == id
task = state.tasks.get i
console.debug 'task:', task!
task.editing.set true
finishEdit: (state, data) ->
console.debug 'finishEdit', data
for t, i in state.tasks! when t.id == data.id and t.editing == true
task = state.tasks.get i
task.editing.set false
task.title.set data.title
@destroy state, id if data.title.trim() == ''
cancelEdit: (state, key, id) ->
console.debug 'cancelEdit', key, id
for t, i in state.tasks! when t.id == id
state.tasks.get i .editing.set false
destroy: (state, id) ->
console.debug 'destroy', id
[idx, ...tail] = [i for let t, i in state.tasks! when t.id == id]
state.tasks.splice idx, 1
clearCompleted: (state) ->
console.debug 'clearCompleted', state
for t in state.tasks when t.completed == true
@destroy state, t.id
### RENDER ###
function render state
sectionContents = [
hg.partial header, state.field, state.inputs
mainSection state.tasks, state.route, state.inputs
# hg.partial statsSection, state.tasks, state.route, state.inputs
]
h '.todomvc-wrapper', {
'style': {'visibility': 'hidden'}
}, [
h 'section#todoapp.todoapp', sectionContents
hg.partial infoFooter
]
function header field, inputs
h \header#header.header, {
'ev-event': [
hg.changeEvent inputs.updateField
hg.submitEvent inputs.addTask
]
}, [
h 'h1', 'Todos'
h \input#new-todo.new-todo, {
placeHolder: 'What needs to be done?'
autofocus: true
value: field
name: 'NewTask'
}
]
function mainSection tasks, route, inputs
allCompleted = tasks.every (t) -> t.completed == true
visibleTasks = [t for t in tasks
when t.completed == true and route == 'completed'
or t.completed == false and route == 'active'
or route == 'all']
taskItems = visibleTasks.map (t) -> taskItem t, inputs
h \section#main.main, {hidden: !tasks.length}, [
h \input#toggle-all.toggle-all, {
type: 'checkbox'
name: 'toggle'
checked: allCompleted
'ev-change': hg.valueEvent inputs.toggleAll
}
h 'label', {htmlFor: 'toggle-all'}, 'Mark all as complete'
h \ul#todo-list.todolist, taskItems
]
function taskItem task, inputs
className = obj-to-pairs task
|> filter (p) -> p[1] == true
|> map (p) -> p[0]
|> join ' '
h 'li', {className: className, key: task.id}, [
h '.view', [
h 'input.toggle', {
type: 'checkbox'
checked: task.completed
'ev-change': hg.event inputs.toggle, {
id: task.id,
completed: !task.completed
}
}
h 'label', {'ev-dblclick': hg.event inputs.startEdit, task.id}, task.title
h 'button.destroy', {'ev-click': hg.event inputs.destroy, task.id}
]
h 'input.edit', {
value: task.title
name: 'title'
'ev-focus': if task.editing then doMutableFocus! else null
'ev-keydown': hg.keyEvent inputs.cancelEdit, ESCAPE, task.id
"ev-event": hg.submitEvent inputs.finishEdit, {id: task.id}
'ev-blur': hg.valueEvent inputs.finishEdit, {id: task.id}
}
]
function statsSection tasks, route, inputs
incompleteTasksLen = reject completed, tasks .length
completedTasksLen = tasks.length - incompleteTasksLen
h \footer#footer.footer, {hidden: !tasks.length}, [
h \span#todo-count.todo-count, [
h 'strong', incompleteTasksLen
if incompleteTasksLen == 1 then ' item' else ' items'
' left'
]
h \ul#filters.filters, [
link \#/, 'All', route == 'all'
link \#/active, 'Active', route == 'active'
link \#/completed, 'Completed', route == 'completed'
]
h \button#clear-completed.clear-completed, do
hidden: completedTasksLen == 0
'ev-click': hg.event inputs.clearCompleted
, 'Clear completed (' + completedTasksLen + ')'
]
function link uri, text, isSelected
h 'li', [
h 'a', do
className: if isSelected then 'selected' else ''
href: uri
, text
]
function infoFooter
h \footer#info.info, [
h 'p', 'Double-click to edit a todo'
]
### INPUTS ###
inputs = hg.input keys Actions
inputs.setRoute = EventRouter!
state = window.state = EmptyState inputs
[inputs[i] Actions[i].bind null, state for i in keys Actions]
function EventRouter
router = HashRouter!
window.addEventListener 'hashchange', router
Event (emit) -> router.on 'hash', emit
hg.app document.body, state, render
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment