| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- <script src="../../dist/vue.min.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="state.newTodo"
- @keyup.enter="addTodo"
- />
- </header>
- <section class="main" v-show="state.todos.length">
- <input
- id="toggle-all"
- class="toggle-all"
- type="checkbox"
- v-model="state.allDone"
- />
- <label for="toggle-all">Mark all as complete</label>
- <ul class="todo-list">
- <li
- v-for="todo in state.filteredTodos"
- class="todo"
- :key="todo.id"
- :class="{ completed: todo.completed, editing: todo === state.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 === state.editedTodo"
- @blur="doneEdit(todo)"
- @keyup.enter="doneEdit(todo)"
- @keyup.escape="cancelEdit(todo)"
- />
- </li>
- </ul>
- </section>
- <footer class="footer" v-show="state.todos.length">
- <span class="todo-count">
- <strong>{{ state.remaining }}</strong>
- <span>{{ state.remainingText }}</span>
- </span>
- <ul class="filters">
- <li>
- <a href="#/all" :class="{ selected: state.visibility === 'all' }"
- >All</a
- >
- </li>
- <li>
- <a
- href="#/active"
- :class="{ selected: state.visibility === 'active' }"
- >Active</a
- >
- </li>
- <li>
- <a
- href="#/completed"
- :class="{ selected: state.visibility === 'completed' }"
- >Completed</a
- >
- </li>
- </ul>
- <button
- class="clear-completed"
- @click="removeCompleted"
- v-show="state.todos.length > state.remaining"
- >
- Clear completed
- </button>
- </footer>
- </section>
- </div>
- <script>
- const { reactive, computed, watchEffect, onMounted, onUnmounted } = Vue
- const STORAGE_KEY = 'todos-vuejs-3.x-composition'
- 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
- })
- }
- }
- function pluralize(n) {
- return n === 1 ? 'item' : 'items'
- }
- new Vue({
- setup() {
- const state = reactive({
- todos: todoStorage.fetch(),
- editedTodo: null,
- newTodo: '',
- beforeEditCache: '',
- visibility: 'all',
- remaining: computed(() => {
- return filters.active(state.todos).length
- }),
- remainingText: computed(() => {
- return ` ${pluralize(state.remaining)} left`
- }),
- filteredTodos: computed(() => {
- return filters[state.visibility](state.todos)
- }),
- allDone: computed({
- get: function () {
- return state.remaining === 0
- },
- set: function (value) {
- state.todos.forEach(todo => {
- todo.completed = value
- })
- }
- })
- })
- watchEffect(() => {
- todoStorage.save(state.todos)
- })
- onMounted(() => {
- window.addEventListener('hashchange', onHashChange)
- onHashChange()
- })
- onUnmounted(() => {
- window.removeEventListener('hashchange', onHashChange)
- })
- function onHashChange() {
- const visibility = window.location.hash.replace(/#\/?/, '')
- if (filters[visibility]) {
- state.visibility = visibility
- } else {
- window.location.hash = ''
- state.visibility = 'all'
- }
- }
- function addTodo() {
- const value = state.newTodo && state.newTodo.trim()
- if (!value) {
- return
- }
- state.todos.push({
- id: todoStorage.uid++,
- title: value,
- completed: false
- })
- state.newTodo = ''
- }
- function removeTodo(todo) {
- state.todos.splice(state.todos.indexOf(todo), 1)
- }
- function editTodo(todo) {
- state.beforeEditCache = todo.title
- state.editedTodo = todo
- }
- function doneEdit(todo) {
- if (!state.editedTodo) {
- return
- }
- state.editedTodo = null
- todo.title = todo.title.trim()
- if (!todo.title) {
- removeTodo(todo)
- }
- }
- function cancelEdit(todo) {
- state.editedTodo = null
- todo.title = state.beforeEditCache
- }
- function removeCompleted() {
- state.todos = filters.active(state.todos)
- }
- return {
- state,
- addTodo,
- removeTodo,
- editTodo,
- doneEdit,
- cancelEdit,
- removeCompleted
- }
- },
- directives: {
- 'todo-focus': (el, { value }) => {
- if (value) {
- el.focus()
- }
- }
- }
- }).$mount('#app')
- </script>
|