Files
buuh/example/index.js
T
Yoshua Wuyts eb9b7fd59f v6 beta branch (#489)
* become prototype based

upgrade to new nanobus

remove nanohistory

add nanotiming

remove nanomount

move newTree to next tick

upgrade timing API

add more node warnings to .start/.mount

add morph timing

pass app instance to .use()

self-clearing async-timing

clean up timings

better mount error

fix mount bug

de-prototype helper functions

update to latest nanotiming

upgrade to nanotiming 5

update nanotiming

add toString asserts

more assertions

add event based tracing

expose createlocation

assert return types from render to always be the same

add navigate event

expose route on state

expose events

update uglify

use scroll-to-anchor

update nanobus

document events

update docs

more docs

changelog v6

upgrade nanomorph

fixup! neatify morph assertions

update bel dep

add popstate docs to changelog

* expose events on state

* upgrade deps
2017-06-28 15:53:37 +02:00

387 lines
8.9 KiB
JavaScript

var Microbounce = require('microbounce')
var Microframe = require('microframe')
var mutate = require('xtend/mutable')
var expose = require('choo-expose')
var css = require('sheetify')
var html = require('bel')
var choo = require('../')
css('todomvc-common/base.css')
css('todomvc-app-css/index.css')
// we export this so tests can run
if (module.parent) {
exports.todoStore = todoStore
} else {
var app = choo()
if (process.env.NODE_ENV !== 'production') {
var persist = require('choo-persist')
var logger = require('choo-log')
app.use(persist())
app.use(logger())
}
app.use(expose())
app.use(todoStore)
app.route('/', mainView)
app.route('#active', mainView)
app.route('#completed', mainView)
app.mount('body')
}
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 todoStore (state, emitter) {
// Default values
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')
}
}