todomvc.html 5.3 KB

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