一、在github中下载TodoMVC的模板:
1、TodoMVC官网:http://todomvc.com/
2、模板下载的GitHub网址:https://github.com/tastejs/todomvc-app-template
方式一:使用git下载到项目文件夹:
git clone https://github.com/tastejs/todomvc-app-template.git
方式二:直接下载zip压缩包:
二、nmp 安装依赖:
1、进入项目所在文件夹下:
2、使用命令:npm install,回车安装。
注意:要使用 npm 命令,需要安装 node.js 环境才可用。node.js 环境安装请自行百度。
三、在VS code中打开项目文件夹:
打开index.html文件,在浏览器中打开测试一下:
四、初始化项目:
1、下载vue依赖模块:
在VS code中打开终端,进入到项目所在文件夹下,使用以下命令安装vue:
npm install vue@2.6.10
2、引入vue.js到index.html文件中:
3、找到index.html中要被vue管理的父元素:<section>标签
注:下方<footer>标签为页面下方提示信息,只是静态显示,不需要vue操作。
4、为<section>标签取个id名:id="todoapp",作为vue管理的el入口。
5、模板中默认引入了app.js文件,作为vue.js的代码存入的地方,因此,在app.js中创建vue的实例对象。
打开index.html测试:
五、数据列表渲染:
1、声明静态事件项:
2、找到事件项所在的元素上:
3、v-for迭代输出<li>元素:
4、模板中定义了一个completed的样式,这个样式会使已经完成的项目文字上有删除线:
现在要基于事件项前面的勾选按钮来控制completed样式的生效与否,在completed样式上进行属性绑定v-bind:
:class={key为class样式名,value为获取的数据true或false}
5、事件项后删除按钮绑定事件项的id:
要为删除按钮绑定事件,通过获取事件项的id值来确定删除的是哪一项:
6、若没有事件项,则事件项下方的操作按钮应该设置为隐藏:
在浏览器中找到下方的操作按钮所在元素标签:
使用v-show通过对数组中的长度判断,来设置<section>和<footer>中的内容显示与否:
当items.length = 0,则解析为:v-show = false,则标签中的内容不显示,否则显示。
测试:注释掉items中的数组,查看浏览器:
六、添加任务:
1、在最上面的文本框中添加新的事件项:
找到文本框所在的input标签:
在app.js中添加函数方法:
2、获取输入框中的输入的值:
使用event事件操作DOM,来获取输入框中的值:
最后在浏览器控制台查看,其中value值在target下:
因此获取value的值只需要:e.target.value。
3、将输入的事件项添加到未完成事件项中:
①若输入为空或者为空格,则不做操作;(判断是否为空可以使用.trim()函数,可以去除空格)
②若不为空则添加到事件列表中,默认状态为未完成。(添加操作可以使用.push()函数)
4、添加完成后清除输入框中的内容:
注:此实例数据是提前声明的,未用到数据库,因此添加事件项后若刷新页面,依旧是显示没添加新数据之前的网页。解决方法是将数据保存在数据库中,然后对数据库进行增删改查来改动数据。
七、显示所有未完成数据:
1、左下角显示未完成事件项的个数:
2、查找“显示所有未完成数据”这一操作在什么标签元素下:
3、双向绑定:
4、定义计算属性,检测未完成数量:(使用.filter数组函数过滤事件项)
其中filter()中有一个回调函数,定义一个i用来接收items数组中的数组项,然后i.completed获取“未完成的事件项”的数组并返回,在赋值给unitems数组,最后返回unitems数组的长度,也就是未完成的事件项的个数。
5、调节item细节:
当未完成事件项数为0或者>1个时,页面中显示为items,
当未完成事件项数为1时,页面中显示为item。
八、改变所有任务状态:
1、单机输入框左边的复选框按钮可以切换所以事件项的状态:
2、实现方法,添加计算属性,设置set/get方法,双向监听:
①查找复选框所在元素标签:
②双向绑定,添加计算属性:
③使用set/get:
注:计算属性如果不设置get/set,则默认是用 get
,可以把值返回出去。
set
方法可以有一个参数,这里我定义为 newStatus,在这段代码中则就是函数 toggleAll
的值。
九、移除事件项:
1、悬停在某个事件项上显示 X 移除按钮,可点击移除当前事件项。
2、实现方法:通过数组函数 splice() 移除事件项
①查找X按钮所在标签元素:
②绑定事件,添加时间函数:
③获取删除的事件项的索引值并在点击函数中加入:(删除哪个事件项的id)
④删除所选事件项:
首先介绍splice()函数用法:
arrayObject.splice(index,howmany,item1,.....,itemX);
参数 | 描述 |
---|---|
index | 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。 |
howmany | 必需。要删除的项目数量。如果设置为 0,则不会删除项目。 |
item1, ..., itemX | 可选。向数组添加的新项目。 |
此例中,不需要加入新的数组,即不需要第三个参数。
因此:
十、一键删除已完成的所有事件项:
1、功能分析:
右下角有Clear completed按钮:
当没有已完成事件项,则此按钮隐藏,否则显示;
当点击此按钮时,可以清除已选择的已完成事件项。
2、查找按钮所在元素标签:
3、添加点击事件,用filter()函数过滤:
4、v-show设置按钮的隐藏显示:
注:remain为上面第七步的计算属性中的一个方法函数,返回值是未完成的事件数量。
十一、双击编辑事件项:
1、查找事件项所在元素:
2、双击(dblclick) label进入编辑状态:(在<li>上通过 .editing 进行切换状态)
①添加双击事件:
②在<li>上添加editing 属性:
currentItem是获取当前点击的事件项:需要在app.js中声明currentItem。
3、进入编辑状态后,输入框中显示的是原内容:
4、进入编辑状态后,按ESC按钮退出编辑状态:
注意:必须要在光标获取到焦点后,按ESC按键才能取消编辑状态。解决方法:定义进入编辑状态,自动获取焦点。
5、按Enter键或失去焦点时,数据自动保存,若输入为空,则清除该事件项:
6、自定义指令自动获取焦点(包括主输入框和双击事件项进入编辑状态皆自动获取焦点):
7、为主输入框和双击事件项进入编辑状态添加自动获取焦点属性:
注:当为主输入框添加自动获取焦点属性,测试可以成功,但为“双击事件项进入编辑状态”添加自动获取焦点属性则测试失败,原因是在定义自定义指令时,用的是inserted钩子函数,此方法只第一次插入时才调用。
因此,才有update钩子函数:update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
8、判断是否是当前点击的事件项:
另:自定义局部指令获取焦点:
十二、过滤不同状态数据:
本例中事件项列表下方有三个按钮:
分别代表:All显示所有;Active显示未完成;Completed显示已完成。
1、在 data 中定义变量 filterStatus , 用于接收变化的状态值:
2、通过 window.onhashchange获取点击的路由hash,来获取对应的那个状态值,并将状态值赋值给filterStatus:
3、在Vue的计算属性中创建filter方法函数用于过滤出目标数据, 用于感知filterStatus的状态值变化:
4、在index.html文件中循环遍历当filterStatus状态值不同时的事件项列表:
5、根据当前所在hash页面,改变对应的按钮样式:class="selected"
十三、数据保存到本地localStorage:
之前在第六步已然提及:
此实例数据是提前声明的,未用到数据库,因此添加事件项后若刷新页面,依旧是显示没添加新数据之前的网页。解决方法是将数据保存在数据库中,然后对数据库进行增删改查来改动数据。
当然还可以将所有任务项数据持久化到localStorage中,用于本地存储数据。
思路:使用 Vue 中的 watch 监听器,监听任务数组items一旦有改变,则使用 window.localStorage 将它就重新保存到 localStorage。
1、定义 watch 监听器:
因为要监听items数组中的每个对象和每个对象下的每个属性的改变,但watch监听器无法做到,因此要使用命令式的 vm.$watch API深度监听。
使用方法参见中文文档:https://cn.vuejs.org/v2/api/#vm-watch
items数组内部是对象,当对象的值发生变化后要被监听到,在选项参数中使用deep: true。
2、将数据保存到本地localStorage:(localStorage用法详见:https://www.runoob.com/jsref/prop-win-localstorage.html)
①初始化数据操作:
localStorage.getItem(STORAGE_KEY)||'[]' //获取items中的字符串值,若为空则返回[]。
②调用方法,将数据保存到本地:
1)更改当前初始化items数据:
2)将数据保存到本地:
十四、附:所有代码
1、index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Template • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<section class="todoapp" id="todoapp">
<header class="header">
<h1>{{title}}</h1>
<input @keyup.enter = "addItem" class="new-todo" placeholder="What needs to be done?" v-focus>
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main" v-show="items.length">
<input v-model="toggleAll" id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<li v-for = "(item,index) in filter" :class = "{completed:item.completed,editing:item === currentItem}" >
<div class="view">
<!-- v-model双向绑定 -->
<input class="toggle" type="checkbox" v-model="item.completed">
<!-- 修改事件项的内容 -->
<label @dblclick="dblc(item)">{{item.content}}</label>
<button @click = "removeItem(index)" class="destroy" :value = "item.id"></button>
</div>
<input v-focus="item === currentItem" @keyup.enter="enter(item,index,$event)" @blur="enter(item,index,$event)"
@keyup.esc="esc" class="edit" :value="item.content">
</li>
</ul>
</section>
<!-- This footer should hidden by default and shown when there are todos -->
<footer class="footer" v-show="items.length" >
<!-- This should be `0 items left` by default -->
<span class="todo-count"><strong>{{remain}}</strong> item{{remain === 1?'':'s'}} left</span>
<!-- Remove this if you don't implement routing -->
<ul class="filters">
<li>
<a :class="{selected:filterStatus==='all'}" href="#/">All</a>
</li>
<li>
<a :class="{selected:filterStatus==='active'}" href="#/active">Active</a>
</li>
<li>
<a :class="{selected:filterStatus==='completed'}" href="#/completed">Completed</a>
</li>
</ul>
<!-- Hidden if no completed items are left ↓ -->
<!-- 当总任务数大于未完成任务数,则显示按钮 -->
<button v-show="items.length > remain" @click="clearItems" class="clear-completed">Clear completed</button>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<!-- Remove the below line ↓ -->
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<!-- Change this out with your name and url ↓ -->
<p>Created by <a href="http://todomvc.com">you</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<!-- Scripts here. Don't remove ↓ -->
<script src="node_modules/vue/dist/vue.js"></script>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="js/app.js"></script>
</body>
</html>
2、app.js
(function (Vue) {
//声明一个key值
const STORAGE_KEY = 'item';
//声明变量获取保存本地数据
const localstorage ={
//获取数据方法
fetch:function(){
//JSON.parse将字符串转换为数组对象
return JSON.parse(
localStorage.getItem(STORAGE_KEY)||'[]'
)
},
//保存数据方法
save:function(items){
//JSON.stringify(value)将value中的内容转成字符串
localStorage.setItem(STORAGE_KEY,JSON.stringify(items))
}
}
const items = [
{
id:1, //编号
content:'今天早上学JS', //事件内容
completed:false //完成状态
},
{
id:2, //编号
content:'今天下午学HTML',//事件内容
completed:false //完成状态
},
{
id:3, //编号
content:'今天晚上学CSS', //事件内容
completed:false //完成状态
}
]
//自定义全局指令,自动获取焦点
Vue.directive('focus',{
inserted(el,binding){
el.focus();
},
update(el,binding) {
if(binding.value)
el.focus();
},
})
var vm = new Vue({
el:'#todoapp',
data:{
title:'myList',
//items:items, // ES6中可直接写一个items等价于——>items:items
items:localstorage.fetch(),
currentItem:null,
filterStatus:'all'
},
//自定义局部指令,自动获取焦点
//directives:{
// 'focus':{
// update(el,binding) {
// if(binding.value)
// el.focus();
// },
// }
//},
computed: {
//在Vue的计算属性中创建filter方法函数用于过滤出目标数据, 用于感知filterStatus的状态值变化:
filter(){
switch (this.filterStatus) {
case 'active':
return this.items.filter(function(i){
return !i.completed;
})
break;
case 'completed':
return this.items.filter(i=>i.completed);
break;
//否则默认返回所有事件项
default:
return this.items;
break;
}
},
toggleAll:{
//当事件列表中的状态发生变化,更新复选框状态
get(){
//恒等式左边代表未完成的个数,右边恒为0,只有两边恒等,则返回true
//否则返回false
return this.remain === 0;
},
//当复选框状态发生变化,更新任务列表所有事件项的状态
set(newStatus){
//遍历出数组中所有事件项
//将当前复选框的状态赋值给每个事件项的completed状态
this.items.forEach(function(i){
i.completed = newStatus;
})
}
},
remain(){ //ES6简写
const unitems = this.items.filter(function(i){
return i.completed === false;
});
return unitems.length;
}
},
methods: {
//完成编辑,保存数据
enter(item,index,e){
//1获取当前输入框的值
const content = e.target.value.trim();
//2判断输入框的值是否为空,如果为空,删除事件项
if(!content){
//复用下面的removeItem()函数
this.removeItem(index);
return;
}
//3否则,更新事件项
else{
item.content = content;
}
//4取消editing的作用,退出编辑状态
this.currentItem = null;
},
//取消编辑状态
esc(){
//当this.currentItem为空时,editing:item === currentItem返回false
//则取消editing的作用
this.currentItem = null;
},
//进入双击编辑状态
dblc(item){
//把当前点击的事件项赋值给currentItem数组
this.currentItem = item;
},
clearItems(){
//过滤所有未完成的事件项并赋值给新数组i
this.items = this.items.filter(function(i){
return i.completed === false;
})
//ES6写法:
//this.items = this.items(i => !i.completed );
},
removeItem(index){
//splice(index,1)从要删除的位置,每次删除一个
this.items.splice(index,1);
},
addItem:function(e){ //ES6中 直接——> addItem(){}
const content = e.target.value.trim();
if (!content.length){
return;
}
else{
const id = this.items.length + 1;
this.items.push(
{
id:id, //ES6直接一个 id 即可
content:content, //ES6直接一个 content 即可
completed:false //完成状态默认false
}
)
}
e.target.value = "";
}
},
watch: {
items:{
deep:true,
handler:function(newItems,oldItems){
//将数据保存到本地
localstorage.save(newItems)
}
}
},
})
//window.onhashchange要在Vue实例对象之外
window.onhashchange = function(){
//截取从索引为2之后的hash字符串,若为空,则返回'all'
const hash = window.location.hash.substr(2)||'all';
//将hash赋值给上面Vue实例中的filterStatus
//在Vue的计算属性中创建filter方法函数
vm.filterStatus = hash;
}
//调用
window.onhashchange();
})(Vue);