Skip to content

Instantly share code, notes, and snippets.

@lsbardel
Last active September 4, 2022 22:11
Show Gist options
  • Save lsbardel/caf173c3eb284c56edadc0e03179fb27 to your computer and use it in GitHub Desktop.
Save lsbardel/caf173c3eb284c56edadc0e03179fb27 to your computer and use it in GitHub Desktop.
Todo app with d3-view
license: bsd-3-clause

A minimal todo web app with d3-view. 80 lines of javascript + 40 lines of html!

Tip: double click to edit a todo entry

It uses the todoapp custom component and d3-attr-*, d3-for, d3-class and d3-on-* directives. In addition, this example illustrates the use of d3-require when using only d3 plugins required by your application.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Todo View Example</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/css/bootstrap.min.css"/>
<script src="https://unpkg.com/[email protected]/build/d3-require.js"></script>
<style>
.view .done {
text-decoration: line-through;
color: #b7b7b7;
}
.info {
padding: .75rem 2rem;
}
.todo-container {
max-width: 800px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-lg-3"></div>
<div class="col-lg-6">
<todoapp></todoapp>
</div>
<div class="col-lg-3"></div>
</div>
</div>
<script src="./script.js"></script>
</body>
window.d3.require('d3-view', 'd3-transition').then(function (d3) {
var todoapp = {
model () {
return {
placeholder: "What needs to be done?",
newTodo: "",
todos: [todo('something to do'), todo('something else to do')],
done: {
reactOn: ['todos'],
get: function () {
return this.todos.length - count(this.todos);
}
},
count: {
reactOn: ['todos'],
get: function () {
return count(this.todos);
}
},
left: {
reactOn: ['todos'],
get: function () {
var c = count(this.todos);
if (c === 1) return '1 item left';
return c + ' items left';
}
},
// Model actions
$addTodo () {
if (this.$event && this.$event.keyCode !== 13) return;
if (this.newTodo) {
var todos = this.todos.slice();
todos.push(todo(this.newTodo));
this.newTodo = '';
this.todos = todos;
}
},
$clear () {
if (this.$event) this.$event.preventDefault();
var todos = this.todos.reduce(function (t, todo) {
if (!todo.done) t.push(todo);
return t;
}, []);
if (todos.length < this.todos.length) this.todos = todos;
},
$toggle (item) {
item.done = !item.done;
// trigger a change in todos property
this.$change('todos');
},
$edit (item) {
if (!item.done) item.edit = true;
},
$doneEdit (item) {
if (this.$event && this.$event.keyCode !== 13) return;
item.edit = false;
}
};
},
render () {
return this.renderFromUrl('./todo.html')
}
};
var vm = d3.view({
components: {
todoapp: todoapp
}
});
vm.mount('body');
function todo (text) {
return {
text: text,
done: false,
edit: false
}
}
function count (todos) {
return todos.reduce(function (c, t) {
return c + !t.done;
}, 0);
}
});
<section class="todoapp">
<header class="header">
<h1 class="display-1 text-center">Todos</h1>
</header>
<section class="main">
<ul class="list-group todo-list">
<li class="list-group-item">
<input class="form-control form-control-lg new-todo"
autofocus="autofocus" autocomplete="off"
d3-attr-placeholder="placeholder"
d3-on-keyup="$addTodo()"
d3-value="newTodo">
</li>
<li class="list-group-item info">
<span class="todo-count" d3-html="left"></span>
<a d3-if="done"
class="clear-completed float-right"
href="#"
d3-on="$clear()">Clear completed</a>
</li>
<li class="list-group-item todo" d3-for="item in todos" data-transition-duration=500>
<div class="input-group view">
<span class="input-group-addon">
<input type="checkbox" class=="toggle" d3-on="$toggle(item)">
</span>
<input type="text"
class="form-control form-control-lg"
d3-class="item.done ? 'done' : null"
d3-attr-value="item.text"
d3-attr-readOnly="!item.edit"
d3-on-dblclick="$edit(item)"
d3-on-keyup="$doneEdit(item)">
</div>
</li>
</ul>
</section>
</section>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment