<script src="../../dist/vue.global.js"></script> <link rel="stylesheet" href="../../../../node_modules/todomvc-app-css/index.css"> <div id="app"> <section class="todoapp"> <header class="header"> <h1>todos</h1> <input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keyup.enter="addTodo"> </header> <section class="main" v-show="todos.length"> <input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo === editedTodo }"> <div class="view"> <input class="toggle" type="checkbox" v-model="todo.completed"> <label @dblclick="editTodo(todo)">{{ todo.title }}</label> <button class="destroy" @click="removeTodo(todo)"></button> </div> <input class="edit" type="text" v-model="todo.title" v-todo-focus="todo === editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.escape="cancelEdit(todo)" > </li> </ul> </section> <footer class="footer" v-show="todos.length"> <span class="todo-count"> <strong>{{ remaining }}</strong> <span>{{ pluralize(remaining) }} left</span> </span> <ul class="filters"> <li><a href="#/all" :class="{ selected: visibility === 'all' }">All</a></li> <li><a href="#/active" :class="{ selected: visibility === 'active' }">Active</a></li> <li><a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a></li> </ul> <button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining"> Clear completed </button> </footer> </section> </div> <script> const STORAGE_KEY = 'todos-vuejs-3.x' const todoStorage = { fetch() { const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') todos.forEach((todo, index) => { todo.id = index }) todoStorage.uid = todos.length return todos }, save(todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) } } const filters = { all(todos) { return todos }, active(todos) { return todos.filter((todo) => { return !todo.completed }) }, completed(todos) { return todos.filter(function (todo) { return todo.completed }) } } Vue.createApp({ // app initial state data: () => ({ todos: todoStorage.fetch(), newTodo: '', editedTodo: null, visibility: 'all' }), // watch todos change for localStorage persistence watch: { todos: { handler(todos) { todoStorage.save(todos) }, deep: true } }, mounted() { window.addEventListener('hashchange', this.onHashChange) this.onHashChange() }, computed: { filteredTodos() { return filters[this.visibility](this.todos) }, remaining() { return filters.active(this.todos).length }, allDone: { get() { return this.remaining === 0 }, set(value) { this.todos.forEach(function (todo) { todo.completed = value }) } } }, // methods that implement data logic. // note there's no DOM manipulation here at all. methods: { addTodo() { var value = this.newTodo && this.newTodo.trim() if (!value) { return } this.todos.push({ id: todoStorage.uid++, title: value, completed: false }) this.newTodo = '' }, removeTodo(todo) { this.todos.splice(this.todos.indexOf(todo), 1) }, editTodo(todo) { this.beforeEditCache = todo.title this.editedTodo = todo }, doneEdit(todo) { if (!this.editedTodo) { return } this.editedTodo = null todo.title = todo.title.trim() if (!todo.title) { this.removeTodo(todo) } }, cancelEdit(todo) { this.editedTodo = null todo.title = this.beforeEditCache }, removeCompleted() { this.todos = filters.active(this.todos) }, onHashChange() { var visibility = window.location.hash.replace(/#\/?/, '') if (filters[visibility]) { this.visibility = visibility } else { window.location.hash = '' this.visibility = 'all' } }, pluralize (n) { return n === 1 ? 'item' : 'items' } }, directives: { 'todo-focus'(el, binding) { if (binding.value) { el.focus() } } } }).mount('#app') </script>