day02
Vue常用特性
表单基本操作
-
获取单选框中的值
- 通过v-model
<!-- 1、 两个单选框需要同时通过v-model 双向绑定 一个值 2、 每一个单选框必须要有value属性 且value 值不能一样 3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据 gender 的值就是选中的值,我们只需要实时监控他的值就可以了 --> <input type="radio" id="male" value="1" v-model='gender'> <label for="male">男</label> <input type="radio" id="female" value="2" v-model='gender'> <label for="female">女</label> <script> new Vue({ data: { // 默认会让当前的 value 值为 2 的单选框选中 gender: 2, }, }) </script>
-
获取复选框中的值
- 通过v-model
- 和获取单选框中的值一样
- 复选框
checkbox
这种的组合时 data 中的 hobby 我们要定义成数组 否则无法实现多选
<!-- 1、 复选框需要同时通过v-model 双向绑定 一个值 2、 每一个复选框必须要有value属性 且value 值不能一样 3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据 hobby 的值就是选中的值,我们只需要实时监控他的值就可以了 --> <div> <span>爱好:</span> <input type="checkbox" id="ball" value="1" v-model='hobby'> <label for="ball">篮球</label> <input type="checkbox" id="sing" value="2" v-model='hobby'> <label for="sing">唱歌</label> <input type="checkbox" id="code" value="3" v-model='hobby'> <label for="code">写代码</label> </div> <script> new Vue({ data: { // 默认会让当前的 value 值为 2 和 3 的复选框选中 hobby: ['2', '3'], }, }) </script>
-
获取下拉框和文本框中的值
- 通过v-model
<div> <span>职业:</span> <!-- 1、 需要给select 通过v-model 双向绑定 一个值 2、 每一个option 必须要有value属性 且value 值不能一样 3、 当某一个option选中的时候 v-model 会将当前的 value值 改变 data 中的 数据 occupation 的值就是选中的值,我们只需要实时监控他的值就可以了 --> <!-- multiple 多选 --> <select v-model='occupation' multiple> <option value="0">请选择职业...</option> <option value="1">教师</option> <option value="2">软件工程师</option> <option value="3">律师</option> </select> <!-- textarea 是 一个双标签 不需要绑定value 属性的 --> <textarea v-model='desc'></textarea> </div> <script> new Vue({ data: { // 默认会让当前的 value 值为 2 和 3 的下拉框选中 occupation: ['2', '3'], desc: 'nihao' }, }) </script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
form div {
height: 40px;
line-height: 40px;
}
form div:nth-child(4) {
height: auto;
}
form div span:first-child {
display: inline-block;
width: 100px;
}
</style>
</head>
<body>
<div id="app">
<form action="http://itcast.cn">
<div>
<span>姓名:</span>
<span>
<input type="text" v-model='uname'>
</span>
</div>
<div>
<span>性别:</span>
<span>
<input type="radio" id="male" value="1" v-model='gender'>
<label for="male">男</label>
<input type="radio" id="female" value="2" v-model='gender'>
<label for="female">女</label>
</span>
</div>
<div>
<span>爱好:</span>
<input type="checkbox" id="ball" value="1" v-model='hobby'>
<label for="ball">篮球</label>
<input type="checkbox" id="sing" value="2" v-model='hobby'>
<label for="sing">唱歌</label>
<input type="checkbox" id="code" value="3" v-model='hobby'>
<label for="code">写代码</label>
</div>
<div>
<span>职业:</span>
<select v-model='occupation' multiple>
<option value="0">请选择职业...</option>
<option value="1">教师</option>
<option value="2">软件工程师</option>
<option value="3">律师</option>
</select>
</div>
<div>
<span>个人简介:</span>
<textarea v-model='desc'></textarea>
</div>
<div>
<input type="submit" value="提交" @click.prevent='handle'>
</div>
</form>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
表单基本操作
*/
var vm = new Vue({
el: '#app',
data: {
uname: 'lisi',
gender: 2,
hobby: ['2','3'],
// occupation: 3
occupation: ['2','3'],
desc: 'nihao'
},
methods: {
handle: function(){
// console.log(this.uname)
// console.log(this.gender)
// console.log(this.hobby.toString())
// console.log(this.occupation)
console.log(this.desc)
}
}
});
</script>
</body>
</html>
表单修饰符
-
.number 转换为数值
- 注意点:
- 当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值
- 所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串。
-
.trim 自动过滤用户输入的首尾空白字符
- 只能去掉首尾的 不能去除中间的空格
-
.lazy 将input事件切换成change事件
- .lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
-
在失去焦点 或者 按下回车键时才更新
<!-- 自动将用户的输入值转为数值类型 --> <input v-model.number="age" type="number"> <!--自动过滤用户输入的首尾空白字符 --> <input v-model.trim="msg"> <!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg" >
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model.number='age'>
<input type="text" v-model.trim='info'>
<input type="text" v-model.lazy='msg'>
<div>{{msg}}</div>
<button @click='handle'>点击</button>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
表单域修饰符
*/
var vm = new Vue({
el: '#app',
data: {
age: '',
info: '',
msg: ''
},
methods: {
handle: function(){
// console.log(this.age + 13)
// console.log(this.info.length)
}
}
});
</script>
</body>
</html>
自定义指令
- 内置指令不能满足我们特殊的需求
- Vue允许我们自定义指令
Vue.directive 注册全局指令
<!--
使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。
-->
<input type="text" v-focus>
<script>
// 注意点:
// 1、 在自定义指令中 如果以驼峰命名的方式定义 如 Vue.directive('focusA',function(){})
// 2、 在HTML中使用的时候 只能通过 v-focus-a 来使用
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中。 其中 el为dom元素
inserted: function (el) {
// 聚焦元素
el.focus();
}
});
new Vue({
el:'#app'
});
</script>
Vue.directive 注册全局指令 带参数
<input type="text" v-color='msg'>
<script type="text/javascript">
/*
自定义指令-带参数
bind - 只调用一次,在指令第一次绑定到元素上时候调用
*/
Vue.directive('color', {
// bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
// el 为当前自定义指令的DOM元素
// binding 为自定义的函数形参 通过自定义属性传递过来的值 存在 binding.value 里面
bind: function(el, binding){
// 根据指令的参数设置背景色
// console.log(binding.value.color)
el.style.backgroundColor = binding.value.color;
}
});
var vm = new Vue({
el: '#app',
data: {
msg: {
color: 'blue'
}
}
});
</script>
自定义指令局部指令
- 局部指令,需要定义在 directives 的选项 用法和全局用法一样
- 局部指令只能在当前组件里面使用
- 当全局指令和局部指令同名时以局部指令为准
<input type="text" v-color='msg'>
<input type="text" v-focus>
<script type="text/javascript">
/*
自定义指令-局部指令
*/
var vm = new Vue({
el: '#app',
data: {
msg: {
color: 'red'
}
},
//局部指令,需要定义在 directives 的选项
directives: {
color: {
bind: function(el, binding){
el.style.backgroundColor = binding.value.color;
}
},
focus: {
inserted: function(el) {
el.focus();
}
}
}
});
</script>
计算属性 computed
- 模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁
- 计算属性是基于它们的响应式依赖进行缓存的
- computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化
<div id="app">
<!--
当多次调用 reverseString 的时候
只要里面的 num 值不改变 他会把第一次计算的结果直接返回
直到data 中的num值改变 计算属性才会重新发生计算
-->
<div>{{reverseString}}</div>
<div>{{reverseString}}</div>
<!-- 调用methods中的方法的时候 他每次会重新调用 -->
<div>{{reverseMessage()}}</div>
<div>{{reverseMessage()}}</div>
</div>
<script type="text/javascript">
/*
计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
*/
var vm = new Vue({
el: '#app',
data: {
msg: 'Nihao',
num: 100
},
methods: {
reverseMessage: function(){
console.log('methods')
return this.msg.split('').reverse().join('');
}
},
//computed 属性 定义 和 data 已经 methods 平级
computed: {
// reverseString 这个是我们自己定义的名字
reverseString: function(){
console.log('computed')
var total = 0;
// 当data 中的 num 的值改变的时候 reverseString 会自动发生计算
for(var i=0;i<=this.num;i++){
total += i;
}
// 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果
return total;
}
}
});
</script>
侦听器 watch
- 使用watch来响应数据的变化
- 一般用于异步或者开销较大的操作
- watch 中的属性 一定是data 中 已经存在的数据
- 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
<div id="app">
<div>
<span>名:</span>
<span>
<input type="text" v-model='firstName'>
</span>
</div>
<div>
<span>姓:</span>
<span>
<input type="text" v-model='lastName'>
</span>
</div>
<div>{{fullName}}</div>
</div>
<script type="text/javascript">
/*
侦听器
*/
var vm = new Vue({
el: '#app',
data: {
firstName: 'Jim',
lastName: 'Green',
// fullName: 'Jim Green'
},
//watch 属性 定义 和 data 已经 methods 平级
watch: {
// 注意: 这里firstName 对应着data 中的 firstName
// 当 firstName 值 改变的时候 会自动触发 watch
firstName: function(val) {
this.fullName = val + ' ' + this.lastName;
},
// 注意: 这里 lastName 对应着data 中的 lastName
lastName: function(val) {
this.fullName = this.firstName + ' ' + val;
}
}
});
</script>
侦听器案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<span>用户名:</span>
<span>
<input type="text" v-model.lazy='uname'>
</span>
<span>{{tip}}</span>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
侦听器
1、采用侦听器监听用户名的变化
2、调用后台接口进行验证
3、根据验证的结果调整提示信息
*/
var vm = new Vue({
el: '#app',
data: {
uname: '',
tip: ''
},
methods: {
checkName: function(uname) {
// 调用接口,但是可以使用定时任务的方式模拟接口调用
var that = this;
setTimeout(function(){
// 模拟接口调用
if(uname == 'admin') {
that.tip = '用户名已经存在,请更换一个';
}else{
that.tip = '用户名可以使用';
}
}, 2000);
}
},
watch: {
uname: function(val){
// 调用后台接口验证用户名的合法性
this.checkName(val);
// 修改提示信息
this.tip = '正在验证...';
}
}
});
</script>
</body>
</html>
过滤器
- Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
- 过滤器可以用在两个地方:双花括号插值和v-bind表达式。
- 过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
- 支持级联操作
- 过滤器不改变真正的
data
,而只是改变渲染的结果,并返回过滤后的版本 - 全局注册时是filter,没有s的。而局部过滤器是filters,是有s的
<div id="app">
<input type="text" v-model='msg'>
<!-- upper 被定义为接收单个参数的过滤器函数,表达式 msg 的值将作为参数传入到函数中 -->
<div>{{msg | upper}}</div>
<!--
支持级联操作
upper 被定义为接收单个参数的过滤器函数,表达式msg 的值将作为参数传入到函数中。
然后继续调用同样被定义为接收单个参数的过滤器 lower ,将upper 的结果传递到lower中
-->
<div>{{msg | upper | lower}}</div>
<div :abc='msg | upper'>测试数据</div>
</div>
<script type="text/javascript">
// lower 为全局过滤器
/*
过滤器
1、可以用与插值表达式和属性绑定
2、支持级联操作
*/
// Vue.filter('upper', function(val) {
// return val.charAt(0).toUpperCase() + val.slice(1);
// });
Vue.filter('lower', function(val) {
return val.charAt(0).toLowerCase() + val.slice(1);
});
var vm = new Vue({
el: '#app',
data: {
msg: ''
},
//filters 属性 定义 和 data 已经 methods 平级
// 定义filters 中的过滤器为局部过滤器
filters: {
// upper 自定义的过滤器名字
// upper 被定义为接收单个参数的过滤器函数,表达式 msg 的值将作为参数传入到函数中
upper: function(val) {
// 过滤器中一定要有返回值 这样外界使用过滤器的时候才能拿到结果
return val.charAt(0).toUpperCase() + val.slice(1);
}
}
});
</script>
过滤器中传递参数
<div id="box">
<!--
filterA 被定义为接收三个参数的过滤器函数。
其中 message 的值作为第一个参数,
普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
-->
{{ message | filterA('arg1', 'arg2') }}
</div>
<script>
// 在过滤器中 第一个参数 对应的是 管道符前面的数据 n 此时对应 message
// 第2个参数 a 对应 实参 arg1 字符串
// 第3个参数 b 对应 实参 arg2 字符串
Vue.filter('filterA',function(n,a,b){
if(n<10){
return n+a;
}else{
return n+b;
}
});
new Vue({
el:"#box",
data:{
message: "哈哈哈"
}
})
</script>
带参数的过滤器案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{date | format('yyyy-MM-dd hh:mm:ss')}}</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
过滤器案例:格式化日期
*/
// Vue.filter('format', function(value, arg) {
// if(arg == 'yyyy-MM-dd') {
// var ret = '';
// ret += value.getFullYear() + '-' + (value.getMonth() + 1) + '-' + value.getDate();
// return ret;
// }
// return value;
// })
Vue.filter('format', function(value, arg) {
function dateFormat(date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
var v = map[t];
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v;
v = v.substr(v.length - 2);
}
return v;
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
}
return dateFormat(value, arg);
})
var vm = new Vue({
el: '#app',
data: {
date: new Date()
}
});
</script>
</body>
</html>
生命周期
- 事物从出生到死亡的过程
- Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数
常用的钩子函数
beforeCreate | 在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了 |
---|---|
created | 在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来 |
beforeMount | 在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已 |
mounted | el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件 |
beforeUpdate | 数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的 |
updated | 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的 |
beforeDestroy | 实例销毁之前调用 |
destroyed | 实例销毁后调用 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{msg}}</div>
<button @click='update'>更新</button>
<button @click='destroy'>销毁</button>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
Vue实例的生命周期
*/
var vm = new Vue({
el: '#app',
data: {
msg: '生命周期'
},
methods: {
update: function(){
this.msg = 'hello';
},
destroy: function(){
this.$destroy();
}
},
beforeCreate: function(){
console.log('beforeCreate');
},
created: function(){
console.log('created');
},
beforeMount: function(){
console.log('beforeMount');
},
mounted: function(){
console.log('mounted');
},
beforeUpdate: function(){
console.log('beforeUpdate');
},
updated: function(){
console.log('updated');
},
beforeDestroy: function(){
console.log('beforeDestroy');
},
destroyed: function(){
console.log('destroyed');
}
});
</script>
</body>
</html>
数组变异方法
- 在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变
- 变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展
push() | 往数组最后面添加一个元素,成功返回当前数组的长度 |
---|---|
pop() | 删除数组的最后一个元素,成功返回删除元素的值 |
shift() | 删除数组的第一个元素,成功返回删除元素的值 |
unshift() | 往数组最前面添加一个元素,成功返回当前数组的长度 |
splice() | 有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值 |
sort() | sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组 |
reverse() | reverse() 将数组倒序,成功返回倒序后的数组 |
替换数组
- 不会改变原始数组,但总是返回一个新数组
filter | filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。 |
---|---|
concat | concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组 |
slice | slice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<span>
<input type="text" v-model='fname'>
<button @click='add'>添加</button>
<button @click='del'>删除</button>
<button @click='change'>替换</button>
</span>
</div>
<ul>
<li :key='index' v-for='(item,index) in list'>{{item}}</li>
</ul>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
Vue数组操作
1、变异方法:会影响数组的原始数据的变化。
2、替换数组:不会影响原始的数组数据,而是形成一个新的数组。
*/
var vm = new Vue({
el: '#app',
data: {
fname: '',
list: ['apple','orange','banana']
},
methods: {
add: function(){
this.list.push(this.fname);
},
del: function(){
this.list.pop();
},
change: function(){
this.list = this.list.slice(0,2);
}
}
});
</script>
</body>
</html>
动态数组响应式数据
- Vue.set(a,b,c) 让 触发视图重新更新一遍,数据动态起来
- a是要更改的数据 、 b是数据的第几项、 c是更改后的数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li v-for='item in list'>{{item}}</li>
</ul>
<div>
<div>{{info.name}}</div>
<div>{{info.age}}</div>
<div>{{info.gender}}</div>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
动态处理响应式数据
*/
var vm = new Vue({
el: '#app',
data: {
list: ['apple', 'orange', 'banana'],
info: {
name: 'lisi',
age: 12
}
},
});
// vm.list[1] = 'lemon';
// Vue.set(vm.list, 2, 'lemon');
vm.$set(vm.list, 1, 'lemon');
// vm.info.gender = 'male';
vm.$set(vm.info, 'gender', 'female');
</script>
</body>
</html>
图书列表案例
- 静态列表效果
- 基于数据实现模板效果
- 处理每行的操作按钮
1、 提供的静态数据
- 数据存放在vue 中 data 属性中
var vm = new Vue({
el: '#app',
data: {
books: [{
id: 1,
name: '三国演义',
date: ''
},{
id: 2,
name: '水浒传',
date: ''
},{
id: 3,
name: '红楼梦',
date: ''
},{
id: 4,
name: '西游记',
date: ''
}]
}
}); var vm = new Vue({
el: '#app',
data: {
books: [{
id: 1,
name: '三国演义',
date: ''
},{
id: 2,
name: '水浒传',
date: ''
},{
id: 3,
name: '红楼梦',
date: ''
},{
id: 4,
name: '西游记',
date: ''
}]
}
});
2、 把提供好的数据渲染到页面上
- 利用 v-for循环 遍历 books 将每一项数据渲染到对应的数据中
<tbody>
<tr :key='item.id' v-for='item in books'>
<!-- 对应的id 渲染到页面上 -->
<td>{{item.id}}</td>
<!-- 对应的name 渲染到页面上 -->
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<td>
<!-- 阻止 a 标签的默认跳转 -->
<a href="" @click.prevent>修改</a>
<span>|</span>
<a href="" @click.prevent>删除</a>
</td>
</tr>
</tbody>
3、 添加图书
- 通过双向绑定获取到输入框中的输入内容
- 给按钮添加点击事件
- 把输入框中的数据存储到 data 中的 books 里面
<div>
<h1>图书管理</h1>
<div class="book">
<div>
<label for="id">
编号:
</label>
<!-- 3.1、通过双向绑定获取到输入框中的输入的 id -->
<input type="text" id="id" v-model='id'>
<label for="name">
名称:
</label>
<!-- 3.2、通过双向绑定获取到输入框中的输入的 name -->
<input type="text" id="name" v-model='name'>
<!-- 3.3、给按钮添加点击事件 -->
<button @click='handle'>提交</button>
</div>
</div>
</div>
<script type="text/javascript">
/*
图书管理-添加图书
*/
var vm = new Vue({
el: '#app',
data: {
id: '',
name: '',
books: [{
id: 1,
name: '三国演义',
date: ''
},{
id: 2,
name: '水浒传',
date: ''
},{
id: 3,
name: '红楼梦',
date: ''
},{
id: 4,
name: '西游记',
date: ''
}]
},
methods: {
handle: function(){
// 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字
var book = {};
book.id = this.id;
book.name = this.name;
book.date = '';
// 3.5 把book 通过数组的变异方法 push 放到 books 里面
this.books.push(book);
//3.6 清空输入框
this.id = '';
this.name = '';
}
}
});
</script>
4 修改图书-上
- 点击修改按钮的时候 获取到要修改的书籍名单
- 4.1 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去 这样才知道需要修改的是哪一本书籍
- 把需要修改的书籍名单填充到表单里面
- 4.2 根据传递过来的id 查出books 中 对应书籍的详细信息
- 4.3 把获取到的信息填充到表单
<div id="app">
<div class="grid">
<div>
<h1>图书管理</h1>
<div class="book">
<div>
<label for="id">
编号:
</label>
<input type="text" id="id" v-model='id' :disabled="flag">
<label for="name">
名称:
</label>
<input type="text" id="name" v-model='name'>
<button @click='handle'>提交</button>
</div>
</div>
</div>
<table>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key='item.id' v-for='item in books'>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<td>
<!---
4.1 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去
这样才知道需要修改的是哪一本书籍
--->
<a href="" @click.prevent='toEdit(item.id)'>修改</a>
<span>|</span>
<a href="" @click.prevent>删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script type="text/javascript">
/*
图书管理-添加图书
*/
var vm = new Vue({
el: '#app',
data: {
flag: false,
id: '',
name: '',
books: [{
id: 1,
name: '三国演义',
date: ''
},{
id: 2,
name: '水浒传',
date: ''
},{
id: 3,
name: '红楼梦',
date: ''
},{
id: 4,
name: '西游记',
date: ''
}]
},
methods: {
handle: function(){
// 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字
var book = {};
book.id = this.id;
book.name = this.name;
book.date = '';
// 3.5 把book 通过数组的变异方法 push 放到 books 里面
this.books.push(book);
//3.6 清空输入框
this.id = '';
this.name = '';
},
toEdit: function(id){
console.log(id)
//4.2 根据传递过来的id 查出books 中 对应书籍的详细信息
var book = this.books.filter(function(item){
return item.id == id;
});
console.log(book)
//4.3 把获取到的信息填充到表单
// this.id 和 this.name 通过双向绑定 绑定到了表单中 一旦数据改变视图自动更新
this.id = book[0].id;
this.name = book[0].name;
}
}
});
</script>
5 修改图书-下
- 5.1 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 即 处于编辑状态下 当前控制书籍编号的输入框禁用
- 5.2 通过属性绑定给书籍编号的 绑定 disabled 的属性 flag 为 true 即为禁用
- 5.3 flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁用
- 5.4 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据
<div id="app">
<div class="grid">
<div>
<h1>图书管理</h1>
<div class="book">
<div>
<label for="id">
编号:
</label>
<!-- 5.2 通过属性绑定 绑定 disabled 的属性 flag 为 true 即为禁用 -->
<input type="text" id="id" v-model='id' :disabled="flag">
<label for="name">
名称:
</label>
<input type="text" id="name" v-model='name'>
<button @click='handle'>提交</button>
</div>
</div>
</div>
<table>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key='item.id' v-for='item in books'>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<td>
<a href="" @click.prevent='toEdit(item.id)'>修改</a>
<span>|</span>
<a href="" @click.prevent>删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script type="text/javascript">
/*图书管理-添加图书*/
var vm = new Vue({
el: '#app',
data: {
// 5.1 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改
// 即 处于编辑状态下 当前控制书籍编号的输入框禁用
flag: false,
id: '',
name: '',
},
methods: {
handle: function() {
/*
5.4 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑
如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据
*/
if (this.flag) {
// 编辑图书
// 5.5 根据当前的ID去更新数组中对应的数据
this.books.some((item) => {
if (item.id == this.id) {
// 箭头函数中 this 指向父级作用域的this
item.name = this.name;
// 完成更新操作之后,需要终止循环
return true;
}
});
// 5.6 编辑完数据后表单要处以可以输入的状态
this.flag = false;
// 5.7 如果 flag为false 表单处于输入状态 此时执行的用户添加数据
} else {
var book = {};
book.id = this.id;
book.name = this.name;
book.date = '';
this.books.push(book);
// 清空表单
this.id = '';
this.name = '';
}
// 清空表单
this.id = '';
this.name = '';
},
toEdit: function(id) {
/*
5.3 flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁 用
*/
this.flag = true;
console.log(id)
var book = this.books.filter(function(item) {
return item.id == id;
});
console.log(book)
this.id = book[0].id;
this.name = book[0].name;
}
}
});
</script>
6 删除图书
- 6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来
- 6.2 根据id从数组中查找元素的索引
- 6.3 根据索引删除数组元素
<tbody>
<tr :key='item.id' v-for='item in books'>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<td>
<a href="" @click.prevent='toEdit(item.id)'>修改</a>
<span>|</span>
<!-- 6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来 -->
<a href="" @click.prevent='deleteBook(item.id)'>删除</a>
</td>
</tr>
</tbody>
<script type="text/javascript">
/*
图书管理-添加图书
*/
var vm = new Vue({
methods: {
deleteBook: function(id){
// 删除图书
#// 6.2 根据id从数组中查找元素的索引
// var index = this.books.findIndex(function(item){
// return item.id == id;
// });
#// 6.3 根据索引删除数组元素
// this.books.splice(index, 1);
// -------------------------
#// 方法二:通过filter方法进行删除
# 6.4 根据filter 方法 过滤出来id 不是要删除书籍的id
# 因为 filter 是替换数组不会修改原始数据 所以需要 把 不是要删除书籍的id 赋值给 books
this.books = this.books.filter(function(item){
return item.id != id;
});
}
}
});
</script>
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
.grid {
margin: auto;
width: 530px;
text-align: center;
}
.grid table {
border-top: 1px solid #C2D89A;
width: 100%;
border-collapse: collapse;
}
.grid th,td {
padding: 10;
border: 1px dashed #F3DCAB;
height: 35px;
line-height: 35px;
}
.grid th {
background-color: #F3DCAB;
}
.grid .book {
padding-bottom: 10px;
padding-top: 5px;
background-color: #F3DCAB;
}
.grid .total {
height: 30px;
line-height: 30px;
background-color: #F3DCAB;
border-top: 1px solid #C2D89A;
}
</style>
</head>
<body>
<div id="app">
<div class="grid">
<div>
<h1>图书管理</h1>
<div class="book">
<div>
<label for="id">
编号:
</label>
<input type="text" id="id" v-model='id' :disabled="flag" v-focus>
<label for="name">
名称:
</label>
<input type="text" id="name" v-model='name'>
<button @click='handle' :disabled="submitFlag">提交</button>
</div>
</div>
</div>
<div class="total">
<span>图书总数:</span>
<span>{{total}}</span>
</div>
<table>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key='item.id' v-for='item in books'>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
<td>
<a href="" @click.prevent='toEdit(item.id)'>修改</a>
<span>|</span>
<a href="" @click.prevent='deleteBook(item.id)'>删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
图书管理-添加图书
*/
Vue.directive('focus', {
inserted: function (el) {
el.focus();
}
});
Vue.filter('format', function(value, arg) {
function dateFormat(date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
var v = map[t];
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v;
v = v.substr(v.length - 2);
}
return v;
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
}
return dateFormat(value, arg);
})
var vm = new Vue({
el: '#app',
data: {
flag: false,
submitFlag: false,
id: '',
name: '',
books: []
},
methods: {
handle: function(){
if(this.flag) {
// 编辑图书
// 就是根据当前的ID去更新数组中对应的数据
this.books.some((item) => {
if(item.id == this.id) {
item.name = this.name;
// 完成更新操作之后,需要终止循环
return true;
}
});
this.flag = false;
}else{
// 添加图书
var book = {};
book.id = this.id;
book.name = this.name;
book.date = 2525609975000;
this.books.push(book);
// 清空表单
this.id = '';
this.name = '';
}
// 清空表单
this.id = '';
this.name = '';
},
toEdit: function(id){
// 禁止修改ID
this.flag = true;
console.log(id)
// 根据ID查询出要编辑的数据
var book = this.books.filter(function(item){
return item.id == id;
});
console.log(book)
// 把获取到的信息填充到表单
this.id = book[0].id;
this.name = book[0].name;
},
deleteBook: function(id){
// 删除图书
// 根据id从数组中查找元素的索引
// var index = this.books.findIndex(function(item){
// return item.id == id;
// });
// 根据索引删除数组元素
// this.books.splice(index, 1);
// -------------------------
// 方法二:通过filter方法进行删除
this.books = this.books.filter(function(item){
return item.id != id;
});
}
},
computed: {
total: function(){
// 计算图书的总数
return this.books.length;
}
},
watch: {
name: function(val) {
// 验证图书名称是否已经存在
var flag = this.books.some(function(item){
return item.name == val;
});
if(flag) {
// 图书名称存在
this.submitFlag = true;
}else{
// 图书名称不存在
this.submitFlag = false;
}
}
},
mounted: function(){
// 该生命周期钩子函数被触发的时候,模板已经可以使用
// 一般此时用于获取后台数据,然后把数据填充到模板
var data = [{
id: 1,
name: '三国演义',
date: 2525609975000
},{
id: 2,
name: '水浒传',
date: 2525609975000
},{
id: 3,
name: '红楼梦',
date: 2525609975000
},{
id: 4,
name: '西游记',
date: 2525609975000
}];
this.books = data;
}
});
</script>
</body>
</html>
常用特性应用场景
1 过滤器
- Vue.filter 定义一个全局过滤器
<tr :key='item.id' v-for='item in books'>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<!-- 1.3 调用过滤器 -->
<td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
<td>
<a href="" @click.prevent='toEdit(item.id)'>修改</a>
<span>|</span>
<a href="" @click.prevent='deleteBook(item.id)'>删除</a>
</td>
</tr>
<script>
#1.1 Vue.filter 定义一个全局过滤器
Vue.filter('format', function(value, arg) {
function dateFormat(date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
var v = map[t];
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v;
v = v.substr(v.length - 2);
}
return v;
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
}
return dateFormat(value, arg);
})
#1.2 提供的数据 包含一个时间戳 为毫秒数
[{
id: 1,
name: '三国演义',
date: 2525609975000
},{
id: 2,
name: '水浒传',
date: 2525609975000
},{
id: 3,
name: '红楼梦',
date: 2525609975000
},{
id: 4,
name: '西游记',
date: 2525609975000
}];
</script>
2 自定义指令
- 让表单自动获取焦点
- 通过Vue.directive 自定义指定
<!-- 2.2 通过v-自定义属性名 调用自定义指令 -->
<input type="text" id="id" v-model='id' :disabled="flag" v-focus>
<script>
# 2.1 通过Vue.directive 自定义指定
Vue.directive('focus', {
inserted: function (el) {
el.focus();
}
});
</script>
3 计算属性
- 通过计算属性计算图书的总数
- 图书的总数就是计算数组的长度
<div class="total">
<span>图书总数:</span>
<!-- 3.2 在页面上 展示出来 -->
<span>{{total}}</span>
</div>
<script type="text/javascript">
/*
计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
*/
var vm = new Vue({
data: {
flag: false,
submitFlag: false,
id: '',
name: '',
books: []
},
computed: {
total: function(){
// 3.1 计算图书的总数
return this.books.length;
}
},
});
</script>
生命周期
每日作业-Vue第02天
TODOS案例
-
题目描述
- 当在输入框中输入完内容的按回车键 当前内容展示到页面上
- 点击三角 实现全选和全不选功能
- 点击叉号实现删除功能
- 双击标题实现编辑功能
- 如 点击 SECTION 1 则 内容区域显示 对应 SECTION 1 的内容 同时当前 SECTION的字体颜色变成蓝色
-
训练目标
- 能够理解vue 中的数据渲染
- 能够理解 v-for, v-if , v-bind , v-click 的使用
- 能够理解 vue 中自定义指令、计算属性、数组变异方法
-
训练提示
-
提供的数据如下
- 1、引入todos的CSS样式 (HTML和 CSS 已经写好 我们需要改成Vue动态渲染的
<!-- HTML --> <section id="todoapp" class="todoapp"> <header class="header"> <h1>todos</h1> <input placeholder="What needs to be done?" class="new-todo"> </header> <section class="main"> <input id="toggle-all" type="checkbox" class="toggle-all"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <li class=""> <div class="view"><input type="checkbox" class="toggle"> <label>吃饭</label> <button class="destroy"></button> </div> <input class="edit"> </li> <li class=""> <div class="view"> <input type="checkbox" class="toggle"> <label>睡觉</label> <button class="destroy"></button> </div> <input class="edit"></li> <li class="completed"> <div class="view"> <input type="checkbox" class="toggle"> <label>打豆豆</label> <button class="destroy"> </button></div> <input class="edit"> </li> </ul> </section> <footer class="footer"> <span class="todo-count"> <strong>2</strong> item left</span> <ul class="filters"> <li><a href="#/" class="selected">All</a></li> <li><a href="#/active">Active</a></li> <li><a href="#/completed">Completed</a></li> </ul> <button class="clear-completed">Clear completed</button> </footer> </section> <!--- 2CSS --> <link rel="stylesheet" href="css/base.css"> <link rel="stylesheet" href="css/index.css"> <!--- 3、 提供的数据 --> <script> new Vue({ el: "#todoapp", data: { todos: [{ id: 1, title: '吃饭', completed: false }, { id: 2, title: '睡觉', completed: false }, { id: 3, title: '打豆豆', completed: true }] } }) </script>
-
-
操作步骤
- 1、 把数据渲染到页面上
- 根据completeed 的状态动态给li 绑定类名
- 未完成状态:不需要样式 完成状态: 类名为 completed 编辑状态:类名为 editing
- 如果completed 为 true 则给当前li 添加 completed
- 根据completeed 的状态动态给li 绑定类名
- 2、把类名是 new-todo 按回车键的时候 把输入框中的数据展示到页面上
-
- 获取文本框中用户输入的数据
-
- 判断数据是否非空 如果是空的,则什么都不做 如果不是空的,则添加到数组中
-
- 添加到数组中
-
- 清空文本框
-
- 3、 实现全选功能
- 3.1 当点击三角即类名为 toggle-all 的复选框的时候
- 如果当前三角高亮 即 复选框为选中状态 让当前所有的li 为 完成状态
- 通过双向绑定获取当前复选框的选中状态
- 否则为未完成状态
- 如果当前三角高亮 即 复选框为选中状态 让当前所有的li 为 完成状态
- 3.2 当点击单个li 里面的复选框的时候 如果当前复选框选中 则当前的状态为完成状态
- 通过双向绑定获取当前复选框选中状态 通过选中状态动态改变 completed 的值
- 3.3 如果当前所有的li 都处于完成状态 即 复选框都选中 则上面的 toggle-all 复选框选中 有一个没有选中则当前toggle-all 复选框 处于未选中状态
- 3.1 当点击三角即类名为 toggle-all 的复选框的时候
- 4、实现删除功能
- 给类名是 destroy 的按钮添加点击事件
- 点击当前按钮 删除当前按钮所在的 li
- 5 实现编辑功能
- 5.1 双击标题的时候 当前li 的类名添加 editing
- 5.1.1 给当前标题添加双击事件
- 5.1.2 给当前li 添加editing 添加editing后 当前隐藏的输入框会显示出来
- 5.2 输入框的默认值为当前标题
- 5.3 当用户没有编辑 的时候 按esc退出的时候 数据不发生变化
- 5.4 当用户输入内容按回车键的时候 把标题更新
- 5.5当用户失去焦点的 时候 把输入框中的标题更新
- 5.1 双击标题的时候 当前li 的类名添加 editing
- 6 Clear completed
- 点击Clear completed 的时候删除所有的 已完成项
- 7 number item left
- 通过计算属性检测当前complete未完成的状态
- 1、 把数据渲染到页面上
每日作业-Vue第02天参考答案
todos案例
1 提供的数据和 HTML结构
- 引入todos的CSS样式 (HTML和 CSS 已经写好 我们需要改成Vue动态渲染的)
<!-- HTML -->
<section id="todoapp" class="todoapp">
<header class="header">
<h1>todos</h1>
<input placeholder="What needs to be done?" class="new-todo">
</header>
<section class="main">
<input id="toggle-all" type="checkbox" class="toggle-all">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li class="">
<div class="view"><input type="checkbox" class="toggle">
<label>吃饭</label>
<button class="destroy"></button>
</div>
<input class="edit">
</li>
<li class="">
<div class="view">
<input type="checkbox" class="toggle">
<label>睡觉</label>
<button class="destroy"></button>
</div> <input class="edit"></li>
<li class="completed">
<div class="view">
<input type="checkbox" class="toggle">
<label>打豆豆</label>
<button class="destroy">
</button></div>
<input class="edit">
</li>
</ul>
</section>
<footer class="footer">
<span class="todo-count">
<strong>2</strong> item left</span>
<ul class="filters">
<li><a href="#/" class="selected">All</a></li>
<li><a href="#/active">Active</a></li>
<li><a href="#/completed">Completed</a></li>
</ul>
<button class="clear-completed">Clear completed</button>
</footer>
</section>
<!--- 2CSS -->
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/index.css">
<!--- 3、 提供的数据 -->
<script>
new Vue({
el: "#todoapp",
data: {
todos: [{
id: 1,
title: '吃饭',
completed: false
}, {
id: 2,
title: '睡觉',
completed: false
}, {
id: 3,
title: '打豆豆',
completed: true
}]
}
})
</script>
2 把数据渲染到页面上
- 根据completeed 的状态动态给li 绑定类名
- 未完成状态:不需要样式 完成状态: 类名为 completed 编辑状态:类名为 editing
- 如果completed 为 true 则给当前li 添加 completed
<li v-for="(item, index) in todos"
v-bind:class="{completed: item.completed}"
>
<div class="view">
<input type="checkbox" class="toggle">
<label>{{item.title}}</label> <button class="destroy"></button>
</div>
<input class="edit">
</li>
3 把类名是 new-todo 按回车键的时候 把输入框中的数据展示到页面上
-
- 获取文本框中用户输入的数据
-
- 判断数据是否非空 如果是空的,则什么都不做 如果不是空的,则添加到数组中
-
- 添加到数组中
-
- 清空文本框
<header class="header">
<h1>todos</h1>
<!--
@keydown="addTodo" 麻烦,还需要自己来判断 keyCode
@keyup.13="addTodo" 按键修饰符
@keyup.enter="addTodo" 修饰符别名,推荐用法
-->
<input class="new-todo" placeholder="What needs to be done?" @keyup.enter="addTodo" >
</header>
<script>
new Vue({
el: "#todoapp",
data: {
todos: [{
id: 1,
title: '吃饭',
completed: false
}, {
id: 2,
title: '睡觉',
completed: false
}, {
id: 3,
title: '打豆豆',
completed: true
}]
},
methods:{
addTodo(event) {
// 1. 获取文本框中用户输入的数据
var todoText = event.target.value.trim()
// 2. 判断数据是否非空
if (!todoText.length) {
return
}
// 3. 添加到数组中
const lastTodo = this.todos[this.todos.length - 1]
const id = lastTodo ? lastTodo.id + 1 : 1
// 3.1 当数组发生变化,则绑定渲染该数组的视图也会得到更新
this.todos.push({
id,
title: todoText,
completed: false
})
// 4. 清空文本框
event.target.value = ''
},
}
}
})
</script>
4. 实现全选功能
- 4.1 当点击三角即类名为 toggle-all 的复选框的时候
- 如果当前三角高亮 即 复选框为选中状态 让当前所有的li 为 完成状态
- 通过双向绑定获取当前复选框的选中状态
- 否则为未完成状态
- 如果当前三角高亮 即 复选框为选中状态 让当前所有的li 为 完成状态
- 4.2 当点击单个li 里面的复选框的时候 如果当前复选框选中 则当前的状态为完成状态
- 通过双向绑定获取当前复选框选中状态 通过选中状态动态改变 completed 的值
- 4.3 如果当前所有的li 都处于完成状态 即 复选框都选中 则上面的 toggle-all 复选框选中 有一个没有选中则当前toggle-all 复选框 处于未选中状态
<section class="main">
<!-- 4.1 通过双向绑定获取当前复选框的选中状态 --->
<input v-model="toggleStat"
id="toggle-all"
type="checkbox" class="toggle-all">
<ul class="todo-list">
<li v-for="(item, index) in todos"
v-bind:class="{completed: item.completed}">
<div class="view">
<!-- 4.2 当点击单个li 里面的复选框的时候
如果当前复选框选中 则当前的状态为完成状态
4.2.1 通过双向绑定获取当前复选框选中状态
通过选中状态动态改变 completed 的值
--->
<input type="checkbox" class="toggle" v-model="item.completed">
<label>{{item.title}}</label>
<button class="destroy"></button>
</div>
<input class="edit">
</li>
</ul>
</section>
<script>
new Vue({
el: "#todoapp",
methods: {
addTodo(event) {
},
// 删除任务项
removeTodo(delIndex, event) {
this.todos.splice(delIndex, 1)
},
},
computed: {
toggleStat: {
/*
当读取一个变量的时候会触发该变量的getter
当修改该变量时候会触发他的setter.
*/
get() {
// 4.3 4.3 如果当前所有的li 都处于完成状态 即 复选框都选中 则上面的 toggle-all 复选框选中 有一个没有选中则当前toggle-all 复选框 处于未选中状态
return this.todos.every(item => item.completed)
},
// 当复选框为选中的时候当前 传入的为 true 没有选中传入的为false
set(val) {
this.todos.forEach(todo => todo.completed = val)
}
}
}
})
</script>
5 实现删除功能
- 给类名是 destroy 的按钮添加点击事件
- 点击当前按钮 删除当前按钮所在的 li
<section class="main">
<input v-model="toggleStat" id="toggle-all" type="checkbox"
class="toggle-all">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="(item, index) in todos"
v-bind:class="{completed: item.completed}">
<div class="view">
<input type="checkbox" class="toggle" v-model="item.completed">
<label>{{item.title}}</label>
<!-- 5.1 给按钮按钮添加点击事件 -->
<button class="destroy"
@click="removeTodo(index)">
</button>
</div>
<input class="edit">
</li>
</ul>
</section>
<script>
new Vue({
el: "#todoapp",
methods: {
addTodo(event) {
},
// 删除任务项
removeTodo(delIndex) {
this.todos.splice(delIndex, 1)
},
},
})
</script>
6 实现编辑功能
-
6.1 双击标题的时候 当前li 的类名添加 editing
- 6.1.1 给当前标题添加双击事件
- 6.1.2 给当前li 添加editing 添加editing后 当前隐藏的输入框会显示出来
-
6.2 输入框的默认值为当前标题
-
6.3 当用户没有编辑 的时候 按esc退出的时候 数据不发生变化
-
6.4 当用户输入内容按回车键的时候 把标题更新
-
- 5当用户失去焦点的 时候 把输入框中的标题更新
<section class="main">
<input v-model="toggleStat" id="toggle-all" type="checkbox"
class="toggle-all">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<!--
6.1.1 点击的时候 通过一个标识符 来控制是否给当前li 添加 类名
-->
<li v-for="(item, index) in todos"
v-bind:class="{completed: item.completed, editing: item === currentEditing}">
<div class="view">
<input type="checkbox" class="toggle" v-model="item.completed">
<!--
6.1.1 给当前标题添加双击事件
点击的时候 通过一个标识符 来控制是否给当前li 添加 类名
-->
<label
@dblclick="currentEditing = item"
>{{item.title}}</label>
<button class="destroy"
@click="removeTodo(index)">
</button>
</div>
<!-- 6.2 输入框的默认值为当前标题
6.3 当用户没有编辑 的时候 按esc退出的时候 数据不发生变化
6.4 当用户输入内容按回车键的时候 把标题更新
6.5当用户失去焦点的 时候 把输入框中的标题更新
-->
<input class="edit"
:value="item.title"
@keyup.esc="currentEditing = null"
@keyup.enter="saveEdit(item, index, $event)"
@blur="saveEdit(item, index, $event)"
>
</li>
</ul>
</section>
<script>
new Vue({
el: "#todoapp",
data: {
// 6.1.2 标识符默认的为空即一开始加载的时候类名 editing 不加载
currentEditing: null,
},
methods:{
// 保存编辑项
saveEdit(item, index, event) {
// 1. 拿到文本框中的数据
// 非空校验
// 如果为空,则直接删除这个 item
// 如果不为空,则修改任务项的 title 数据
var editText = event.target.value.trim()
// 程序员要具有工匠精神:优化简写
// !editText.length ?
// this.todos.splice(index, 1) :
// item.title = editText
if (!editText.length) {
// 将元素直接从数组中移除
return this.todos.splice(index, 1)
}
// 2. 将数据设置到任务项中
item.title = editText
// 3. 去除 editing 样式
this.currentEditing = null
},
}
})
</script>
7 Clear completed
- 点击Clear completed 的时候删除所有的 已完成项
<footer class="footer">
<button
class="clear-completed"
@click="removeAllDone">Clear completed</button>
</footer>
<script>
new Vue({
el: "#todoapp",
data: {
// 6.1.2 标识符默认的为空即一开始加载的时候类名 editing 不加载
currentEditing: null,
},
methods:{
// 删除所有已完成任务项
removeAllDone() {
// 找到所有已完成的任务项,把其删除。错误的写法
// this.todos.forEach((item, index) => {
// if (item.completed) {
// // 已完成
// console.log(item.title)
// this.todos.splice(index, 1)
// }
// })
// 把所有需要保留的数据过滤出来,然后重新赋值给 todos
this.todos = this.todos.filter(item => !item.completed)
// 如果想要就在遍历的过程去删除,则可以使用 for 循环
// 没删除一个,我们可以控制让索引 --
// for (let i = 0; i < this.todos.length; i++) {
// if (this.todos[i].completed) {
// this.todos.splice(i, 1)
// i--
// }
// }
},
}
})
</script>
8 number item left
- 通过计算属性检测当前complete未完成的状态
<span class="todo-count"><strong>{{leftCount}}</strong> item left</span>
<script>
new Vue({
computed: {
leftCount: function() {
return this.todos.filter(item => !item.completed).length
}
}
})
</script>
完整代码
基础版
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<section id="todoapp" class="todoapp">
<header class="header">
<h1>todos</h1>
<input placeholder="What needs to be done?" @keyup.enter="addTodo" class="new-todo">
</header>
<section class="main">
<input v-model="toggleStat" id="toggle-all" type="checkbox" class="toggle-all">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="(item, index) in todos" v-bind:class="{completed: item.completed, editing: item === currentEditing}">
<div class="view">
<input type="checkbox" class="toggle" v-model="item.completed">
<label @dblclick="currentEditing = item">{{item.title}}</label>
<button class="destroy" @click="removeTodo(index, $event)"></button>
</div>
<input class="edit" @keyup.enter="saveEdit(item, index, $event)" :value="item.title" @keyup.esc="currentEditing = null">
</li>
</ul>
</section>
<footer class="footer">
<span class="todo-count"><strong>{{leftCount}}</strong> item left</span>
<ul class="filters">
<li><a href="#/" class="selected">All</a></li>
<li><a href="#/active" class="">Active</a></li>
<li><a href="#/completed" class="">Completed</a></li>
</ul> <button class="clear-completed">Clear completed</button></footer>
</section>
<script src="js/vue.js"></script>
<script>
new Vue({
el: "#todoapp",
data: {
currentEditing: null,
todos: [{
id: 1,
title: '吃饭',
completed: false
}, {
id: 2,
title: '睡觉',
completed: false
}, {
id: 3,
title: '打豆豆',
completed: true
}]
},
methods: {
addTodo(event) {
// 1. 获取文本框中用户输入的数据
// 2. 判断数据是否非空
// 如果是空的,则什么都不做
// 如果不是空的,则添加到数组中
// 3. 添加到数组中
// 4. 清空文本框
var todoText = event.target.value.trim()
if (!todoText.length) {
return
}
const lastTodo = this.todos[this.todos.length - 1]
const id = lastTodo ? lastTodo.id + 1 : 1
// 当数组发生变化,则绑定渲染该数组的视图也会得到更新
this.todos.push({
id,
title: todoText,
completed: false
})
// 清空文本框
event.target.value = ''
},
// 删除任务项
removeTodo(delIndex) {
this.todos.splice(delIndex, 1)
},
// 保存编辑项
saveEdit(item, index, event) {
// 1. 拿到文本框中的数据
// 非空校验
// 如果为空,则直接删除这个 item
// 如果不为空,则修改任务项的 title 数据
var editText = event.target.value.trim()
// 程序员要具有工匠精神:优化简写
// !editText.length ?
// this.todos.splice(index, 1) :
// item.title = editText
if (!editText.length) {
// 将元素直接从数组中移除
return this.todos.splice(index, 1)
}
// 2. 将数据设置到任务项中
item.title = editText
// 3. 去除 editing 样式
this.currentEditing = null
},
},
computed: {
toggleStat: {
get() {
console.log(2345)
return this.todos.every(item => item.completed)
},
set(val) {
console.log(678)
console.log(val)
this.todos.forEach(todo => todo.completed = val)
}
},
leftCount: function() {
return this.todos.filter(item => !item.completed).length
},
}
})
</script>
</body>
</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="css/base.css">
<link rel="stylesheet" href="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>todos</h1>
<!--
@keydown="addTodo" 麻烦,还需要自己来判断 keyCode
@keyup.13="addTodo" 按键修饰符
@keyup.enter="addTodo" 修饰符别名,推荐用法
-->
<input
class="new-todo"
placeholder="What needs to be done?"
@keyup.enter="addTodo"
v-focus>
</header>
<!--
当需要按条件控制渲染多个元素的时候,可以把他们都放到 template 这个特殊的标签中。
Vue 会识别的它,在渲染的结果中不会包含 template 这个节点
-->
<template v-if="todos.length">
<!-- This section should be hidden by default and shown when there are todos -->
<section class="main">
<input
id="toggle-all"
class="toggle-all"
type="checkbox"
v-model="toggleStat">
<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 -->
<!--
未完成状态:不需要样式
完成状态:completed
编辑状态:editing
v-bind:class="{类名: 布尔值}"
当布尔值为 true 的时候,则作用这个类名
当布尔值为 false 的时候,则去除这个类名
任务项双击获得 editing 样式:
这里使用一个中间变量,默认为 null ,也就是所有任务项都没有 editing 样式
那 editing 的样式取决于:currentEditing 中间变量是否等价于当前任务项
所以,当我双击的时候,我就手动把 currentEditing = 当前我双击的任务项
那这个时候,样式判定条件 item === currentEditing 就满足了,满足就作用了这个样式。
-->
<li
v-bind:class="{completed: item.completed, editing: item === currentEditing}"
v-for="(item, index) of filterTodos">
<div class="view">
<input
class="toggle"
type="checkbox"
v-model="item.completed">
<label
@dblclick="currentEditing = item">{{ item.title }}</label>
<button
class="destroy"
@click="removeTodo(index, $event)"></button>
</div>
<input
class="edit"
:value="item.title"
@keyup.esc="currentEditing = null"
@keyup.enter="saveEdit(item, index, $event)"
@blur="saveEdit(item, index, $event)"
v-editing-focus="item === currentEditing">
</li>
</ul>
</section>
<!-- This footer should hidden by default and shown when there are todos -->
<footer class="footer">
<!-- This should be `0 items left` by default -->
<!--
在模板绑定中也可以调用函数,函数的返回值将被渲染到这里。
同样的,如果函数中依赖的 data 成员一旦发生变化,则函数会重新计算得到最新结果。
也就是说这也是响应式的。
严谨一点的说法:绑定函数所在的视图模板如果更新,则该函数也会重新执行。
计算属性和方法(返回结果用于模板绑定):
都可以达到同样的效果。
- 如果是方法,则一旦方法所在的视图发生变化,则方法一定会重新执行
- 方法没有缓存,也就是说多次使用该方法,则会重复执行多次
- 计算属性是真正的依赖于内部 data 中的数据,如果数据没变,则计算属性不会重新执行
- 所以相比来说,计算属性要比方法更为高效一些。
-->
<span class="todo-count"><strong>{{ leftCount }}</strong> item left</span>
<!-- Remove this if you don't implement routing -->
<ul class="filters" v-auto-active>
<li>
<a :class="{selected: filterStat === 'all'}" href="#/">All</a>
</li>
<li>
<a :class="{selected: filterStat === 'active'}" href="#/active">Active</a>
</li>
<li>
<a :class="{selected: filterStat === 'completed'}" href="#/completed">Completed</a>
</li>
</ul>
<!-- Hidden if no completed items are left ↓ -->
<button
class="clear-completed"
@click="removeAllDone">Clear completed</button>
</footer>
</template>
</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/todomvc-common/base.js"></script> -->
<script src="js/vue.js"></script>
<script src="js/app.js"></script>
</body>
</html>
base.css
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
index.css
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
text-align: center;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
}
.toggle-all + label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all + label:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked + label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
app.js
/**
* Vue
* MVVM 框架
* 这个思想的哲学就是:数据驱动视图
* 把需要改变视图的数据都初始化到 Vue 中
* 然后我们就可以通过修改 Vue 中的数据,从而实现对视图的更新
*/
;
((Vue) => {
const todos = [{
id: 1,
title: '吃饭',
completed: false
},
{
id: 2,
title: '睡觉',
completed: false
},
{
id: 3,
title: '打豆豆',
completed: true
}
]
// 什么时候需要封装自定义指令:
// 当你需要进行一些底层 DOM 操作的时候,使用自定义指令更为方便
// 注册一个全局自定义指令 `v-focus`
// 第一个参数:指令名称,focus
// 使用的时候,必须加上 v- 前缀来使用
// 后面会解释为什么一个简简单单的自动获得焦点都需要封装一个自定义指令
// Vue 带来便利的同时也会有一些让人头疼的问题,所以在软件开发中,有这么一句话:没有银弹
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
// el.focus()
// el 就是作用了 v-focus 的 DOM 元素
console.log(el)
el.focus()
}
})
// 在 Vue 中,不要出现这样的代码
// document.getElementById('xx')
Vue.directive('auto-active', {
// 当被绑定的元素插入到 DOM 中时……
// 这里就是纯原生 DOM 操作
inserted: function (el) {
// 聚焦元素
// el.focus()
// el 就是作用了 v-focus 的 DOM 元素
// ul a
// 给 a 注册点击事件
var links = el.getElementsByTagName('a')
Array.from(links).forEach(function (link) {
link.onclick = function () {
console.log(this)
}
})
}
})
window.app = new Vue({
el: '#todoapp',
data: {
// EcmaScript 6 对象属性简写方式
// 当 key 和 值的名字一样的时候,可以省略简写
// 下面的情况等价于 todos: todos
todos,
filterStat: 'all',
currentEditing: null,
toggleAllStat: true,
// 这里不要使用 this 因为 this 指向的是 window
// 所以这里无法实现你想要的结果
// leftCount: this.todos.filter(item => !item.completed).length
},
computed: {
// 该属性比较特殊,从代码来看是一个方法,但是只能当做属性来使用
// 也就是说,在模板中不能调用它,只能当做属性来使用它
leftCount: function () {
return this.todos.filter(item => !item.completed).length
},
filterTodos: function () {
switch (this.filterStat) {
case 'active':
return this.todos.filter(item => !item.completed)
break
case 'completed':
return this.todos.filter(item => item.completed)
break
default:
return this.todos
break
}
},
// 使用计算属性的方式处理全选的联动效果
toggleStat: {
get() {
return this.todos.every(item => item.completed)
},
set (val) {
this.todos.forEach(todo => todo.completed = val)
}
}
},
methods: {
// EcmaScript 6 对象属性函数的简写方式
// 等价于:addTodo: function () {}
// 它没有任何特殊的特性,仅仅是简写了而已
addTodo(event) {
// 1. 获取文本框中用户输入的数据
// 2. 判断数据是否非空
// 如果是空的,则什么都不做
// 如果不是空的,则添加到数组中
// 3. 添加到数组中
// 4. 清空文本框
var todoText = event.target.value.trim()
if (!todoText.length) {
return
}
const lastTodo = this.todos[this.todos.length - 1]
const id = lastTodo ? lastTodo.id + 1 : 1
// 当数组发生变化,则绑定渲染该数组的视图也会得到更新
this.todos.push({
id,
title: todoText,
completed: false
})
// 清空文本框
event.target.value = ''
},
toggleAll(event) {
// 获取点击的 checkbox 的选中状态
var checked = event.target.checked
// 遍历数组中的所有元素,把每个元素的 completed 都设置为当前点击的 checkbox 的状态
// 由于我们使用 v-model 为每一个任务项的完成状态都绑定了 completed
// 所以当数据发生变化的时候,任务项的完成状态也会跟着变化
this.todos.forEach(todo => todo.completed = checked)
},
// 删除任务项
removeTodo(delIndex, event) {
this.todos.splice(delIndex, 1)
},
// 删除所有已完成任务项
removeAllDone() {
// 找到所有已完成的任务项,把其删除。错误的写法
// this.todos.forEach((item, index) => {
// if (item.completed) {
// // 已完成
// console.log(item.title)
// this.todos.splice(index, 1)
// }
// })
// 把所有需要保留的数据过滤出来,然后重新赋值给 todos
this.todos = this.todos.filter(item => !item.completed)
// 如果想要就在遍历的过程去删除,则可以使用 for 循环
// 没删除一个,我们可以控制让索引 --
// for (let i = 0; i < this.todos.length; i++) {
// if (this.todos[i].completed) {
// this.todos.splice(i, 1)
// i--
// }
// }
},
// 方法也可以用于模板绑定
// 在模板中调用方法,方法的返回值将会渲染到绑定的位置
getLeftCount() {
console.log('方法被调用了')
return this.todos.filter(item => !item.completed).length
},
// 保存编辑项
saveEdit(item, index, event) {
// 1. 拿到文本框中的数据
// 非空校验
// 如果为空,则直接删除这个 item
// 如果不为空,则修改任务项的 title 数据
var editText = event.target.value.trim()
// 程序员要具有工匠精神:优化简写
// !editText.length ?
// this.todos.splice(index, 1) :
// item.title = editText
if (!editText.length) {
// 将元素直接从数组中移除
return this.todos.splice(index, 1)
}
// 2. 将数据设置到任务项中
item.title = editText
// 3. 去除 editing 样式
this.currentEditing = null
},
// 状态切换(先不要使用)
// toggle (item, event) {
// // console.log(item.completed) // false
// // 之前这里没有这个问题,应该 Vue 更新了新的机制了
// // 声明周期、模板更新的问题
// // 这里的解决方案就是把你的代码放到 Vue.nextTick() 的回调函数中
// Vue.nextTick(() => {
// this.toggleAllStat = this.todos.every(item => item.completed)
// })
// }
},
// 配置局部自定义指令
directives: {
// 当作用了该指令的元素所在模板发生更新的时候,则这个 update 钩子会自动调用
editingFocus: {
// 在指令钩子中,函数内部的 this 是 window
update(el, binding) {
if (binding.value) {
el.focus()
}
}
}
}
})
window.onhashchange = function () {
// 得到点击的路由 hash
var hash = window.location.hash.substr(2) || 'all'
// 设置到程序中的过滤状态
// 过滤状态一旦改变,则计算属性会感知到这个 filterStat 变了
// 当它感知到 filterStat 变了之后,就会重新计算执行
// 当 filterTodos 重新计算执行之后,数据得到了更新,则自动同步更新到视图中
window.app.filterStat = hash
}
// 页面第一次进来,执行一次
window.onhashchange()
})(Vue)