todomvc.html 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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="newTodo"
  11. @keyup.enter="addTodo">
  12. </header>
  13. <section class="main" v-show="todos.length">
  14. <input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone">
  15. <label for="toggle-all">Mark all as complete</label>
  16. <ul class="todo-list">
  17. <li v-for="todo in filteredTodos"
  18. class="todo"
  19. :key="todo.id"
  20. :class="{ completed: todo.completed, editing: todo === 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 === 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="todos.length">
  37. <span class="todo-count">
  38. <strong>{{ remaining }}</strong> <span>{{ pluralize(remaining) }} left</span>
  39. </span>
  40. <ul class="filters">
  41. <li><a href="#/all" :class="{ selected: visibility === 'all' }">All</a></li>
  42. <li><a href="#/active" :class="{ selected: visibility === 'active' }">Active</a></li>
  43. <li><a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a></li>
  44. </ul>
  45. <button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
  46. Clear completed
  47. </button>
  48. </footer>
  49. </section>
  50. </div>
  51. <script>
  52. const STORAGE_KEY = 'todos-vuejs-3.x'
  53. const todoStorage = {
  54. fetch() {
  55. const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
  56. todos.forEach((todo, index) => {
  57. todo.id = index
  58. })
  59. todoStorage.uid = todos.length
  60. return todos
  61. },
  62. save(todos) {
  63. localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  64. }
  65. }
  66. const filters = {
  67. all(todos) {
  68. return todos
  69. },
  70. active(todos) {
  71. return todos.filter((todo) => {
  72. return !todo.completed
  73. })
  74. },
  75. completed(todos) {
  76. return todos.filter(function (todo) {
  77. return todo.completed
  78. })
  79. }
  80. }
  81. Vue.createApp({
  82. // app initial state
  83. data: () => ({
  84. todos: todoStorage.fetch(),
  85. newTodo: '',
  86. editedTodo: null,
  87. visibility: 'all'
  88. }),
  89. // watch todos change for localStorage persistence
  90. watch: {
  91. todos: {
  92. handler(todos) {
  93. todoStorage.save(todos)
  94. },
  95. deep: true
  96. }
  97. },
  98. mounted() {
  99. window.addEventListener('hashchange', this.onHashChange)
  100. this.onHashChange()
  101. },
  102. computed: {
  103. filteredTodos() {
  104. return filters[this.visibility](this.todos)
  105. },
  106. remaining() {
  107. return filters.active(this.todos).length
  108. },
  109. allDone: {
  110. get() {
  111. return this.remaining === 0
  112. },
  113. set(value) {
  114. this.todos.forEach(function (todo) {
  115. todo.completed = value
  116. })
  117. }
  118. }
  119. },
  120. // methods that implement data logic.
  121. // note there's no DOM manipulation here at all.
  122. methods: {
  123. addTodo() {
  124. var value = this.newTodo && this.newTodo.trim()
  125. if (!value) {
  126. return
  127. }
  128. this.todos.push({
  129. id: todoStorage.uid++,
  130. title: value,
  131. completed: false
  132. })
  133. this.newTodo = ''
  134. },
  135. removeTodo(todo) {
  136. this.todos.splice(this.todos.indexOf(todo), 1)
  137. },
  138. editTodo(todo) {
  139. this.beforeEditCache = todo.title
  140. this.editedTodo = todo
  141. },
  142. doneEdit(todo) {
  143. if (!this.editedTodo) {
  144. return
  145. }
  146. this.editedTodo = null
  147. todo.title = todo.title.trim()
  148. if (!todo.title) {
  149. this.removeTodo(todo)
  150. }
  151. },
  152. cancelEdit(todo) {
  153. this.editedTodo = null
  154. todo.title = this.beforeEditCache
  155. },
  156. removeCompleted() {
  157. this.todos = filters.active(this.todos)
  158. },
  159. onHashChange() {
  160. var visibility = window.location.hash.replace(/#\/?/, '')
  161. if (filters[visibility]) {
  162. this.visibility = visibility
  163. } else {
  164. window.location.hash = ''
  165. this.visibility = 'all'
  166. }
  167. },
  168. pluralize (n) {
  169. return n === 1 ? 'item' : 'items'
  170. }
  171. },
  172. directives: {
  173. 'todo-focus'(el, binding) {
  174. if (binding.value) {
  175. el.focus()
  176. }
  177. }
  178. }
  179. }).mount('#app')
  180. </script>