JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(二)

前言:上篇 JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(一) 介绍了下knockout.js的一些基础用法,由于篇幅的关系,所以只能分成两篇,望见谅!昨天就觉得应该快点完成下篇,要不然有点标题党的感觉,思及此,博主心有不安,于是加班赶出了下篇。如果你也打算用ko去做项目,且看看吧!

一、效果预览

其实也没啥效果,就是简单的增删改查,重点还是在代码上面,使用ko能够大量节省界面DOM数据绑定的操作。下面是整个整个增删改查逻辑的js代码:

页面效果:

二、代码示例

好了,进入重点吧!博主打算分两块介绍,第一部分是表格初始化部分,第二部分是按钮操作增删改部分。

1、表格初始化

1.1、准备工作

首先看看需要引用的js和css文件

    <link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <link href="~/Content/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" />

    <script src="~/scripts/jquery-1.9.1.min.js"></script>
    <script src="~/Content/bootstrap/js/bootstrap.min.js"></script>
    <script src="~/Content/bootstrap-table/bootstrap-table.min.js"></script>
    <script src="~/Content/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script>

    <script src="~/scripts/knockout/knockout-3.4.0.min.js"></script>
    <script src="~/scripts/knockout/extensions/knockout.mapping-latest.js"></script>
    <script src="~/Content/bootstrap-table/knockout.bootstraptable.js"></script>
    <script src="~/scripts/Department.js"></script>

都是一些常用的css和js文件,我们自定义的js文件主要有两个: knockout.bootstraptable.js 和 Department.js 。上篇我们介绍过使用ko可以自定义我们的data-bind。同样,这里对于table的绑定,我们也定义一个自定义的绑定,代码 knockout.bootstraptable.js 里面。

//添加ko自定义绑定
ko.bindingHandlers.myBootstrapTable = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        //这里的oParam就是绑定的viewmodel
        var oViewModel = valueAccessor();
        var $ele = $(element).bootstrapTable(oViewModel.params);
        //给viewmodel添加bootstrapTable方法
        oViewModel.bootstrapTable = function () {
            return $ele.bootstrapTable.apply($ele, arguments);
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {}
};

//初始化
(function ($) {
    //向ko里面新增一个bootstrapTableViewModel方法
    ko.bootstrapTableViewModel = function (options) {
        var that = this;

        this.default = {
            search: true,                       //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大
            strictSearch: true,
            showColumns: true,                  //是否显示所有的列
            cache:false,
            showRefresh: true,                  //是否显示刷新按钮
            minimumCountColumns: 2,             //最少允许的列数
            clickToSelect: true,                //是否启用点击选中行
            showToggle: true,
        };
        this.params = $.extend({}, this.default, options || {});

        //得到选中的记录
        this.getSelections = function () {
            var arrRes = that.bootstrapTable("getSelections")
            return arrRes;
        };

        //刷新
        this.refresh = function () {
            that.bootstrapTable("refresh");
        };
    };
})(jQuery);

代码释疑:这个js文件主要做了两件事

  1. 自定义data-bind属性myBootstrapTable。对于ko.bindingHandlers.myBootstrapTable里面的update方法,如非必须,可以不用定义。
  2. 通过向ko对象里面添加bootstrapTableViewModel来封装bootstrapTable。

1.2、html标签启动绑定

        <table id="tb_dept" data-bind="myBootstrapTable:$root">
            <thead>
                <tr>
                    <th data-checkbox="true"></th>
                    <th data-field="Name">部门名称</th>
                    <th data-field="Level">部门级别</th>
                    <th data-field="Des">描述</th>
                    <th data-field="strCreatetime">创建时间</th>
                </tr>
            </thead>
        </table>

代码释疑:定义一个table标签,使用自定义绑定myBootstrapTable,上篇说过,$root可以理解为初始化的意思。为了简单,所有的colums就直接在<th>里面写了。

1.3、激活ko的绑定

在页面加载完成之后,启动ko的绑定:

//初始化
$(function () {
    //1、初始化表格
    tableInit.Init();

    //2、注册增删改事件
    operate.operateInit();
});

//初始化表格
var tableInit = {
    Init: function () {
        //绑定table的viewmodel
        this.myViewModel = new ko.bootstrapTableViewModel({
            url: ‘/Department/GetDepartment‘,         //请求后台的URL(*)
            method: ‘get‘,                      //请求方式(*)
            toolbar: ‘#toolbar‘,                //工具按钮用哪个容器
            queryParams: function (param) {
                return { limit: param.limit, offset: param.offset };
            },//传递参数(*)
            pagination: true,                   //是否显示分页(*)
            sidePagination: "server",           //分页方式:client客户端分页,server服务端分页(*)
            pageNumber: 1,                      //初始化加载第一页,默认第一页
            pageSize: 10,                       //每页的记录行数(*)
            pageList: [10, 25, 50, 100],        //可供选择的每页的行数(*)
        });
        ko.applyBindings(this.myViewModel, document.getElementById("tb_dept"));
    }
};

代码释疑:页面加载完成之后,调用上面封装的bootstrapTableViewModel对象合并传递的参数,最后激活绑定,将this.myViewModel作为绑定的viewmodel激活。调试代码可知,当执行到 ko.applyBindings(this.myViewModel, document.getElementById("tb_dept")); 这一句的时候,自定义绑定才会生效,程序才会进入到 ko.bindingHandlers.myBootstrapTable 对象的init方法去初始化bootstrapTable。这里需要说明一点:

    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        //这里的oParam就是绑定的viewmodel
        var oViewModel = valueAccessor();
        var $ele = $(element).bootstrapTable(oViewModel.params);
        //给viewmodel添加bootstrapTable方法
        oViewModel.bootstrapTable = function () {
            return $ele.bootstrapTable.apply($ele, arguments);
        }
    }

上文中的init方法,通过第二个参数valueAccessor,我们得到的是当前绑定的viewmodel,也就是我们上面的this.myViewModel这个对象,博主觉得这一点有利于你理解自定义绑定的逻辑。基本上执行到 var $ele = $(element).bootstrapTable(oViewModel.params); 这一句的时候,我们表格的初始化就完成了。后台对应的方法博主随便定义了一个集合,为了完整,这里还是贴出来:

 public class DepartmentController : Controller
    {
        // GET: Department
        public ActionResult Index()
        {
            return View();
        }

        [HttpGet]
        public JsonResult GetDepartment(int limit,int offset)
        {
            var lstRes = DepartmentModel.GetData();
            lstRes.ForEach(x=> {
                x.strCreatetime = x.Createtime.ToString("yyyy-MM-dd HH:mm:ss");
            });
            var oRes = new
            {
                rows = lstRes.Skip(offset).Take(limit).ToList(),
                total = lstRes.Count
            };
            return Json(oRes, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public JsonResult Add(Department oData)
        {
            DepartmentModel.Add(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public JsonResult Update(Department oData)
        {
            DepartmentModel.Update(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public JsonResult Delete(List<Department> oData)
        {
            DepartmentModel.Delete(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }
    }

DepartmentController

2、按钮操作

上面通过bootstrapTable的初始化完成了我们的自定义data-bind的使用。下面的按钮操作我们来体验一把使用监控属性的“爽歪歪”。

2.1、view页面

首先在view页面上面定义我们的增删改按钮

        <div id="toolbar" class="btn-group">
            <button id="btn_add" type="button" class="btn btn-default">
                <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增
            </button>
            <button id="btn_edit" type="button" class="btn btn-default">
                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改
            </button>
            <button id="btn_delete" type="button" class="btn btn-default">
                <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>删除
            </button>
        </div>

为了简便,博主使用了一个隐藏的弹出框用来包含新增和编辑的文本框。当然,一般情况下,可能这里用的是部分视图,你的项目里面可能会有一个Edit.cshtml,但这里博主将这些都放在一个页面上面,因为这不是文本的重点。

       <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                        <h4 class="modal-title" id="myModalLabel">操作</h4>
                    </div>
                    <div class="modal-body">

                        <div class="form-group">
                            <label for="txt_departmentname">部门名称</label>
                            <input type="text" name="txt_departmentname" data-bind="value:Name" class="form-control" id="txt_departmentname" placeholder="部门名称">
                        </div>
                        <div class="form-group">
                            <label for="txt_departmentlevel">部门级别</label>
                            <input type="text" name="txt_departmentlevel" data-bind="value:Level" class="form-control" id="txt_departmentlevel" placeholder="部门级别">
                        </div>
                        <div class="form-group">
                            <label for="txt_des">描述</label>
                            <input type="text" name="txt_des" data-bind="value:Des" class="form-control" id="txt_des" placeholder="描述">
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button>
                        <button type="button" id="btn_submit" class="btn btn-primary" data-dismiss="modal"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button>
                    </div>
                </div>
            </div>
        </div>

2.2、JS初始化按钮操作

//操作
var operate = {
    //初始化按钮事件
    operateInit: function () {
        this.operateAdd();
        this.operateUpdate();
        this.operateDelete();
        this.DepartmentModel = {
            id: ko.observable(),
            Name: ko.observable(),
            Level: ko.observable(),
            Des: ko.observable(),
            CreateTime: ko.observable()
        };
    },
    //新增
    operateAdd: function(){
        $(‘#btn_add‘).on("click", function () {
            $("#myModal").modal().on("shown.bs.modal", function () {
                var oEmptyModel = {
                    id: ko.observable(),
                    Name: ko.observable(),
                    Level: ko.observable(),
                    Des: ko.observable(),
                    CreateTime: ko.observable()
                };
                ko.utils.extend(operate.DepartmentModel, oEmptyModel);
                ko.applyBindings(operate.DepartmentModel, document.getElementById("myModal"));
                operate.operateSave();
            }).on(‘hidden.bs.modal‘, function () {
                ko.cleanNode(document.getElementById("myModal"));
            });
        });
    },
    //编辑
    operateUpdate: function () {
        $(‘#btn_edit‘).on("click", function () {
            $("#myModal").modal().on("shown.bs.modal", function () {
                var arrselectedData = tableInit.myViewModel.getSelections();
                if (!operate.operateCheck(arrselectedData)) { return; }
                //将选中该行数据有数据Model通过Mapping组件转换为viewmodel
                ko.utils.extend(operate.DepartmentModel, ko.mapping.fromJS(arrselectedData[0]));
                ko.applyBindings(operate.DepartmentModel, document.getElementById("myModal"));
                operate.operateSave();
            }).on(‘hidden.bs.modal‘, function () {
                //关闭弹出框的时候清除绑定(这个清空包括清空绑定和清空注册事件)
                ko.cleanNode(document.getElementById("myModal"));
            });
        });
    },
    //删除
    operateDelete: function () {
        $(‘#btn_delete‘).on("click", function () {
            var arrselectedData = tableInit.myViewModel.getSelections();
            $.ajax({
                url: "/Department/Delete",
                type: "post",
                contentType: ‘application/json‘,
                data: JSON.stringify(arrselectedData),
                success: function (data, status) {
                    alert(status);
                    //tableInit.myViewModel.refresh();
                }
            });
        });
    },
    //保存数据
    operateSave: function () {
        $(‘#btn_submit‘).on("click", function () {
            //取到当前的viewmodel
            var oViewModel = operate.DepartmentModel;
            //将Viewmodel转换为数据model
            var oDataModel = ko.toJS(oViewModel);var funcName = oDataModel.id?"Update":"Add";
            $.ajax({
                url: "/Department/"+funcName,
                type: "post",
                data: oDataModel,
                success: function (data, status) {
                    alert(status);
                    tableInit.myViewModel.refresh();
                }
            });
        });
    },
    //数据校验
    operateCheck:function(arr){
        if (arr.length <= 0) {
            alert("请至少选择一行数据");
            return false;
        }
        if (arr.length > 1) {
            alert("只能编辑一行数据");
            return false;
        }
        return true;
    }
}

代码释疑:说说这里的执行逻辑,首先在$(function(){})方法里面调用 operate.operateInit(); 。在operateInit()方法里面注册页面上面按钮的点击事件,同时也定义 this.DepartmentModel 作为我们新增编辑的viewmodel,这个viewmodel里面定义了和页面元素对应的监控属性。还记得上面隐藏的弹出框里面的一些data-bind吗,没错,里面对应的value值就是和这里的监控属性对应,这样设置绑定之后,js里面所有的导致 this.DepartmentModel 里面监控的变化,都会触发界面上面这些绑定标签的value值变化,反之,界面上面的所有标签的Value值的变化,也势必会引起它的监控属性值的变化,此之所谓双向绑定。下面具体看看双向绑定的执行。

2.3、新增操作

     $(‘#btn_add‘).on("click", function () {
            $("#myModal").modal().on("shown.bs.modal", function () {
                var oEmptyModel = {
                    id: ko.observable(),
                    Name: ko.observable(),
                    Level: ko.observable(),
                    Des: ko.observable(),
                    CreateTime: ko.observable()
                };
                ko.utils.extend(operate.DepartmentModel, oEmptyModel);
                ko.applyBindings(operate.DepartmentModel, document.getElementById("myModal"));
                operate.operateSave();
            }).on(‘hidden.bs.modal‘, function () {
                ko.cleanNode(document.getElementById("myModal"));
            });
        });

当我们界面触发新增操作的时候,首先会弹出上面说的隐藏模态框。在模态框显示的时候,首先定义一个空的viewmodel,然后调用 ko.utils.extend(operate.DepartmentModel, oEmptyModel); 这一句,将全局的operate.DepartmentModel被空的viewmodel覆盖。ko.utils.extend()这个方法的作用和jquery里面的$.extend()作用类似,都是根据后面对象合并前面对象,合并之后,使用新的viewmodel激活绑定。激活绑定之后,注册保存按钮的click事件。这样新增的时候,弹出模态框,由于viewmodel里面的监控属性都是空的,对应界面元素的value也会被清空,所以新增我们看到是这样:

当弹出框关闭后,我们通过关闭的事件,执行 ko.cleanNode(document.getElementById("myModal")); 这一句,这个很重要,因为对于同一个dom,ko只能绑定一次,如果需要再次绑定,需要先清空绑定,并且cleanNode()这个方法,它不仅会清空绑定,还是会dom里面注册的事件也会清空,使用的时候需要注意下!

2.4、编辑操作

    $(‘#btn_edit‘).on("click", function () {
            $("#myModal").modal().on("shown.bs.modal", function () {
                var arrselectedData = tableInit.myViewModel.getSelections();
                if (!operate.operateCheck(arrselectedData)) { return; }
                //将选中该行数据有数据Model通过Mapping组件转换为viewmodel
                ko.utils.extend(operate.DepartmentModel, ko.mapping.fromJS(arrselectedData[0]));
                ko.applyBindings(operate.DepartmentModel, document.getElementById("myModal"));
                operate.operateSave();
            }).on(‘hidden.bs.modal‘, function () {
                //关闭弹出框的时候清除绑定(这个清空包括清空绑定和清空注册事件)
                ko.cleanNode(document.getElementById("myModal"));
            });
        });

当我们触发编辑操作的时候,界面还是弹出框。在弹出框的弹出事件里面,我们取到当前选中的行,然后校验是否选中了一行。最好通过 ko.mapping.fromJS(arrselectedData[0]) 这一句,将普通的Json对象转换为带有监控属性的viewmodel,上篇说过,这个方法需要 knockout.mapping-latest.js 这个js文件的支持。转换之后,还是通过ko.utils.extend()方法更新viewmodel,然后激活绑定。由于viewmodel被当前选中行的数据更新了,所以得到结果:

2.5、保存操作

在新增和编辑弹出框之后,修改相关信息后点击保存,就会触发保存事件。

        $(‘#btn_submit‘).on("click", function () {
            //取到当前的viewmodel
            var oViewModel = operate.DepartmentModel;
            //将Viewmodel转换为数据model
            var oDataModel = ko.toJS(oViewModel);
var funcName = oDataModel.id?"Update":"Add";
            $.ajax({
                url: "/Department/"+funcName,
                type: "post",
                data: oDataModel,
                success: function (data, status) {
                    alert(status);
                    tableInit.myViewModel.refresh();
                }
            });
        });

当触发保存事件的时候,我们首先取到页面绑定的viewmodel,即operate.DepartmentModel,然后使用ko.toJS()方法将带有监控属性的viewmodel转换为纯数据的Json对象,这个方法是ko内置的,不需要其他js支持。得到json对象之后,发送ajax请求,去新增或者编辑数据。这样就很好地体现了双向绑定,界面上面所有文本框的value发生了变化之后,也会触发operate.DepartmentModel的变化。

2.6、删除操作

删除操作没什么好说的,和ko关系不大。

三、总结

以上通过一个简单的增删改查操作,介绍了下ko和bootstrapTable的联合使用。ko可以让你从DOM中解放出来,把关注点放在viewmodel上面。纵观整个js代码,几乎看不到jquery的val()、text()等对界面dom做取值和赋值的操作,是不是看着干净清爽,并且高大上了呢~~当然,这或许只是ko的一些比较基础的用法,毕竟博主学习ko才3天,更多高级用法还有待摸索,等过段时间用熟了,再将它的一些高级用法分享给大家。如果你觉得本文能够帮助你理解ko的原理以及它的一些用法,不妨推荐下,博主一定继续努力!

时间: 2024-10-12 14:12:49

JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(二)的相关文章

JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(三):两个Viewmodel搞定增删改查

前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控件的取值和赋值,远远没有将MVVM的精妙展现出来.最近项目打算正式将ko用起来,于是乎对ko和bootstraptable做了一些封装,在此分享出来供园友们参考.封装思路参考博客园大神萧秦,如果园友们有更好的方法,欢迎讨论. KnockoutJS系列文章: JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(一) JS组件

BootstrapTable+KnockoutJS实现增删改查解决方案

BootstrapTable+KnockoutJS实现增删改查解决方案 前言:上篇介绍了下ko增删改查的封装,确实节省了大量的js代码.博主是一个喜欢偷懒的人,总觉得这些基础的增删改查效果能不能通过一个什么工具直接生成页面效果,啥代码都不用写了,那该多爽.于是研究了下T4的语法,虽然没有完全掌握,但是算是有了一个大致的了解.于是乎有了今天的这篇文章:通过T4模板快速生成页面. KnockoutJS系列文章: JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(

JS组件系列——BootstrapTable 行内编辑解决方案:x-editable

前言:之前介绍bootstrapTable组件的时候有提到它的行内编辑功能,只不过为了展示功能,将此一笔带过了,罪过罪过!最近项目里面还是打算将行内编辑用起来,于是再次研究了下x-editable组件,遇到过一些坑,再此做个采坑记录吧!想要了解bootstrapTable的园友可以移步 JS组件系列——表格组件神器:bootstrap table. 本文原创地址:http://www.cnblogs.com/landeanfen/p/5821192.html 一.x-editable组件介绍 x

Sql Server2008温故而知新系列02:数据增删改查之&quot;增&quot;

增删改查-数据库最基本使用方法,也是数据库最常用的操作方法: 用到的命令:insert[into] 插入:delete from  删除:update 修改:select 查询. 首先说一说插入的格式(即新增数据): 1.insert into table_name(field1,field2,field3,…………)  values(字段1记录,字段2记录,…………) 如有多行记录重复写入多行 2.insert into table_name(field1,field2,field3,…………

通过JDBC进行简单的增删改查(二)

本章笔记更易理解和学习,也是我第一次初学的笔记. 1 package javastudy; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.ResultSetMetaData; 8 import java.sql.SQLException; 9 im

搭建第一个web项目:实现用户的增删改查(二)

最近学了点java和微信的东西,公司这个项目有搭建不起来,在公司有时间的时候就学点Android了,放了个端午假期又颓废了一段时间,这个增删改还没有整理好,我勒个去啊. 不过最近慢慢整理项目的时候,发现了一些思路,对以后和像我一样的新手来说也许有不少的帮助. 1.不要一味心思的去赶紧实现自己的想法,其实一步一步,从简单的来,慢慢测试,也许更节约时间. 这次就是急着去扩展easyUI的dialog,模仿公司的扩展方法,可是一直不成功.反而先做一个简单的dialog,慢慢一步步调试,就会发现自己犯的

通用DAO之MyBatis封装,封装通用的增删改查(二)

曾经发过一篇文章,大概写的就是阿海多么多么厉害,见到某位同事在Hibernate的基础上封装了一下,就以一己之力开发什么什么框架,最后写了个超大的一坨的事. 那么,后续篇来了.阿海不是自负之人,当之前的CRUD框架并没有达到理想的结果时,阿海转向了Mybatis封装.别问我为什么不是Hibernate.我本来就不喜欢Hibernate,即使在之前的一家公司一直被强制性的约束使用Hibernate时,也没有对Hibernate产生什么真正的好感,反而屡次发现了Hibernate的一些问题. 或许是

Mybatis实现增删改查(二)

1. 准备         请先完成Mybatis基本配置(一)的基本内容 2. 查询多条商品信息         1)在com.mybatis.dao.PartDao中增加接口函数 public List<PartInfo> getAllPartInfo();         2)在com.mybatis.dao.mapper.PartMapper中增加其实现方法.尽管我需要赶回的数据类型为List<PartInfo>,但是在Mapper文件中配置resultType属性的时候依

MVC中的Ajax与增删改查(二)

上一篇记录的是前台操作,下面写一下后台 ,本来自认为是没有必要做补充,毕竟思路啥的都有,实际上在做删除操作的时候,折腾了一天,还是自己太嫩,逻辑不够严谨,这里作下记录. 关于表结构这里再作下说明: ①表A是三个联合主键,key1,key2,... ②表B是四个联合主键 key1,key2,key3,... ③A表主键是B表外键 ④A,B表的某个栏位的描述信息都来自语言表C ⑤C表联合主键(TableName,TableKey,Language,Desc,...) ⑥如果记录A表描述信息: Tab