概述
mvc
通常與模型,檢視,控制器三方配合。vue本身已偏向view渲染層,因此本例用localStorage
作資料持久化儲存器,director
路由庫充當控制器,負責頁面節點渲染分發排程,從而實現一個簡易的todo應用。
知識點
vuejs
單頁面的增刪改查 TodoMVC
vue
自定義指令,觀察函式,計算屬性,方法使用
localStorage
物件 結合 JSON
的序列化與反序列化作為持久化層
director
無依賴輕量級的前端路由庫,可在服務端/客戶端/命令列路由
路由
- 正則匹配路由,傳參監聽
director
基於hash的路由方式 如 /a/c
匹配的對應路徑 index.php#/a/c
Router({
'/(active|completed|all)/?': function (filter) {
// 間接渲染vue例項關聯檢視
app.visibility = filter
}
}).init()
資料持久化
var STORAGE_KEY = 'pardon110'
var todoStorage = {
fetch() {
var 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))
}
}
效果圖
原始碼
var STORAGE_KEY = 'pardon110'
// 儲存器例項物件
var todoStorage = {
fetch() {
var 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))
}
}
// 可見性過濾
var filters = {
all: todos => todos,
active: todos => todos.filter(todo => !todo.completed),
completed: todos => todos.filter(todo => todo.completed)
}
var app = new Vue({
// 初始化應用狀態
data: {
todos: todoStorage.fetch(),
newTodo: '',
editedTodo: null,
visibility: 'all'
},
// 觀察持久化儲存
watch: {
todos: {
// 注意,不應該使用箭頭函式來定義 watcher 函式
// 箭頭函式繫結了父級作用域的上下文, 其this 將不會按照期望指向 Vue 例項
handler: todoStorage.save,
// 該回撥會在任何被偵聽的物件的 property 改變時被呼叫,不論其被巢狀多深
deep: true
}
},
// 計算屬性
computed: {
filteredTodos() {
return filters[this.visibility](this.todos)
},
remaining() {
return filters.active(this.todos).length
},
// getter/setter
allDone: {
get() {
return this.remaining === 0
},
set(value) {
this.todos.forEach(todo => todo.completed = value)
}
}
},
// 檢視過濾器,型別於管道用|
filters: {
// 複數轉換
pluralize: function (n) {
return n === 1 ? 'item' : 'items'
}
},
// 實現資料增刪改邏輯,注意不直接操作Dom節點
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)
}
},
// 自定義vue指令,在輸入欄位前實現聚集效果
directives: {
'todo-focus': function (el, binding) {
if (binding.value) {
el.focus()
}
}
}
})
// 使用director庫,註冊路由
Router({
'/(active|completed|all)/?': function (filter) {
// 變更vue各應資料,渲染檢視
app.visibility = filter
}
}).init()
// Vue例項掛載Dom節點
app.$mount('.todoapp')
<!DOCTYPE html>
<html data-framework="vue">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue.js • TodoMVC</title>
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
<!-- 前端路由庫(支援spa應用) -->
<script src="https://cdn.bootcss.com/Director/1.2.8/director.js"></script>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<!-- 雙向繫結新增 -->
<input v-model="newTodo" placeholder="寫點什麼?" autofocus autocomplete="off" @keyup.enter="addTodo"
class="new-todo">
</header>
<section class="main" v-show="todos.length" v-cloak>
<!-- 全選/全不選切換 -->
<input type="checkbox" class="toggle-all" v-model="allDone">
<!-- 顯示列表 -->
<ul class="todo-list">
<li v-for="todo in filteredTodos" :key="todo.id"
:class="{ completed: todo.completed, editing: todo == editedTodo }">
<div class="view">
<!-- 單項選中與非選中 -->
<input type="checkbox" class="toggle" v-model="todo.completed">
<!-- 雙擊條目編輯todo -->
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<!-- 點選刪除按鈕 -->
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input type="text" class="edit" v-model="todo.title" v-todo-focus="todo == editedTodo"
@blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length" v-cloak>
<span class="todo-count">
<strong> {{ remaining }}</strong>
<!-- 過濾器管道 -->
{{ remaining | pluralize }} left
</span>
<!--使用directory庫進行路由,自動分發排程,觸發vue例項回撥,間接渲染檢視-->
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: visibility == 'all'}">全部</a>
<a href="#/active" :class="{ selected: visibility == 'active'}">活躍</a>
<a href="#/completed" :class="{ selected: visibility == 'completed'}">回收站</a>
</li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
清空
</button>
</footer>
</section>
<footer class="info">
<p>雙擊編輯一個todo應用</p>
<p>Written by <a href="http://evanyou.me">Evan You</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="app.d.js"></script>
</body>
</html>