complete base implementation of todoapp

This commit is contained in:
Géry Debongnie
2019-03-20 13:36:37 +01:00
parent 9baf3ad4a7
commit c32f1d1e38
5 changed files with 104 additions and 33 deletions
+19 -6
View File
@@ -2,6 +2,8 @@ import { TodoItem } from "./TodoItem.js";
const { StoreMixin, Component } = odoo.core; const { StoreMixin, Component } = odoo.core;
const ENTER_KEY = 13;
export class TodoApp extends StoreMixin(Component) { export class TodoApp extends StoreMixin(Component) {
template = "todoapp"; template = "todoapp";
widgets = { TodoItem }; widgets = { TodoItem };
@@ -11,12 +13,23 @@ export class TodoApp extends StoreMixin(Component) {
return this.env.store.state.todos; return this.env.store.state.todos;
} }
get visibleTodos() {
let todos = this.todos;
if (this.state.filter === "active") {
todos = todos.filter(t => !t.completed);
}
if (this.state.filter === "completed") {
todos = todos.filter(t => t.completed);
}
return todos;
}
get allChecked() { get allChecked() {
return this.todos.every(todo => todo.done); return this.todos.every(todo => todo.completed);
} }
get remaining() { get remaining() {
return this.todos.filter(todo => !todo.done).length; return this.todos.filter(todo => !todo.completed).length;
} }
get remainingText() { get remainingText() {
@@ -24,10 +37,10 @@ export class TodoApp extends StoreMixin(Component) {
} }
addTodo(ev) { addTodo(ev) {
if (ev.keyCode === 13) { if (ev.keyCode === ENTER_KEY) {
const text = ev.target.value; const title = ev.target.value;
if (text.trim()) { if (title.trim()) {
this.env.store.dispatch("addTodo", text); this.env.store.dispatch("addTodo", title);
} }
ev.target.value = ""; ev.target.value = "";
} }
+38
View File
@@ -1,6 +1,11 @@
const ENTER_KEY = 13;
const ESC_KEY = 27;
export class TodoItem extends odoo.core.Component { export class TodoItem extends odoo.core.Component {
template = "todoitem"; template = "todoitem";
state = { isEditing: false };
removeTodo() { removeTodo() {
this.env.store.dispatch("removeTodo", this.props.id); this.env.store.dispatch("removeTodo", this.props.id);
} }
@@ -8,4 +13,37 @@ export class TodoItem extends odoo.core.Component {
toggleTodo() { toggleTodo() {
this.env.store.dispatch("toggleTodo", this.props.id); this.env.store.dispatch("toggleTodo", this.props.id);
} }
async editTodo() {
await this.updateState({ isEditing: true });
this.refs.input.value = "";
this.refs.input.focus();
this.refs.input.value = this.props.title;
}
handleKeyup(ev) {
if (ev.keyCode === ENTER_KEY) {
this.updateTitle(ev.target.value);
}
if (ev.keyCode === ESC_KEY) {
ev.target.value = this.props.title;
this.updateState({ isEditing: false });
}
}
handleBlur(ev) {
this.updateTitle(ev.target.value);
}
updateTitle(title) {
const value = title.trim();
if (!value) {
this.removeTodo(this.props.id);
} else {
this.env.store.dispatch("editTodo", {
id: this.props.id,
title: value
});
this.updateState({ isEditing: false });
}
}
} }
+30 -21
View File
@@ -3,27 +3,30 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const actions = { const actions = {
addTodo({ commit }, text) { addTodo({ commit }, title) {
commit("addTodo", text); commit("addTodo", title);
}, },
removeTodo({ commit }, id) { removeTodo({ commit }, id) {
commit("removeTodo", id); commit("removeTodo", id);
}, },
toggleTodo({ state, commit }, id) { toggleTodo({ state, commit }, id) {
const todo = state.todos.find(t => t.id === id); const todo = state.todos.find(t => t.id === id);
commit("editTodo", { id, done: !todo.done }); commit("editTodo", { id, completed: !todo.completed });
}, },
clearCompleted({ state, commit }) { clearCompleted({ state, commit }) {
state.todos state.todos
.filter(todo => todo.done) .filter(todo => todo.completed)
.forEach(todo => { .forEach(todo => {
commit("removeTodo", todo.id); commit("removeTodo", todo.id);
}); });
}, },
toggleAll({ state, commit }, done) { toggleAll({ state, commit }, completed) {
state.todos.forEach(todo => { state.todos.forEach(todo => {
commit("editTodo", { id: todo.id, done }); commit("editTodo", { id: todo.id, completed });
}); });
},
editTodo({ commit }, { id, title }) {
commit("editTodo", { id, title });
} }
}; };
@@ -32,35 +35,41 @@ const actions = {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const mutations = { const mutations = {
addTodo(state, text) { addTodo(state, title) {
const id = state.nextId++; const id = state.nextId++;
const todo = { id, text, done: false }; const todo = { id, title, completed: false };
state.todos.push(todo); state.todos.push(todo);
}, },
removeTodo(state, id) { removeTodo(state, id) {
const index = state.todos.findIndex(t => t.id === id); const index = state.todos.findIndex(t => t.id === id);
state.todos.splice(index, 1); state.todos.splice(index, 1);
}, },
editTodo(state, { id, text, done }) { editTodo(state, { id, title, completed }) {
const todo = state.todos.find(t => t.id === id); const todo = state.todos.find(t => t.id === id);
if (text !== undefined) { if (title !== undefined) {
todo.text = text; todo.title = title;
} }
if (done !== undefined) { if (completed !== undefined) {
todo.done = done; todo.completed = completed;
} }
} }
}; };
class TodoStore extends odoo.core.Store { //------------------------------------------------------------------------------
commit(...args) { // STORE
super.commit(...args); //------------------------------------------------------------------------------
window.localStorage.setItem("todos", JSON.stringify(this.state.todos)); const LOCALSTORAGE_KEY = "todos-odoo";
}
}
export function makeStore() { export function makeStore() {
const todos = JSON.parse(window.localStorage.getItem("todos") || "[]"); const todos = JSON.parse(
window.localStorage.getItem(LOCALSTORAGE_KEY) || "[]"
);
const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1; const nextId = Math.max(0, ...todos.map(t => t.id || 0)) + 1;
const state = { todos, nextId }; const state = { todos, nextId };
return new TodoStore({ state, actions, mutations }); const store = new odoo.core.Store({ state, actions, mutations });
store.on("update", null, () => {
const state = JSON.stringify(store.state.todos);
window.localStorage.setItem(LOCALSTORAGE_KEY, state);
});
return store;
} }
+17 -5
View File
@@ -12,7 +12,7 @@
<input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="toggleAll"/> <input class="toggle-all" id="toggle-all" type="checkbox" t-att-checked="allChecked" t-on-click="toggleAll"/>
<label for="toggle-all"></label> <label for="toggle-all"></label>
<ul class="todo-list"> <ul class="todo-list">
<t t-foreach="todos" t-as="todo"> <t t-foreach="visibleTodos" t-as="todo">
<t t-widget="TodoItem" t-props="todo"/> <t t-widget="TodoItem" t-props="todo"/>
</t> </t>
</ul> </ul>
@@ -25,20 +25,32 @@
</strong> </strong>
<t t-esc="remainingText"/> <t t-esc="remainingText"/>
</span> </span>
<ul class="filters">
<li>
<a href="#/all" t-on-click="updateState({filter:'all'})" t-att-class="state.filter === 'all' ? 'selected' : ''">All</a>
</li>
<li>
<a href="#/active" t-on-click="updateState({filter:'active'})" t-att-class="state.filter === 'active' ? 'selected' : ''">Active</a>
</li>
<li>
<a href="#/completed" t-on-click="updateState({filter:'completed'})" t-att-class="state.filter === 'completed' ? 'selected' : ''">Completed</a>
</li>
</ul>
<button class="clear-completed" t-if="todos.length gt remaining" t-on-click="clearCompleted"> <button class="clear-completed" t-if="todos.length gt remaining" t-on-click="clearCompleted">
Clear completed Clear completed
</button> </button>
</footer> </footer>
</section> </section>
<li t-name="todoitem" class="todo"> <li t-name="todoitem" class="todo" t-att-class="(props.completed ? 'completed ' : ' ') + (state.isEditing ? 'editing' : '')">
<div class="view"> <div class="view">
<input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.done"/> <input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.completed"/>
<label> <label t-on-dblclick="editTodo">
<t t-esc="props.text"/> <t t-esc="props.title"/>
</label> </label>
<button class="destroy" t-on-click="removeTodo"></button> <button class="destroy" t-on-click="removeTodo"></button>
</div> </div>
<input class="edit" t-ref="input" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-on-blur="handleBlur"/>
</li> </li>
</templates> </templates>
-1
View File
@@ -35,7 +35,6 @@ function updateAttrs(oldVnode: VNode, vnode: VNode): void {
for (key in attrs) { for (key in attrs) {
const cur = attrs[key]; const cur = attrs[key];
const old = oldAttrs[key]; const old = oldAttrs[key];
debugger;
if (old !== cur) { if (old !== cur) {
if (cur === true) { if (cur === true) {
elm.setAttribute(key, ""); elm.setAttribute(key, "");