一步一步学Vue(二)

  接上篇,在本篇中,咱们将要实现以下,功能,编辑和查询,咱们当前的todolist程序,和线上其它的demo程序不一样,咱们会对其进行增删改查的基本操做,以后进行进一步的完善,按照常规的系统使用经验,通常咱们新增和编辑都是在模态框中处理,这里咱们不会去构建复杂的模态框,只用一个简单的div层来代替,后期接下来的文章中咱们会重复造轮子,构建咱们本身的轻量级框架(UI库)。css

  首先,咱们对咱们的页面结构进行一下简单的调整,加入bootstrap只是为了让页面不那么赤裸裸,对其它不会有任何影响html

<!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>demo1</title>
    <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">

</head>

<body class="container">
    <div id="app" class='row'>
        <div class="col-md-6">
            <table class="table table-bordered">
                <tr>
                    <th>title</th>
                    <th>desc</th>
                    <th></th>
                </tr>
                <tr v-for="(todoItem,index) in todolist">
                    <td>{{todoItem.title}}</td>
                    <td>{{todoItem.desc}}</td>
                    <td><input type="button" value="remove" @click="remove(index)" class="btn btn-danger" /></td>
                </tr>
            </table>
        </div>
        <div class="col-md-6">

            <div class="form-inline">
                <label for="title" class="control-label col-md-4">title:</label>
                <input type="text" v-model="title" class="form-control col-md-8">
            </div>
            <div class="form-inline">
                <label for="desc" class="control-label col-md-4">desc</label>
                <input type="text" v-model="desc" class="form-control col-md-8">
            </div>
            <div class="form-inline">
                <input type="button" value="OK" v-on:click="addItem()" class="btn btn-primary offset-md-10" />

            </div>

        </div>


    </div>
    <script>
        var TodoItem = function (title, desc) {
            this.title = title;
            this.desc = desc;
        }
        new Vue({
            el: '#app',
            data: {
                todolist: [],
                title: '',
                desc: ''
            },
            methods: {
                addItem: function () {
                    this.todolist.push(new TodoItem(this.title, this.desc))

                    this.title = this.desc = '';

                },
                remove: function (index) {
                    this.todolist.splice(index, 1);
                }

            }
        })
    </script>
</body>

</html>

js没有任何变化,只是引入了bootstrap4以后,对html结构进行了微调整,因为咱们须要增长编辑操做,咱们把增长编辑操做概括为如下几个步骤:vue

一、增长编辑按钮;ios

二、点击编辑按钮绑定所对应todoitem到表单进行编辑bootstrap

三、点击表单中OK按钮,对编辑结果进行应用。axios

注意:这里须要区分,在点击OK按钮时,进行的是新增操做仍是编辑操做,咱们对咱们数据结构加入自增ID来标示,若是编辑项目有ID,则为保存编辑操做,若是不存在ID则为新增保存操做,对咱们的数据结构进行如下微调,因为新增了ID项目,那么在data属性中也要增长ID属性,这样每次新增属性都要直接修改data属性,这就是变化点,下面咱们对变化点进行简单封装,修改代码以下:后端

 data: {
                todolist: [],
                todoItem:{
                    id:'',
                    title:'',
                    desc:''
                }
            },

另外咱们须要实现自增ID,这里采用最直接的方式,全局ID,使其自增便可,对TodoItem进行简单的闭包处理:缓存

  var TodoItem = (function () {
            var id = 1;
            return function (title, desc) {
                this.title = title;
                this.desc = desc;

                this.id = id++;
            }
        })();

为了适应新数据结构的变化,则其它修改部分总体贴出来,变化部分见黄色:数据结构

<!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>demo1</title>
    <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">

</head>

<body class="container">
    <div id="app" class='row'>
        <div class="col-md-6">
            <table class="table table-bordered">
                <tr>
                    <th></th>
                    <th>title</th>
                    <th>desc</th>
                    <th></th>
                </tr>
                <tr v-for="(todoItem,index) in todolist">
                    <th>{{todoItem.id}}</th>
                    <td>{{todoItem.title}}</td>
                    <td>{{todoItem.desc}}</td>
                    <td><input type="button" value="remove" @click="remove(index)" class="btn btn-danger" /></td>
                </tr>
            </table>
        </div>
        <div class="col-md-6">

            <div class="form-inline">
                <label for="title" class="control-label col-md-4">title:</label>
                <input type="hidden" v-bind:value="todoItem.id" />
                <input type="text" v-model="todoItem.title" class="form-control col-md-8">
            </div>
            <div class="form-inline">
                <label for="desc" class="control-label col-md-4">desc</label>
                <input type="text" v-model="todoItem.desc" class="form-control col-md-8">
            </div>
            <div class="form-inline">
                <input type="button" value="OK" v-on:click="addItem()" class="btn btn-primary offset-md-10" />

            </div>

        </div>


    </div>
    <script>
        var TodoItem = (function () {
            var id = 1;
            return function (title, desc) {
                this.title = title;
                this.desc = desc;

                this.id = id++;
            }
        })();
        new Vue({
            el: '#app',
            data: {
                todolist: [],
                todoItem: {
                    id: '',
                    title: '',
                    desc: ''
                }
            },
            methods: {
                addItem: function () {
                    // this.todolist.push(new TodoItem(this.title, this.desc))
                    this.todolist.push( new TodoItem( this.todoItem.title, this.todoItem.desc ) ); this.todoItem={};

                },
                remove: function (index) {
                    this.todolist.splice(index, 1);
                }

            }
        })
    </script>
</body>

</html>

刷新页面,测试效果以下:闭包

自增ID已经完成,那么添加编辑时绑定,按照上面的步骤,先添加编辑按钮,在删除按钮后添加编辑按钮,并在methods对象中增长对应的回调函数,对edit操做进行响应,函数中主要实现对todoItem对象进行绑定操做,具体代码修改以下:

    <table class="table table-bordered">
                <tr>
                    <th></th>
                    <th>title</th>
                    <th>desc</th>
                    <th></th>
                </tr>
                <tr v-for="(todoItem,index) in todolist">
                    <th>{{todoItem.id}}</th>
                    <td>{{todoItem.title}}</td>
                    <td>{{todoItem.desc}}</td>
                    <td>
                        <input type="button" value="remove" @click="remove(index)" class="btn btn-danger" />
                        <input type="button" value="edit" @click="edit(todoItem.id)" class="btn btn-info" />
                    </td>
                </tr>
            </table>
 methods: {
                edit: function (id) {
                    //找到id值等于所传参数的todoitem
                    var obj = this.todolist.filter(v => v.id === id)[0];
                    //对数据进行绑定,则数据会响应到表单上
                    this.todoItem = obj;
                },
            ...省略其它
}

这样有没有问题呢?咱们运行看一下效果:

从运行结果上看,咱们点击edit操做,的确完成了绑定,可是在咱们修改编辑,尚未点击OK按钮的状况下,表单中的变化已经提现到了列表中,这就不符合正常逻辑了,为何会有这样的状况呢,缘由就在于this.todoItem=obj;这句代码就是所谓的引用赋值,因此todoitem和obj指向的是同一个地址,则对this.todoItem的修改,会直接反应到obj上,也就是todoList中的这个元素上,因此在列表中会直接提现出来,避免这种状况的发生的方法,只要避免饮用赋值便可,因此对上述代码进行以下修改:

 //找到id值等于所传参数的todoitem
                    var obj = this.todolist.filter(v => v.id === id)[0];
                    //对数据进行绑定,则数据会响应到表单上
                    this.todoItem = {
                        id:obj.id,
                        title:obj.title,
                        desc:obj.desc
                    };

刷新运行,发生程序按照预期运行了,接下来,增长更新保存操做,修改OK按钮的事件绑定方式为save,并经过id判断新增仍是修改操做:

 <div class="col-md-6">

            <div class="form-inline">
                <label for="title" class="control-label col-md-4">title:</label>
                <input type="hidden" v-bind:value="todoItem.id" />
                <input type="text" v-model="todoItem.title" class="form-control col-md-8">
            </div>
            <div class="form-inline">
                <label for="desc" class="control-label col-md-4">desc</label>
                <input type="text" v-model="todoItem.desc" class="form-control col-md-8">
            </div>
            <div class="form-inline">
                <input type="button" value="OK" v-on:click="save()" class="btn btn-primary offset-md-10" />
            </div>
        </div>
 methods: {
                edit: function (id) {
                    //找到id值等于所传参数的todoitem
                    var obj = this.todolist.filter(v => v.id === id)[0];
                    //对数据进行绑定,则数据会响应到表单上
                    this.todoItem = {
                        id: obj.id,
                        title: obj.title,
                        desc: obj.desc
                    };
                },
                save: function () {
                    if (this.todoItem.id) {
                        //编辑保存
                        var obj = this.todolist.filter(v => v.id === this.todoItem.id)[0];
                        obj.title = this.todoItem.title;
                        obj.desc = this.todoItem.desc;

                    } else {
                        //新增保存
                        this.todolist.push(
                            new TodoItem(
                                this.todoItem.title,
                                this.todoItem.desc
                            )
                        );
                    }
                    //重置表单  这部分笔误,在实际代码中已修改,可是贴上来的代码没有作修改,具体见最下面代码,错误缘由见下方回复
                    this.todoItem = {};
                },
                
                remove: function (index) {
                    this.todolist.splice(index, 1);
                }

            }

代码比较简单,这里就再也不赘述,能够看一下运行效果:

为了逼格更高一点,这里我再介绍一个指令,其实上面已经使用了,v-bind ,这个指令和v-on是相似的,二者的区别在于后面的参数不一样,通常v-bind用来传递属性参数,通常使用缩写形式:attr,能够绑定自定义属性,上面代码中咱们对Id值的绑定已经使用了v-bind:value="todoItem.id",这里至关于angular中ng-bind。基于对v-bind的了解,咱们能够推理,给v-bind添加disable的属性,使OK按钮只有再title不为空的前提下再可用。

<div class="form-inline">
                <input type="button" value="OK" v-on:click="save()" class="btn btn-primary offset-md-10" :disabled='!todoItem.title'/>
            </div>

刷新运行:

上面代码能很好的运行,可是如今若是我须要修改一下验证规则,在title和desc都不为空的状况下,才使用OK按钮可用,如何作?你固然会说,很简单,直接加入一个&&条件不就好了,可是问题在于,如今个人模型比较小,属性比较少,若是我存在一个大量属性的对象,作相似的验证,这样来修改代码就是一个坑了,说到这里,其实已经能够想到,既然验证规则再变,那么能够考虑做为一个变化点封装起来,最直观的方式,是封装为一个方法,可是vue提供了更好的方式:computed,计算属性,这个计算属性应该是来自于knockout的概念,有兴趣的能够看一下knockout中计算属性,修改代码以下:

 new Vue({
            el: '#app',
            data: {
                todolist: [],
                todoItem: {
                    id: '',
                    title: '',
                    desc: ''
                }
            },
            computed:{ canSave:function(){ return !this.todoItem.title || !this.todoItem.desc; } },
           ...省略其它
            }
        })

能够看到computed属性是以方法的方式来定义的,这里是最简单方式,只读的方式,固然还能够经过get set方式进行读写控制,咱们后期的代码中可能会见到,若是捉急,能够去查看官方文档。在computed使用的时候,和方法调用大相径庭,而是和data属性中代理的模式同样,若是你使用过knockout,那么你对这种用法就见怪不怪了,html部分修改以下:

<div class="form-inline">
                <input type="button" value="OK" v-on:click="save()" class="btn btn-primary offset-md-10" :disabled='canSave'/>
            </div>

刷新页面,运行效果如图:

ok,编辑这部份内容就到这里吧,在没有后端接口的前提下,保存操做都是模拟的,接下来对咱们的查询进行一下简单的介绍,这里查询,因为没有后端接口,我们只作最简单的演示,对代码作以下处理:

一、增长查询输入框,keyword,添加查询按钮

二、点击查询操做,预期结果:根据输入的查询关键字,过滤列表

按照上面思路对咱们代码进行修改:

 <div class="row toolbar">
        <div class="col-md-8">
            keyword:
            <input type="text" v-model="keyword" />
            <input type="button" @click="query()" value="search" />
        </div>
    </div>

data中增长keyword属性,以实现对输入框的绑定,在methods中添加query方法:

 //全局变量,用来缓存全部数据
        var list = [];
 data: {
                todolist: [],
                todoItem: {
                    id: '',
                    title: '',
                    desc: ''
                },
                keyword:''
            },

 

query: function () {
                    //过滤title中不包含keyword的数据
                    //这里必须经过list全局变量过滤,而不能经过this.todolist,由于须要给this.todolist赋值,赋值后没法还原原来的列表。
                    this.todolist = list.filter(v => v.title.indexOf(this.keyword) !== -1);
                }

刷新页面运行效果如图:

代码部分注释中已经写的很清楚了,有疑问可提价comment。本章就到这里,文章有点水,在(三)中会加入添加服务端支持,并使用axios和服务端rest接口进行交互敬请期待。。。准备睡觉。。😴。

<!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>demo1</title>
    <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">

</head>

<body class="container">
    <div id="app">
        <div class="row toolbar">
            <div class="col-md-8">
                keyword:
                <input type="text" v-model="keyword" />
                <input type="button" @click="query()" value="search" />
            </div>
        </div>
        <div class='row'>

            <div class="col-md-6">
                <table class="table table-bordered">
                    <tr>
                        <th></th>
                        <th>title</th>
                        <th>desc</th>
                        <th></th>
                    </tr>
                    <tr v-for="(todoItem,index) in todolist">
                        <th>{{todoItem.id}}</th>
                        <td>{{todoItem.title}}</td>
                        <td>{{todoItem.desc}}</td>
                        <td>
                            <input type="button" value="remove" @click="remove(index)" class="btn btn-danger" />
                            <input type="button" value="edit" @click="edit(todoItem.id)" class="btn btn-info" />
                        </td>
                    </tr>
                </table>
            </div>
            <div class="col-md-6">

                <div class="form-inline">
                    <label for="title" class="control-label col-md-4">title:</label>
                    <input type="hidden" v-bind:value="todoItem.id" />
                    <input type="text" v-model="todoItem.title" class="form-control col-md-8">
                </div>
                <div class="form-inline">
                    <label for="desc" class="control-label col-md-4">desc</label>
                    <input type="text" v-model="todoItem.desc" class="form-control col-md-8">
                </div>
                <div class="form-inline">
                    <input type="button" value="OK" v-on:click="save()" class="btn btn-primary offset-md-10" :disabled='canSave' />
                </div>
            </div>


        </div>
    </div>
    <script>
        var list = [];
        var TodoItem = (function () {
            var id = 1;
            return function (title, desc) {
                this.title = title;
                this.desc = desc;

                this.id = id++;
            }
        })();
        new Vue({
            el: '#app',
            data: {
                todolist: [],
                todoItem: {
                    id: '',
                    title: '',
                    desc: ''
                },
                keyword: ''
            }, computed: {
                canSave: function () {
                    return !this.todoItem.title || !this.todoItem.desc;
                }
            },
            methods: {
                query: function () {
                    //过滤title中不包含keyword的数据
                    //这里必须经过list全局变量过滤,而不能经过this.todolist,由于须要给this.todolist赋值,赋值后没法还原原来的列表。
                    this.todolist = list.filter(v => v.title.indexOf(this.keyword) !== -1);
                },
                edit: function (id) {
                    //找到id值等于所传参数的todoitem
                    var obj = this.todolist.filter(v => v.id === id)[0];
                    //对数据进行绑定,则数据会响应到表单上
                    this.todoItem = {
                        id: obj.id,
                        title: obj.title,
                        desc: obj.desc
                    };
                },
                save: function () {
                    if (this.todoItem.id) {
                        //编辑保存
                        var obj = this.todolist.filter(v => v.id === this.todoItem.id)[0];
                        obj.title = this.todoItem.title;
                        obj.desc = this.todoItem.desc;

                    } else {
                        //新增保存
                        this.todolist.push(
                            new TodoItem(
                                this.todoItem.title,
                                this.todoItem.desc
                            )
                        );
                    }
                    //重置表单
                    this.todoItem = { title: '', desc: '' };
                  
                    list = this.todolist;
                },

                remove: function (index) {
                    this.todolist.splice(index, 1);
                }

            }
        })
    </script>
</body>

</html>
View Code

 订正:

remove方法执行中,加入list=this.todolist;