update example (#583)

This commit is contained in:
Yoshua Wuyts
2017-10-18 14:07:57 +02:00
committed by GitHub
parent 0bedc6cd34
commit a3c2e0cae0
5 changed files with 368 additions and 392 deletions
+10 -376
View File
@@ -1,385 +1,19 @@
var Microbounce = require('microbounce')
var Microframe = require('microframe')
var mutate = require('xtend/mutable')
var expose = require('choo-expose')
var css = require('sheetify') var css = require('sheetify')
var html = require('bel')
var choo = require('../') var choo = require('../')
css('todomvc-common/base.css') css('todomvc-common/base.css')
css('todomvc-app-css/index.css') css('todomvc-app-css/index.css')
// we export this so tests can run var app = choo()
if (module.parent) { if (process.env.NODE_ENV !== 'production') {
exports.todoStore = todoStore app.use(require('choo-devtools')())
} else {
var app = choo()
if (process.env.NODE_ENV !== 'production') {
app.use(require('choo-persist')())
app.use(require('choo-log')())
}
app.use(expose())
app.use(todoStore)
app.route('/', mainView)
app.route('#active', mainView)
app.route('#completed', mainView)
app.route('*', mainView)
app.mount('body')
} }
app.use(require('./store'))
function mainView (state, emit) { app.route('/', require('./view'))
emit('log:debug', 'Rendering main view') app.route('#active', require('./view'))
return html` app.route('#completed', require('./view'))
<body> app.route('*', require('./view'))
<section class="todoapp">
${Header(state, emit)}
${TodoList(state, emit)}
${Footer(state, emit)}
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>choo by <a href="https://yoshuawuyts.com/">Yoshua Wuyts</a></p>
<p>Created by <a href="http://shuheikagawa.com">Shuhei Kagawa</a></p>
</footer>
</body>
`
}
function todoStore (state, emitter) { if (module.parent) module.exports = app
// Default values else app.mount('body')
if (!state.todos) {
state.todos = {}
state.todos.active = []
state.todos.done = []
state.todos.all = []
state.todos.idCounter = 0
}
// Always reset when application boots
state.todos.input = ''
// Register emitters after DOM is loaded to speed up DOM loading
emitter.on('DOMContentLoaded', function () {
// CRUD
emitter.on('todos:create', create)
emitter.on('todos:update', update)
emitter.on('todos:delete', del)
// Special
emitter.on('todos:input', oninput)
// Shorthand
emitter.on('todos:edit', edit)
emitter.on('todos:unedit', unedit)
emitter.on('todos:toggle', toggle)
emitter.on('todos:toggleAll', toggleAll)
emitter.on('todos:deleteCompleted', deleteCompleted)
})
function oninput (text) {
state.todos.input = text
}
function create (name) {
var item = {
id: state.todos.idCounter,
editing: false,
done: false,
name: name
}
state.todos.idCounter += 1
state.todos.active.push(item)
state.todos.all.push(item)
emitter.emit('render')
}
function edit (id) {
state.todos.all.forEach(function (todo) {
if (todo.id === id) todo.editing = true
})
emitter.emit('render')
}
function unedit (id) {
state.todos.all.forEach(function (todo) {
if (todo.id === id) todo.editing = false
})
emitter.emit('render')
}
function update (newTodo) {
var todo = state.todos.all.filter(function (todo) {
return todo.id === newTodo.id
})[0]
if (newTodo.done && todo.done === false) {
state.todos.active.splice(state.todos.active.indexOf(todo), 1)
state.todos.done.push(todo)
} else if (newTodo.done === false && todo.done) {
state.todos.done.splice(state.todos.done.indexOf(todo), 1)
state.todos.active.push(todo)
}
mutate(todo, newTodo)
emitter.emit('render')
}
function del (id) {
var i = null
var todo = null
state.todos.all.forEach(function (_todo, j) {
if (_todo.id === id) {
i = j
todo = _todo
}
})
state.todos.all.splice(i, 1)
if (todo.done) {
var done = state.todos.done
var doneIndex
done.forEach(function (_todo, j) {
if (_todo.id === id) {
doneIndex = j
}
})
done.splice(doneIndex, 1)
} else {
var active = state.todos.active
var activeIndex
active.forEach(function (_todo, j) {
if (_todo.id === id) {
activeIndex = j
}
})
active.splice(activeIndex, 1)
}
emitter.emit('render')
}
function deleteCompleted (data) {
var done = state.todos.done
done.forEach(function (todo) {
var index = state.todos.all.indexOf(todo)
state.todos.all.splice(index, 1)
})
state.todos.done = []
emitter.emit('render')
}
function toggle (id) {
var todo = state.todos.all.filter(function (todo) {
return todo.id === id
})[0]
var done = todo.done
todo.done = !done
var arr = done ? state.todos.done : state.todos.active
var target = done ? state.todos.active : state.todos.done
var index = arr.indexOf(todo)
arr.splice(index, 1)
target.push(todo)
emitter.emit('render')
}
function toggleAll (data) {
var todos = state.todos.all
var allDone = state.todos.all.length &&
state.todos.done.length === state.todos.all.length
todos.forEach(function (todo) {
todo.done = !allDone
})
if (allDone) {
state.todos.done = []
state.todos.active = state.todos.all
} else {
state.todos.done = state.todos.all
state.todos.active = []
}
emitter.emit('render')
}
}
function Footer (state, emit) {
var filter = window.location.hash.replace(/^#/, '')
var activeCount = state.todos.active.length
var hasDone = state.todos.done.length
return html`
<footer class="footer">
<span class="todo-count">
<strong>${activeCount}</strong>
item${state.todos.all === 1 ? '' : 's'} left
</span>
<ul class="filters">
${filterButton('All', '', filter, emit)}
${filterButton('Active', 'active', filter, emit)}
${filterButton('Completed', 'completed', filter, emit)}
</ul>
${hasDone ? deleteCompleted(emit) : ''}
</footer>
`
function filterButton (name, filter, currentFilter, emit) {
var filterClass = filter === currentFilter
? 'selected'
: ''
var uri = '#' + name.toLowerCase()
if (uri === '#all') uri = '/'
return html`
<li>
<a href=${uri} class=${filterClass}>
${name}
</a>
</li>
`
}
function deleteCompleted (emit) {
return html`
<button class="clear-completed" onclick=${deleteAllCompleted}>
Clear completed
</button>
`
function deleteAllCompleted () {
emit('todos:deleteCompleted')
}
}
}
var debounce = Microbounce(256)
var nextFrame = Microframe()
function Header (state, emit) {
return html`
<header class="header">
<h1>todos</h1>
<input class="new-todo"
value=${state.todos.input}
autofocus
placeholder="What needs to be done?"
onkeydown=${createTodo} />
</header>
`
function createTodo (e) {
var value = e.target.value
if (!value) return
if (e.keyCode === 13) {
nextFrame(function () {
emit('todos:input', '')
emit('todos:create', value)
})
} else {
debounce(function () {
nextFrame(function () {
emit('todos:input', value)
})
})
}
}
}
function TodoItem (todo, emit) {
var clx = classList({ completed: todo.done, editing: todo.editing })
return html`
<li id=${todo.id} class=${clx}>
<div class="view">
<input
type="checkbox"
class="toggle"
checked="${todo.done}"
onchange=${toggle} />
<label ondblclick=${edit}>${todo.name}</label>
<button
class="destroy"
onclick=${destroy}
></button>
</div>
<input
class="edit"
value=${todo.name}
onkeydown=${handleEditKeydown}
onblur=${update} />
</li>
`
function toggle (e) {
emit('todos:toggle', todo.id)
}
function edit (e) {
emit('todos:edit', todo.id)
}
function destroy (e) {
emit('todos:delete', todo.id)
}
function update (e) {
emit('todos:update', {
id: todo.id,
editing: false,
name: e.target.value
})
}
function handleEditKeydown (e) {
if (e.keyCode === 13) update(e) // Enter
else if (e.code === 27) emit('todos:unedit') // Escape
}
function classList (classes) {
var str = ''
var keys = Object.keys(classes)
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i]
var val = classes[key]
if (val) str += (key + ' ')
}
return str
}
}
function TodoList (state, emit) {
var filter = window.location.hash.replace(/^#/, '')
var items = filter === 'completed'
? state.todos.done
: filter === 'active'
? state.todos.active
: state.todos.all
var allDone = state.todos.done.length === state.todos.all.length
var nodes = items.map(function (todo) {
return TodoItem(todo, emit)
})
return html`
<section class="main">
<input
class="toggle-all"
type="checkbox"
checked=${allDone}
onchange=${toggleAll}/>
<label for="toggle-all" style="display: none;">
Mark all as done
</label>
<ul class="todo-list">
${nodes}
</ul>
</section>
`
function toggleAll () {
emit('todos:toggleAll')
}
}
+7 -14
View File
@@ -3,26 +3,19 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"deps": "dependency-check . && dependency-check . --extra --no-dev", "start": "bankai start",
"start": "bankai start --open", "build": "bankai build",
"build": "bankai build --optimize", "inspect": "bankai inspect",
"test": "standard && npm run deps && node test.js" "test": "standard && node test.js"
}, },
"dependencies": { "dependencies": {
"bel": "^4.5.1", "choo-devtools": "1.7.0",
"choo-expose": "^1.0.0",
"choo-log": "^7.0.0-0",
"choo-persist": "^3.0.0",
"microbounce": "^1.0.0",
"microframe": "^1.0.0",
"sheetify": "^6.0.1", "sheetify": "^6.0.1",
"todomvc-app-css": "^2.0.6", "todomvc-app-css": "^2.0.6",
"todomvc-common": "^1.0.3", "todomvc-common": "^1.0.3"
"xtend": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"bankai": "^5.4.0", "bankai": "^9.0.0-rc6",
"dependency-check": "^2.8.0",
"standard": "^9.0.1" "standard": "^9.0.1"
} }
} }
+160
View File
@@ -0,0 +1,160 @@
module.exports = todoStore
function todoStore (state, emitter) {
if (!state.todos) {
state.todos = {}
state.todos.active = []
state.todos.done = []
state.todos.all = []
state.todos.idCounter = 0
}
// Always reset when application boots
state.todos.input = ''
// Register emitters after DOM is loaded to speed up DOM loading
emitter.on('DOMContentLoaded', function () {
// CRUD
emitter.on('todos:create', create)
emitter.on('todos:update', update)
emitter.on('todos:delete', del)
// Special
emitter.on('todos:input', oninput)
// Shorthand
emitter.on('todos:edit', edit)
emitter.on('todos:unedit', unedit)
emitter.on('todos:toggle', toggle)
emitter.on('todos:toggleAll', toggleAll)
emitter.on('todos:deleteCompleted', deleteCompleted)
})
function oninput (text) {
state.todos.input = text
}
function create (name) {
var item = {
id: state.todos.idCounter,
editing: false,
done: false,
name: name
}
state.todos.idCounter += 1
state.todos.active.push(item)
state.todos.all.push(item)
emitter.emit('render')
}
function edit (id) {
state.todos.all.forEach(function (todo) {
if (todo.id === id) todo.editing = true
})
emitter.emit('render')
}
function unedit (id) {
state.todos.all.forEach(function (todo) {
if (todo.id === id) todo.editing = false
})
emitter.emit('render')
}
function update (newTodo) {
var todo = state.todos.all.filter(function (todo) {
return todo.id === newTodo.id
})[0]
if (newTodo.done && todo.done === false) {
state.todos.active.splice(state.todos.active.indexOf(todo), 1)
state.todos.done.push(todo)
} else if (newTodo.done === false && todo.done) {
state.todos.done.splice(state.todos.done.indexOf(todo), 1)
state.todos.active.push(todo)
}
Object.assign(todo, newTodo)
emitter.emit('render')
}
function del (id) {
var i = null
var todo = null
state.todos.all.forEach(function (_todo, j) {
if (_todo.id === id) {
i = j
todo = _todo
}
})
state.todos.all.splice(i, 1)
if (todo.done) {
var done = state.todos.done
var doneIndex
done.forEach(function (_todo, j) {
if (_todo.id === id) {
doneIndex = j
}
})
done.splice(doneIndex, 1)
} else {
var active = state.todos.active
var activeIndex
active.forEach(function (_todo, j) {
if (_todo.id === id) {
activeIndex = j
}
})
active.splice(activeIndex, 1)
}
emitter.emit('render')
}
function deleteCompleted (data) {
var done = state.todos.done
done.forEach(function (todo) {
var index = state.todos.all.indexOf(todo)
state.todos.all.splice(index, 1)
})
state.todos.done = []
emitter.emit('render')
}
function toggle (id) {
var todo = state.todos.all.filter(function (todo) {
return todo.id === id
})[0]
var done = todo.done
todo.done = !done
var arr = done ? state.todos.done : state.todos.active
var target = done ? state.todos.active : state.todos.done
var index = arr.indexOf(todo)
arr.splice(index, 1)
target.push(todo)
emitter.emit('render')
}
function toggleAll (data) {
var todos = state.todos.all
var allDone = state.todos.all.length &&
state.todos.done.length === state.todos.all.length
todos.forEach(function (todo) {
todo.done = !allDone
})
if (allDone) {
state.todos.done = []
state.todos.active = state.todos.all
} else {
state.todos.done = state.todos.all
state.todos.active = []
}
emitter.emit('render')
}
}
+1 -2
View File
@@ -2,8 +2,7 @@ var EventEmitter = require('events').EventEmitter
var spok = require('spok') var spok = require('spok')
var tape = require('tape') var tape = require('tape')
var example = require('./') var todoStore = require('./store')
var todoStore = example.todoStore
tape('should initialize empty state', function (t) { tape('should initialize empty state', function (t) {
var emitter = new EventEmitter() var emitter = new EventEmitter()
+190
View File
@@ -0,0 +1,190 @@
var html = require('bel') // cannot require choo/html because it's a nested repo
module.exports = mainView
function mainView (state, emit) {
emit('log:debug', 'Rendering main view')
return html`
<body>
<section class="todoapp">
${Header(state, emit)}
${TodoList(state, emit)}
${Footer(state, emit)}
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>choo by <a href="https://yoshuawuyts.com/">Yoshua Wuyts</a></p>
<p>Created by <a href="http://shuheikagawa.com">Shuhei Kagawa</a></p>
</footer>
</body>
`
}
function Header (state, emit) {
return html`
<header class="header">
<h1>todos</h1>
<input class="new-todo"
value=${state.todos.input}
autofocus
placeholder="What needs to be done?"
onkeydown=${createTodo} />
</header>
`
function createTodo (e) {
var value = e.target.value
if (!value) return
if (e.keyCode === 13) {
emit('todos:input', '')
emit('todos:create', value)
} else {
emit('todos:input', value)
}
}
}
function Footer (state, emit) {
var filter = state.href.replace(/^\//, '') || ''
var activeCount = state.todos.active.length
var hasDone = state.todos.done.length
return html`
<footer class="footer">
<span class="todo-count">
<strong>${activeCount}</strong>
item${state.todos.all === 1 ? '' : 's'} left
</span>
<ul class="filters">
${filterButton('All', '', filter, emit)}
${filterButton('Active', 'active', filter, emit)}
${filterButton('Completed', 'completed', filter, emit)}
</ul>
${hasDone ? deleteCompleted(emit) : ''}
</footer>
`
function filterButton (name, filter, currentFilter, emit) {
var filterClass = filter === currentFilter
? 'selected'
: ''
var uri = '#' + name.toLowerCase()
if (uri === '#all') uri = '/'
return html`
<li>
<a href=${uri} class=${filterClass}>
${name}
</a>
</li>
`
}
function deleteCompleted (emit) {
return html`
<button class="clear-completed" onclick=${deleteAllCompleted}>
Clear completed
</button>
`
function deleteAllCompleted () {
emit('todos:deleteCompleted')
}
}
}
function TodoItem (todo, emit) {
var clx = classList({ completed: todo.done, editing: todo.editing })
return html`
<li id=${todo.id} class=${clx}>
<div class="view">
<input
type="checkbox"
class="toggle"
checked="${todo.done}"
onchange=${toggle} />
<label ondblclick=${edit}>${todo.name}</label>
<button
class="destroy"
onclick=${destroy}
></button>
</div>
<input
class="edit"
value=${todo.name}
onkeydown=${handleEditKeydown}
onblur=${update} />
</li>
`
function toggle (e) {
emit('todos:toggle', todo.id)
}
function edit (e) {
emit('todos:edit', todo.id)
}
function destroy (e) {
emit('todos:delete', todo.id)
}
function update (e) {
emit('todos:update', {
id: todo.id,
editing: false,
name: e.target.value
})
}
function handleEditKeydown (e) {
if (e.keyCode === 13) update(e) // Enter
else if (e.code === 27) emit('todos:unedit') // Escape
}
function classList (classes) {
var str = ''
var keys = Object.keys(classes)
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i]
var val = classes[key]
if (val) str += (key + ' ')
}
return str
}
}
function TodoList (state, emit) {
var filter = state.href.replace(/^\//, '') || ''
var items = filter === 'completed'
? state.todos.done
: filter === 'active'
? state.todos.active
: state.todos.all
var allDone = state.todos.done.length === state.todos.all.length
var nodes = items.map(function (todo) {
return TodoItem(todo, emit)
})
return html`
<section class="main">
<input
class="toggle-all"
type="checkbox"
checked=${allDone}
onchange=${toggleAll}/>
<label for="toggle-all" style="display: none;">
Mark all as done
</label>
<ul class="todo-list">
${nodes}
</ul>
</section>
`
function toggleAll () {
emit('todos:toggleAll')
}
}