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的原理以及它的一些用法,不妨推薦下,博主一定繼續努力!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM