教程: TodoMVC 与 director 路由
概述
mvc
通常与模型,视图,控制器三方配合。vue本身已偏向view渲染层,因此本例用localStorage
作数据持久化存储器,director
路由库充当控制器,负责页面节点渲染分发调度,从而实现一个简易的todoMVC应用。
知识点
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))
}
}
效果图
源码
app.d.js
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')
index.html
文件
<!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>
<!--使用director库进行路由,自动分发调度,触发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>
本作品采用《CC 协议》,转载必须注明作者和本文链接