JS組件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(四):自定義T4模板快速生成頁面

前言:上篇介紹了下ko增刪改查的封裝,確實節省了大量的js代碼。博主是一個喜歡偷懶的人,總以爲這些基礎的增刪改查效果能不能經過一個什麼工具直接生成頁面效果,啥代碼都不用寫了,那該多爽。因而研究了下T4的語法,雖然沒有徹底掌握,可是算是有了一個大體的瞭解,給須要自定義模板的園友們提供一個參考。因而乎有了今天的這篇文章:經過T4模板快速生成頁面。javascript

KnockoutJS系列文章:css

本文原創地址:http://www.cnblogs.com/landeanfen/p/5667022.htmlhtml

1、T4的使用介紹

咱們知道,MVC裏面在添加視圖的時候能夠自動生成增刪改查的頁面效果,那是由於MVC爲咱們內置了基礎增刪改查的模板,這些模板的語法就是使用T4,那麼這些模板在哪裏呢?找了下相關文章,發現MVC4及如下的版本模板位置和MVC5及以上模板的位置有很大的不一樣。前端

  • MVC4及如下版本的模板位置:VS的安裝目錄+\ItemTemplates\CSharp\Web\MVC 2\CodeTemplates。好比博主的D:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 4\CodeTemplates。找到cshtml對應的模板,裏面就有相應的增刪改查的tt文件
  • MVC5及以上版本的模板位置:直接給出博主的模板位置D:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates

知道了這個,那麼接下來就是改造模板,添加本身的生成內容了。能夠直接將List和Edit模板拷貝到過來自行改造,可是最後想了想,仍是別動MVC內置的東西了,咱們本身來建本身的模板不是更好。java

在當前Web項目的根目錄下面新建一個文件夾,命名爲CodeTemplates,而後將MVC模板裏面的MvcControllerEmpty和MvcView兩個模板文件夾拷貝到CodeTemplates文件夾下面,去掉它裏面的原始模板,而後新建幾個本身的模板,以下圖:jquery

這樣咱們在添加新的控制器和新建視圖的時候就能夠看到咱們自定義的模板了:bootstrap

2、T4代碼介紹

上面介紹瞭如何新建本身的模板,模板建好以後就要開始往裏面塞相應的內容了,若是T4的語法展開了說,那一篇是說不完的,有興趣的園友能夠去園子裏找找,文章仍是挺多的。這裏主要仍是來看看幾個模板內容。還有一點須要說明下,貌似從MVC5以後,T4的模板文件後綴所有改爲了t4,而以前的模板一直是tt結尾的,沒有細究它們語法的區別,估計應該差異不大app

一、Controller.cs.t4

爲何要重寫這個空的控制器模板呢?博主以爲增刪改查的好多方法都須要手動去寫好麻煩,寫一個模板直接生成能夠省事不少。來看看模板裏面的實現代碼:ide

<#@ template language="C#" HostSpecific="True" #>
<#@ output extension="cs" #>
<#@ parameter type="System.String" name="ControllerName" #>
<#@ parameter type="System.String" name="ControllerRootName" #>
<#@ parameter type="System.String" name="Namespace" #>
<#@ parameter type="System.String" name="AreaName" #>
<#
    var index = ControllerName.LastIndexOf("Controller");
    var ModelName = ControllerName.Substring(0, index);
#>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestKO.Models;

namespace <#= Namespace #>
{
    public class <#= ControllerName #> : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Edit(<#= ModelName #> model)
        {
            return View(model);
        }


        [HttpGet]
        public JsonResult Get(int limit, int offset)
        {
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        //新增實體
        [HttpPost]
        public JsonResult Add(<#= ModelName #> oData)
        {
            <#= ModelName #>Model.Add(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        //更新實體
        [HttpPost]
        public JsonResult Update(<#= ModelName #> oData)
        {
            <#= ModelName #>Model.Update(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        //刪除實體
        [HttpPost]
        public JsonResult Delete(List<<#= ModelName #>> oData)
        {
            <#= ModelName #>Model.Delete(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }
    }
}

這個內容不難理解,直接查看生成的控制器代碼:工具

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestKO.Models;

namespace TestKO.Controllers
{
    public class UserController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Edit(User model)
        {
            return View(model);
        }


        [HttpGet]
        public JsonResult Get(int limit, int offset)
        {
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        //新增實體
        [HttpPost]
        public JsonResult Add(User oData)
        {
            UserModel.Add(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        //更新實體
        [HttpPost]
        public JsonResult Update(User oData)
        {
            UserModel.Update(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

        //刪除實體
        [HttpPost]
        public JsonResult Delete(List<User> oData)
        {
            UserModel.Delete(oData);
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }
    }
}

二、KoIndex.cs.t4

這個模板主要用於生成列表頁面,大體代碼以下:

<#@ template language="C#" HostSpecific="True" #>
<#@ output extension=".cshtml" #>
<#@ include file="Imports.include.t4" #>
<#
// The following chained if-statement outputs the file header code and markup for a partial view, a view using a layout page, or a regular view.
if(IsPartialView) {
#>

<#
} else if(IsLayoutPageSelected) {
#>
@{
    ViewBag.Title = "<#= ViewName#>";
<#
if (!String.IsNullOrEmpty(LayoutPageFile)) {
#>
    Layout = "<#= LayoutPageFile#>";
<#
}
#>
}
<#
} else {
#>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title><#= ViewName #></title>
    <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="~/scripts/extensions/knockout.index.js"></script>
    <script src="~/scripts/extensions/knockout.bootstraptable.js"></script>
    <script type="text/javascript">
        $(function () {
            var viewModel = {
                bindId: "div_index",
                tableParams :
                {
                    url : "/<#=ViewDataTypeShortName#>/Get",
                    pageSize : 2,
                },
                urls :
                {
                    del : "/<#=ViewDataTypeShortName#>/Delete",
                    edit : "/<#=ViewDataTypeShortName#>/Edit",
                    add : "/<#=ViewDataTypeShortName#>/Edit",
                },
                queryCondition :
                {
                    
                }
            };
            ko.bindingViewModel(viewModel);
        });
    </script>
</head>
<body>
<#
    PushIndent("    ");
}
#>
<div id="toolbar">
    <button data-bind="click:addClick" type="button" class="btn btn-default">
         <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增
    </button>
    <button data-bind="click:editClick" type="button" class="btn btn-default">
        <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改
    </button>
    <button data-bind="click:deleteClick" type="button" class="btn btn-default">
        <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>刪除
    </button>
</div>
<table data-bind="bootstrapTable:bootstrapTable">
    <thead>
        <tr>
            <th data-checkbox="true"></th>
<#
IEnumerable<PropertyMetadata> properties = ModelMetadata.Properties;
foreach (PropertyMetadata property in properties) {
    if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey) {
#>
            <th data-field="<#= GetValueExpression(property) #>"><#= GetValueExpression(property) #></th>
<#
    }
}#>
        </tr>
    </thead>
</table>
<#
// The following code closes the tag used in the case of a view using a layout page and the body and html tags in the case of a regular view page
#>
<#
if(!IsPartialView && !IsLayoutPageSelected) {
    ClearIndent();
#>
</body>
</html>
<#
}
#>
<#@ include file="ModelMetadataFunctions.cs.include.t4" #>

添加一個視圖Index,而後選擇這個模板

獲得的頁面內容

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <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="~/scripts/extensions/knockout.index.js"></script>
    <script src="~/scripts/extensions/knockout.bootstraptable.js"></script>
    <script type="text/javascript">
        $(function () {
            var viewModel = {
                bindId: "div_index",
                tableParams :
                {
                    url : "/User/Get",
                    pageSize : 2,
                },
                urls :
                {
                    del : "/User/Delete",
                    edit : "/User/Edit",
                    add : "/User/Edit",
                },
                queryCondition :
                {
                    
                }
            };
            ko.bindingViewModel(viewModel);
        });
    </script>
</head>
<body>
    <div id="toolbar">
        <button data-bind="click:addClick" type="button" class="btn btn-default">
             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增
        </button>
        <button data-bind="click:editClick" type="button" class="btn btn-default">
            <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改
        </button>
        <button data-bind="click:deleteClick" type="button" class="btn btn-default">
            <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>刪除
        </button>
    </div>
    <table data-bind="bootstrapTable:bootstrapTable">
        <thead>
            <tr>
                <th data-checkbox="true"></th>
                <th data-field="Name">Name</th>
                <th data-field="FullName">FullName</th>
                <th data-field="Age">Age</th>
                <th data-field="Des">Des</th>
                <th data-field="Createtime">Createtime</th>
                <th data-field="strCreatetime">strCreatetime</th>
            </tr>
        </thead>
    </table>
</body>
</html>
Index.cshtml

咱們將上篇說的viewmodel搬到頁面上面來了,這樣每次就不用從controller裏面傳過來了。稍微改一下表格的列名,頁面就能夠跑起來了。

這裏有待優化的幾點:

(1)查詢條件沒有生成,若是將T4的語法研究深一點,能夠在須要查詢的字段上面添加特性標識哪些字段須要查詢,而後自動生成對應的查詢條件。

(2)表格的列名彷佛也能夠經過屬性的字段特性來生成。這點和第一點相似,都須要研究T4的語法。

三、KoEdit.cs.t4

第三個模板頁就是編輯的模板了,它的大體代碼以下:

<#@ template language="C#" HostSpecific="True" #>
<#@ output extension=".cshtml" #>
<#@ include file="Imports.include.t4" #>
@model <#= ViewDataTypeName #>
<#
// "form-control" attribute is only supported for all EditorFor() in System.Web.Mvc 5.1.0.0 or later versions, except for checkbox, which uses a div in Bootstrap
string boolType = "System.Boolean";
Version requiredMvcVersion = new Version("5.1.0.0");
bool isControlHtmlAttributesSupported = MvcVersion >= requiredMvcVersion;
// The following chained if-statement outputs the file header code and markup for a partial view, a view using a layout page, or a regular view.
if(IsPartialView) {
#>

<#
} else if(IsLayoutPageSelected) {
#>

@{
    ViewBag.Title = "<#= ViewName#>";
<#
if (!String.IsNullOrEmpty(LayoutPageFile)) {
#>
    Layout = "<#= LayoutPageFile#>";
<#
}
#>
}

<h2><#= ViewName#></h2>

<#
} else {
#>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title><#= ViewName #></title>
</head>
<body>
<#
    PushIndent("    ");
}
#>
<#
if (ReferenceScriptLibraries) {
#>
<#
    if (!IsLayoutPageSelected && IsBundleConfigPresent) {
#>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")

<#
    }
#>
<#
    else if (!IsLayoutPageSelected) {
#>
<script src="~/Scripts/jquery-<#= JQueryVersion #>.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

<#
    }
#>

<#
}
#>
<form id="formEdit" class="form-horizontal">
    @Html.HiddenFor(model => model.Id)
    <div class="modal-body">
<#
IEnumerable<PropertyMetadata> properties = ModelMetadata.Properties;
foreach (PropertyMetadata property in properties) {
    if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey) {
#>
        <div class="form-group">
            @Html.LabelFor(model => model.<#= GetValueExpression(property) #>, "<#= GetValueExpression(property) #>", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.<#= GetValueExpression(property) #>, new { @class = "form-control", data_bind = "value:editModel.<#= GetValueExpression(property) #>" })
            </div>
        </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="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button>
    </div>
</form>
<#
    var index = ViewDataTypeName.LastIndexOf(".");
    var ModelName = ViewDataTypeName.Substring(index+1, ViewDataTypeName.Length-index-1);
#>
<script src="~/Scripts/extensions/knockout.edit.js"></script>
<script type="text/javascript">
    $(function () {
        var model = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));
        var viewModel = {
            formId: "formEdit",
            editModel : model,
            urls :
            {
                submit : model.id == 0 ? "/<#= ModelName #>/Add" : "/<#= ModelName #>/Update"
            },
            validator:{
                fields: { 
                    Name: {
                        validators: {
                            notEmpty: {
                                message: '名稱不能爲空!'
                            }
                        }
                    }
                }
            }
        };
        ko.bindingEditViewModel(viewModel);  
    });
</script>
<#
if(IsLayoutPageSelected && ReferenceScriptLibraries && IsBundleConfigPresent) {
#>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
<#
}
#>
<#
else if(IsLayoutPageSelected && ReferenceScriptLibraries) {
#>

<script src="~/Scripts/jquery-<#= JQueryVersion #>.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<#
}
#>
<#
// The following code closes the tag used in the case of a view using a layout page and the body and html tags in the case of a regular view page
#>
<#
if(!IsPartialView && !IsLayoutPageSelected) {
    ClearIndent();
#>
</body>
</html>
<#
}
#>
<#@ include file="ModelMetadataFunctions.cs.include.t4" #>

生成的代碼:

@model TestKO.Models.User

<form id="formEdit" class="form-horizontal">
    @Html.HiddenFor(model => model.Id)
    <div class="modal-body">
        <div class="form-group">
            @Html.LabelFor(model => model.Name, "Name", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control", data_bind = "value:editModel.Name" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.FullName, "FullName", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.FullName, new { @class = "form-control", data_bind = "value:editModel.FullName" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Age, "Age", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.Age, new { @class = "form-control", data_bind = "value:editModel.Age" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Des, "Des", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.Des, new { @class = "form-control", data_bind = "value:editModel.Des" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Createtime, "Createtime", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.Createtime, new { @class = "form-control", data_bind = "value:editModel.Createtime" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.strCreatetime, "strCreatetime", new { @class = "control-label col-xs-2" })
            <div class="col-xs-10">
                @Html.TextBoxFor(model => model.strCreatetime, new { @class = "form-control", data_bind = "value:editModel.strCreatetime" })
            </div>
        </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="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button>
    </div>
</form>
<script src="~/Scripts/extensions/knockout.edit.js"></script>
<script type="text/javascript">
    $(function () {
        var model = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));
        var viewModel = {
            formId: "formEdit",
            editModel : model,
            urls :
            {
                submit : model.id == 0 ? "/User/Add" : "/User/Update"
            },
            validator:{
                fields: { 
                    Name: {
                        validators: {
                            notEmpty: {
                                message: '名稱不能爲空!'
                            }
                        }
                    }
                }
            }
        };
        ko.bindingEditViewModel(viewModel);  
    });
</script>
Edit.cshtml

 固然,代碼也須要作稍許修改。經過添加自定義的模板頁,只要後臺對應的實體模型建好了,在前端只須要新建兩個自定義視圖,一個簡單的增刪改查便可完成,不用寫一句js代碼。

 3、select組件的綁定

上面介紹了下T4封裝增刪改查的語法,頁面全部的組件基本都是文本框,然而,在實際項目中,不少的查詢和編輯頁面都會存在下拉框的展現,對於下拉框,咱們該如何處理呢?不賣關子了,直接給出解決方案吧,好比編輯頁面咱們能夠在後臺將下拉框的數據源放在實體裏面。

用戶的實體

[DataContract]
    public class User
    {
        [DataMember]
        public int id { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string FullName { get; set; }

        [DataMember]
        public int Age { get; set; }

        [DataMember]
        public string Des { get; set; }

        [DataMember]
        public DateTime Createtime { get; set; }

        [DataMember]
        public string strCreatetime { get; set; }

        [DataMember]
        public string DepartmentId { get; set; }

        [DataMember]
        public object Departments { get; set; }
    }

而後編輯頁面

        public ActionResult Edit(User model)
        {
            model.Departments = DepartmentModel.GetData();
            return View(model);
        }

而後前端綁定便可。

                <div class="form-group">
                    <label for="txt_des">所屬部門</label>
                    <select id="sel_dept" class="form-control" data-bind="options: editModel.Departments,
                            optionsText: 'Name',
                            optionsValue: 'Id',
                            value:editModel.DepartmentId"></select>
                </div>

JS代碼不用作任何修改,新增和編輯的時候部門字段就能自動添加到viewmodel裏面去。

固然,咱們不少項目使用的下拉框都不是單純的select,由於單純的select樣式實在是難看,因而乎出了不少的select組件,好比博主以前分享的select二、MultiSelect等等。當使用這些組件去初始化select時,審覈元素你會發現,這個時候界面上的下拉框已經不是單純的select標籤了,而是由組件自定義的不少其餘標籤組成。咱們就以select2組件爲例來看看直接按照上面的這樣初始化是否可行。

咱們將編輯頁面初始化的js代碼增長最後一句:

<script type="text/javascript">
    $(function () {
var model = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));
        var viewModel = {
            formId: "formEdit",
            editModel : model,
            urls :
            {
                submit : model.id == 0 ? "/User/Add" : "/User/Update"
            },
            validator:{
                fields: {
                    Name: {
                        validators: {
                            notEmpty: {
                                message: '名稱不能爲空!'
                            }
                        }
                    }
                }
            }
        };
        ko.bindingEditViewModel(viewModel);

        $("#sel_dept").select2({});
    });
</script>

經過新增和編輯發現,這樣確實可行!分析緣由,雖然初始化成select2組件以後,頁面的html發生了變化,可是組件最終仍是會將選中值呈如今原始的select控件上面。不知道除了select2,其餘select初始化組件會不會這樣,待驗證。可是這裏有一點須要說明下,在初始化select2以前,下拉框的options必須先綁定值,也就是說,組件的初始化必需要放在ko.applyBinding()以後。

4、總結

至此,ko結合bootstrapTable的模板生成以及select控件的使用基本可用,固然,還有待完善。後面若是有時間,博主會整理下其餘前端組件和ko的聯合使用,好比咱們最多見的日期控件。若是你以爲本文對你有幫助,歡迎推薦

本文原創出處:http://www.cnblogs.com/landeanfen/

歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索