BREAKING CHANGE: `createApp` API has been adjusted.
  - `createApp()` now accepts the root component, and optionally a props
  object to pass to the root component.
  - `app.mount()` now accepts a single argument (the root container)
  - `app.unmount()` no longer requires arguments.
  New behavior looks like the following:
  ``` js
  const app = createApp(RootComponent)
  app.mount('#app')
  app.unmount()
  ```
		
	
			
		
			
				
	
	
		
			197 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<script src="../../dist/vue.global.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="newTodo"
 | 
						|
              @keyup.enter="addTodo">
 | 
						|
    </header>
 | 
						|
    <section class="main" v-show="todos.length">
 | 
						|
      <input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone">
 | 
						|
      <label for="toggle-all">Mark all as complete</label>
 | 
						|
      <ul class="todo-list">
 | 
						|
        <li v-for="todo in filteredTodos"
 | 
						|
            class="todo"
 | 
						|
            :key="todo.id"
 | 
						|
            :class="{ completed: todo.completed, editing: todo === 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 === editedTodo"
 | 
						|
                  @blur="doneEdit(todo)"
 | 
						|
                  @keyup.enter="doneEdit(todo)"
 | 
						|
                  @keyup.escape="cancelEdit(todo)"
 | 
						|
          >
 | 
						|
        </li>
 | 
						|
      </ul>
 | 
						|
    </section>
 | 
						|
    <footer class="footer" v-show="todos.length">
 | 
						|
        <span class="todo-count">
 | 
						|
          <strong>{{ remaining }}</strong> <span>{{ pluralize(remaining) }} left</span>
 | 
						|
        </span>
 | 
						|
      <ul class="filters">
 | 
						|
        <li><a href="#/all" :class="{ selected: visibility === 'all' }">All</a></li>
 | 
						|
        <li><a href="#/active" :class="{ selected: visibility === 'active' }">Active</a></li>
 | 
						|
        <li><a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a></li>
 | 
						|
      </ul>
 | 
						|
      <button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
 | 
						|
        Clear completed
 | 
						|
      </button>
 | 
						|
    </footer>
 | 
						|
  </section>
 | 
						|
</div>
 | 
						|
 | 
						|
<script>
 | 
						|
const STORAGE_KEY = 'todos-vuejs-3.x'
 | 
						|
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
 | 
						|
    })
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Vue.createApp({
 | 
						|
  // app initial state
 | 
						|
  data: () => ({
 | 
						|
    todos: todoStorage.fetch(),
 | 
						|
    newTodo: '',
 | 
						|
    editedTodo: null,
 | 
						|
    visibility: 'all'
 | 
						|
  }),
 | 
						|
 | 
						|
  // watch todos change for localStorage persistence
 | 
						|
  watch: {
 | 
						|
    todos: {
 | 
						|
      handler(todos) {
 | 
						|
        todoStorage.save(todos)
 | 
						|
      },
 | 
						|
      deep: true
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  mounted() {
 | 
						|
    window.addEventListener('hashchange', this.onHashChange)
 | 
						|
    this.onHashChange()
 | 
						|
  },
 | 
						|
 | 
						|
  computed: {
 | 
						|
    filteredTodos() {
 | 
						|
      return filters[this.visibility](this.todos)
 | 
						|
    },
 | 
						|
    remaining() {
 | 
						|
      return filters.active(this.todos).length
 | 
						|
    },
 | 
						|
    allDone: {
 | 
						|
      get() {
 | 
						|
        return this.remaining === 0
 | 
						|
      },
 | 
						|
      set(value) {
 | 
						|
        this.todos.forEach(function (todo) {
 | 
						|
          todo.completed = value
 | 
						|
        })
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // methods that implement data logic.
 | 
						|
  // note there's no DOM manipulation here at all.
 | 
						|
  methods: {
 | 
						|
    addTodo() {
 | 
						|
      var value = this.newTodo && this.newTodo.trim()
 | 
						|
      if (!value) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
      this.todos.push({
 | 
						|
        id: todoStorage.uid++,
 | 
						|
        title: value,
 | 
						|
        completed: false
 | 
						|
      })
 | 
						|
      this.newTodo = ''
 | 
						|
    },
 | 
						|
 | 
						|
    removeTodo(todo) {
 | 
						|
      this.todos.splice(this.todos.indexOf(todo), 1)
 | 
						|
    },
 | 
						|
 | 
						|
    editTodo(todo) {
 | 
						|
      this.beforeEditCache = todo.title
 | 
						|
      this.editedTodo = todo
 | 
						|
    },
 | 
						|
 | 
						|
    doneEdit(todo) {
 | 
						|
      if (!this.editedTodo) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
      this.editedTodo = null
 | 
						|
      todo.title = todo.title.trim()
 | 
						|
      if (!todo.title) {
 | 
						|
        this.removeTodo(todo)
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    cancelEdit(todo) {
 | 
						|
      this.editedTodo = null
 | 
						|
      todo.title = this.beforeEditCache
 | 
						|
    },
 | 
						|
 | 
						|
    removeCompleted() {
 | 
						|
      this.todos = filters.active(this.todos)
 | 
						|
    },
 | 
						|
 | 
						|
    onHashChange() {
 | 
						|
      var visibility = window.location.hash.replace(/#\/?/, '')
 | 
						|
      if (filters[visibility]) {
 | 
						|
        this.visibility = visibility
 | 
						|
      } else {
 | 
						|
        window.location.hash = ''
 | 
						|
        this.visibility = 'all'
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    pluralize (n) {
 | 
						|
      return n === 1 ? 'item' : 'items'
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  directives: {
 | 
						|
    'todo-focus'(el, binding) {
 | 
						|
      if (binding.value) {
 | 
						|
        el.focus()
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}).mount('#app')
 | 
						|
</script>
 |