todomvc.html 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <script src="../../dist/vue.global.js"></script>
  2. <link rel="stylesheet" href="../../../../node_modules/todomvc-app-css/index.css">
  3. <div id="app">
  4. <section class="todoapp">
  5. <header class="header">
  6. <h1>todos</h1>
  7. <input class="new-todo"
  8. autofocus autocomplete="off"
  9. placeholder="What needs to be done?"
  10. v-model="state.newTodo"
  11. @keyup.enter="addTodo">
  12. </header>
  13. <section class="main" v-show="state.todos.length">
  14. <input id="toggle-all" class="toggle-all" type="checkbox" v-model="state.allDone">
  15. <label for="toggle-all">Mark all as complete</label>
  16. <ul class="todo-list">
  17. <li v-for="todo in state.filteredTodos"
  18. class="todo"
  19. :key="todo.id"
  20. :class="{ completed: todo.completed, editing: todo === state.editedTodo }">
  21. <div class="view">
  22. <input class="toggle" type="checkbox" v-model="todo.completed">
  23. <label @dblclick="editTodo(todo)">{{ todo.title }}</label>
  24. <button class="destroy" @click="removeTodo(todo)"></button>
  25. </div>
  26. <input class="edit" type="text"
  27. v-model="todo.title"
  28. v-todo-focus="todo === state.editedTodo"
  29. @blur="doneEdit(todo)"
  30. @keyup.enter="doneEdit(todo)"
  31. @keyup.escape="cancelEdit(todo)"
  32. >
  33. </li>
  34. </ul>
  35. </section>
  36. <footer class="footer" v-show="state.todos.length">
  37. <span class="todo-count">
  38. <strong>{{ state.remaining }}</strong>
  39. <span>{{ state.remainingText }}</span>
  40. </span>
  41. <ul class="filters">
  42. <li><a href="#/all" :class="{ selected: state.visibility === 'all' }">All</a></li>
  43. <li><a href="#/active" :class="{ selected: state.visibility === 'active' }">Active</a></li>
  44. <li><a href="#/completed" :class="{ selected: state.visibility === 'completed' }">Completed</a></li>
  45. </ul>
  46. <button class="clear-completed" @click="removeCompleted" v-show="state.todos.length > state.remaining">
  47. Clear completed
  48. </button>
  49. </footer>
  50. </section>
  51. </div>
  52. <script>
  53. const { createApp, reactive, computed, watchEffect, onMounted, onUnmounted } = Vue
  54. const STORAGE_KEY = 'todos-vuejs-3.x'
  55. const todoStorage = {
  56. fetch () {
  57. const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
  58. todos.forEach((todo, index) => {
  59. todo.id = index
  60. })
  61. todoStorage.uid = todos.length
  62. return todos
  63. },
  64. save (todos) {
  65. localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  66. }
  67. }
  68. const filters = {
  69. all (todos) {
  70. return todos
  71. },
  72. active (todos) {
  73. return todos.filter((todo) => {
  74. return !todo.completed
  75. })
  76. },
  77. completed (todos) {
  78. return todos.filter(function (todo) {
  79. return todo.completed
  80. })
  81. }
  82. }
  83. function pluralize (n) {
  84. return n === 1 ? 'item' : 'items'
  85. }
  86. createApp({
  87. setup () {
  88. const state = reactive({
  89. todos: todoStorage.fetch(),
  90. editedTodo: null,
  91. newTodo: '',
  92. beforeEditCache: '',
  93. visibility: 'all',
  94. remaining: computed(() => {
  95. return filters.active(state.todos).length
  96. }),
  97. remainingText: computed(() => {
  98. return ` ${pluralize(state.remaining)} left`
  99. }),
  100. filteredTodos: computed(() => {
  101. return filters[state.visibility](state.todos)
  102. }),
  103. allDone: computed({
  104. get: function () {
  105. return state.remaining === 0
  106. },
  107. set: function (value) {
  108. state.todos.forEach((todo) => {
  109. todo.completed = value
  110. })
  111. }
  112. })
  113. })
  114. watchEffect(() => {
  115. todoStorage.save(state.todos)
  116. })
  117. onMounted(() => {
  118. window.addEventListener('hashchange', onHashChange)
  119. onHashChange()
  120. })
  121. onUnmounted(() => {
  122. window.removeEventListener('hashchange', onHashChange)
  123. })
  124. function onHashChange () {
  125. const visibility = window.location.hash.replace(/#\/?/, '')
  126. if (filters[visibility]) {
  127. state.visibility = visibility
  128. } else {
  129. window.location.hash = ''
  130. state.visibility = 'all'
  131. }
  132. }
  133. function addTodo () {
  134. const value = state.newTodo && state.newTodo.trim()
  135. if (!value) {
  136. return
  137. }
  138. state.todos.push({
  139. id: todoStorage.uid++,
  140. title: value,
  141. completed: false
  142. })
  143. state.newTodo = ''
  144. }
  145. function removeTodo (todo) {
  146. state.todos.splice(state.todos.indexOf(todo), 1)
  147. }
  148. function editTodo (todo) {
  149. state.beforeEditCache = todo.title
  150. state.editedTodo = todo
  151. }
  152. function doneEdit (todo) {
  153. if (!state.editedTodo) {
  154. return
  155. }
  156. state.editedTodo = null
  157. todo.title = todo.title.trim()
  158. if (!todo.title) {
  159. removeTodo(todo)
  160. }
  161. }
  162. function cancelEdit (todo) {
  163. state.editedTodo = null
  164. todo.title = state.beforeEditCache
  165. }
  166. function removeCompleted () {
  167. state.todos = filters.active(state.todos)
  168. }
  169. return {
  170. state,
  171. addTodo,
  172. removeTodo,
  173. editTodo,
  174. doneEdit,
  175. cancelEdit,
  176. removeCompleted
  177. }
  178. },
  179. directives: {
  180. 'todo-focus': (el, { value }) => {
  181. if (value) {
  182. el.focus()
  183. }
  184. }
  185. }
  186. }).mount('#app')
  187. </script>