todomvc.html 5.8 KB

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