使用Bootstrap模态框实现增删改查功能

模态框(Modal)是覆盖在父窗体上的子窗体。通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动。子窗体可提供信息、交互等。

本文实现的是使用模态框实现简单的增删改查的功能。主要思想是 使用一个模态框,使用jquery调用一个控制器方法返回一个分部视图表单,加载到这个模态框中。然后,使用jquery 注册 单击事件,当模态框中的按钮点击的时候,提交相应的表单。由于 不同的操作 将会返回不同的分部视图表单,因此,表单提交到的控制器方法也不同,而分别实现了增删改查。

一、新建一个文件_Category.Modal.cshtml,里面存放一个模态框categoryModal。分部视图文件名约定使用 _ 开头。

_Category.Modal.cshtml:

<!-- 模态框 -->

<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="categoryModalLabel" aria-hidden="true"> //该Modal 默认隐藏

<div class="modal-dialog">

<div class="modal-content">

<div class="modal-header">

<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> //关闭模态框X按钮

<h4 class="modal-title" >

正在加载模态框标题

</h4>

</div>

<div class="modal-body" >

正在加载内容

</div>

<div class="modal-footer"> //让关闭模态框的按钮和操作按钮并排放置。

<button type="button" class="btn btn-info" data-dismiss="modal">

关闭

</button>

<button type="button" class="btn btn-success" >正在加载按钮功能</button>

</div>

</div>

</div>

</div>

二、将模态框作为分部视图存放在主视图中,@{ Html.RenderPartial("_Category.Modal");}

1、主控制器和主视图 Index 方法和Index视图,javascript 脚本本例是直接放在主视图中,也可以放在单独的js文件或cshtml中。放在cshtml文件中。定义一个cshtml文件比单独定义一个js文件更加有用。居然在js里面还可以使用url、html辅助方法。

主控制器

public ActionResult Index(string sortOrder,int page =1)

{

ViewBag.CurrentSort = sortOrder;

ViewBag.SortByNumber = string.IsNullOrEmpty(sortOrder) ? "CategoryNumber_desc": "";

ViewBag.SortByCreateTime = sortOrder == "CategoryCreateTime" ? "CategoryCreateTime_desc":"CategoryCreateTime";

ViewBag.SortByCategoryName = sortOrder == "CategoryName" ? "CategoryName_desc" : "CategoryName";

ViewBag.SortByIsUse =sortOrder == "IsUse" ? "IsUse_desc" : "IsUse";

var categories = from category in db.Categories.Include(c =>c.ReviewProjects).Include(c =>c.ReviewIndexItems).Include(c =>c.ReviewIndexSystems) select category; //用Linq方法语法就会提示需要转换IOrderable<category>,用查询表达式语法就不会提示需要转换。

switch (sortOrder)

{

case "CategoryNumber_desc":

categories =categories.OrderByDescending(c =>c.CategoryNumber);

break;

case "CategoryCreateTime":

categories = categories.OrderBy(c => c.CategoryCreateTime);

break;

case "CategoryCreateTime_desc":

categories = categories.OrderByDescending(c => c.CategoryCreateTime);

break;

case "CategoryName":

categories = categories.OrderBy(c => c.CategoryName);

break;

case "CategoryName_desc":

categories = categories.OrderByDescending(c => c.CategoryName);

break;

case "IsUse":

categories =categories.OrderBy(c =>c.IsUsed);

break;

case "IsUse_desc":

categories = categories.OrderByDescending(c => c.IsUsed);

break;

default:

categories =categories.OrderBy(c =>c.CategoryNumber);

break;

}

int pageSize = 10;

foreach (var category in categories) //刷新Index页的时候就更新Category 类别 的使用状态(使用还是未使用)

{

// if (category.ReviewProjects.Count >0 || category.ReviewIndexItems.Count >0 || category.ReviewIndexSystems.Count>0) 使用属性返回Enumber元素的个数。也可以使用Count()方法返回。返回计数器的值

if(category.ReviewProjects.Any() ||category.ReviewIndexItems.Any() || category.ReviewIndexSystems.Any()) //Any()的性能优于count().

{

category.IsUsed = true;

}

else

{

category.IsUsed = false;

}

}

db.SaveChanges();

return View(categories.ToPagedList(page, pageSize));

}

主视图 Index.cshtml

@model PagedList.IPagedList<UniversalReviewSystem.Models.Category>

@using PagedList.Mvc;

@{

ViewBag.Title = "评审项目类别";

}

<h2>@ViewBag.Title</h2>

<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#categoryModal" >新建类别</button>

<table class="table table-hover table-striped">

<thead>

<tr>

<th>

序号

</th>

<th>

@Html.ActionLink("评审项目类别编号", "Index", new { sortOrder = ViewBag.SortByNumber })

</th>

<th>

@Html.ActionLink("评审项目类别", "Index", new { sortOrder =ViewBag.SortByCategoryName })

</th>

<th>

@Html.ActionLink("创建时间", "Index", new { sortOrder = ViewBag.SortByCreateTime })

</th>

<th>

@Html.ActionLink("是否使用", "Index", new { sortOrder = ViewBag.SortByIsUse })

</th>

<th></th>

</tr>

</thead>

<tbody>

@{ int count = 0;}

@foreach (var item in Model)

{

<tr>

<td>

@{ count = count + 1;} @* 给行加序号 *@

@count

</td>

<td>

@Html.DisplayFor(modelItem =>item.CategoryNumber)

</td>

<td>

@Html.DisplayFor(modelItem => item.CategoryName)

</td>

<td>

@Html.DisplayFor(modelItem => item.CategoryCreateTime)

</td>

<td>

@(item.IsUsed ? "已使用" : "未使用")

</td>

<td>

@* @Html.ActionLink("编辑", "Edit", new { id = item.CategoryID })*@

<a href="javascript:updateCategory(\'@item.CategoryID\', \'edit\')">编辑</a> @* item.CategoryID 必须加引号,因为它本身不是数字,而是字符,为string 类型*@ 更新、删除操作使用同一个javascript函数。

@if (!item.IsUsed) //如果该类别已经使用,则在视图不能点击删除按钮

{

<text> |</text>

@* @Html.ActionLink("删除", "Delete", new { id = item.CategoryID })*@

<a href="javascript:updateCategory(\'@item.CategoryID\', \'delete\')">删除</a>

}

</td>

</tr>

}

</tbody>

<tfoot>

<tr>

<td class="text-muted" colspan="4">

每页 @Model.PageSize 条记录,共有 @Model.TotalItemCount 条记录。第 @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) 页,共 @Model.PageCount 页。

</td>

</tr>

</tfoot>

</table>

@Html.PagedListPager(Model, page => Url.Action("Index", new { page, sortOrder=ViewBag.CurrentSort}),

new PagedListRenderOptions { LinkToFirstPageFormat = "首页", LinkToNextPageFormat = "下一页", LinkToPreviousPageFormat = "上一页", LinkToLastPageFormat = "末页", MaximumPageNumbersToDisplay = 3, DisplayItemSliceAndTotal = false }

)

@{ Html.RenderPartial("_Category.Modal");} //使用模态框作为分部视图存放在主视图中 @*模态框 在视图层级中越往外层越好。*@

@section Scripts{

@Scripts.Render("~/bundles/jqueryval") //加载jqueryval验证文件。

<script>

$(function () {

$("#triggerModal-btn").click(function () {

$("#categoryModal #catetoryModalLabel").text("新建项目类别")

$("#categoryModal #operateCategory-btn").text("新建");

$("#categoryModal #categoryPreview").load(\'@Url.Action("GetEmptyCategory")\'); //先弹出模态框,再Ajax加载内容。

});

//将父元素加载一个事件, 单击它的子元素后就执行。jquery中绑定事件的方法有bind,delegate等多种,但on方法是最新的,最好的。

$("#categoryModal").on("click", "#operateCategory-btn", function () {

$("#categoryModal #categoryForm").submit();

});

});

function updateCategory(id, action) {

var currentId = id;

switch (action) {

case \'edit\':

$.post("/Category/GetEditCategory", { id: currentId }).success(function (data) {

$("#categoryModal #categoryPreview").html(data); //使用ajax返回的数据使用 jquery的html()获得的数据,只能读取,不能更改。 因此修改ajax 返回的表单上执行 $("#categoryForm").attr(\'action\', \'@Url.Action("Edit")\');会不起作用

});

$("#categoryModal #catetoryModalLabel").text("编辑项目类别");

$("#categoryModal #operateCategory-btn").text("确认修改");

// $("#categoryForm").attr(\'action\', \'@Url.Action("Edit")\'); //在javascript 中 只能使用控制器操作方法的如 @* @Url.Action("Delete") ,而不能使用带路由值的Url方法,如@Url.Action("Delete", new { id = currentId })。*@

//使用attr 属性与设置的值必须加引号,单引号或双引号都可以。

// $("#categoryForm").attr(\'action\', \'/Category/Edit\' + currentId);

$("#categoryModal").modal(\'show\');

break;

case \'delete\':

$.post("/Category/GetDeleteCategory", { id: currentId }).success(function (data) {

$("#categoryModal #categoryPreview").html(data); //使用ajax返回的数据使用 jquery的html()获得的数据,只能读取,不能更改。

});

$("#categoryModal #catetoryModalLabel").text("删除项目类别");

$("#categoryModal #operateCategory-btn").text("确认删除");

// $("#categoryForm").attr(\'action\', \'@Url.Action("Delete")\');

$("#categoryModal").modal(\'show\');

/*

$("#categoryModal #operateCategory-btn").click(function () {

$("#categoryModal #CategoryForm").submit();

});

*/

break;

default:

console.debug(\'Unknown action \' + action); //写给浏览器调试用的,按F12键会在浏览器 控制台窗口显示信息。

}

}

</script>

}

三、实现新增类别功能。

执行的顺序是:当单击主视图上的按钮触发模态框显示,然后更新模态框的标题及按钮文字,使用jquery load ajax方法访问控制器操作方法,返回分部视图表单到模态框。最后当单击模态框按钮的时候,提交表单。

(1)|主视图Index 中有触发模态框的按钮。

<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#categoryModal" >新建类别</button> //data-toggle ="modal" data-tartget="#categoryModal" 自动实现 单击按钮,触发#categoryModal.

当然,也可以通过 $("#categoryModal").modal(\'show\'),来触发显示模态框。

javascript:

$(function () {

$("#triggerModal-btn").click(function () {

$("#categoryModal #catetoryModalLabel").text("新建项目类别")

$("#categoryModal #operateCategory-btn").text("新建");

$("#categoryModal #categoryPreview").load(\'@Url.Action("GetEmptyCategory")\'); //先弹出模态框,再Ajax加载分部视图。

});

(2)新建一个增加 类别的模态框表单文件。_AddCategory.Modal.Preview.cshtml 。实际上也是从基架自动创建的Create 视图中直接拷贝作为此分部视图的内容。

_AddCategory.Modal.Preview.cshtml:

@model UniversalReviewSystem.Models.Category

<!-- Form标签中的id 大小写敏感,id =CategoryForm 会导致jquery 无响应-->

@using (Html.BeginForm("Create", "Category", FormMethod.Post, htmlAttributes: new { id = "categoryForm" }))

{

@Html.AntiForgeryToken()

<div class="form-horizontal">

@Html.ValidationSummary(true, "", new { @class = "text-danger" })

<div class="form-group">

@Html.LabelFor(model => model.CategoryNumber, htmlAttributes: new { @class = "control-label col-md-4" })

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

@Html.EditorFor(model => model.CategoryNumber, new { htmlAttributes = new { @class = "form-control", placeholder = "请输入评审项目类别编号,如01" } })<span class="help-block">评审类别编码不能与系统中已有的编码重复,否则,就会新建不成功。</span>

@Html.ValidationMessageFor(model => model.CategoryNumber, "", new { @class = "text-danger" })

</div>

</div>

<div class="form-group">

@Html.LabelFor(model => model.CategoryName, htmlAttributes: new { @class = "control-label col-md-4" })

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

@Html.EditorFor(model => model.CategoryName, new { htmlAttributes = new { @class = "form-control", placeholder = "请输入评审项目类别名称" } })<span class="help-block">项目类别名称如:人才培养方案。如果系统中存在此类别,就不用重新新建了。</span>

@Html.ValidationMessageFor(model => model.CategoryName, "", new { @class = "text-danger" })

</div>

</div>

@* <div class="form-group">

<div class="col-md-offset-2 col-md-10">

<input type="submit" value="新建" class="btn btn-default" />

</div>

</div>

*@

</div>

}

(3)新增一个控制器操作方法GetEmptyCategory,用于返回包含表单的 _AddCategory.Modal.Preview 分部视图文件,加载到模态框中。

[HttpGet]

[Authorize(Roles = "SuperAdministrator")]

public ActionResult GetEmptyCategory()

{

return PartialView("_AddCategory.Modal.Preview");

}

(4)因为使用jquery on方法注册,模态框按钮的单击事件,从而触发模态框,提交表单。因为模态框是加载的是 新增 类别 的表单,因此,新增视图的表单会被提交到Create控制器。

//将父元素加载一个事件, 单击它的子元素后就执行

$("#categoryModal").on("click", "#operateCategory-btn", function () {

$("#categoryModal #categoryForm").submit();

});

(5)执行Create Post控制器方法: 现在不需要get版本的方法和Create视图了。

[HttpPost]

[ValidateAntiForgeryToken]

public ActionResult Create([Bind(Include = "CategoryNumber,CategoryName")] Category category)

{

if (ModelState.IsValid)

{

ValidateCategoryNumberDumplicate(category);

}

if (ModelState.IsValid)

{

db.Categories.Add(category);

db.SaveChanges();

return RedirectToAction("Index");

}

var CategoryNumberErrorMessages = "";

foreach (var error in ModelState["CategoryNumber"].Errors)

{

CategoryNumberErrorMessages += error.ErrorMessage;

}

throw new Exception(CategoryNumberErrorMessages); //抛出异常不是一个验证错误的好方法。可以使用Remote 注解实现远程服务器验证。

}

//检查项目类别编号是否重复,如果重复,给模型状态的 类别代码字段设置错误信息,返回给视图。

private void ValidateCategoryNumberDumplicate(Category category)

{

if (category.CategoryNumber != null)

{

var dumplicateCategory = db.Categories.Where(c => c.CategoryNumber == category.CategoryNumber).AsNoTracking().SingleOrDefault();

if (dumplicateCategory != null && dumplicateCategory.CategoryNumber == category.CategoryNumber)

{

string errorMessage = string.Format("该编号与 {0} 评审项目类别的编号重复,请重新编号!!!", dumplicateCategory.CategoryName);

ModelState.AddModelError("CategoryNumber", errorMessage);

}

}

}

二、实现更新和删除功能。

1、在Index 视图中 每条记录的编辑、删除连接 改设置为执行javascript函数。

<td>

@* @Html.ActionLink("编辑", "Edit", new { id = item.CategoryID })*@

<a href="javascript:updateCategory(\'@item.CategoryID\', \'edit\')">编辑</a> @* item.CategoryID 必须加引号,因为它本身不是数字,而是字符,为string 类型*@

@if (!item.IsUsed)

{

<text> |</text>

@* @Html.ActionLink("删除", "Delete", new { id = item.CategoryID })*@

<a href="javascript:updateCategory(\'@item.CategoryID\', \'delete\')">删除</a>

}

</td>

2、执行主视图 Index中的 javascript 。执行顺序于增加类别 是一样的。通过jquery 访问控制器方法,返回相应分部视图表单加载到模态框,显示模态框,点击模态框的按钮触发对应的表单提交至控制器。完成操作。

function updateCategory(id, action) {

var currentId = id;

switch (action) {

case \'edit\':

$.post("/Category/GetEditCategory", { id: currentId }).success(function (data) {

$("#categoryModal #categoryPreview").html(data); //使用ajax返回的数据使用 jquery的html()获得的数据,只能读取,不能更改。 因此修改ajax 返回的表单上执行 $("#categoryForm").attr(\'action\', \'@Url.Action("Edit")\');会不起作用

});

$("#categoryModal #catetoryModalLabel").text("编辑项目类别");

$("#categoryModal #operateCategory-btn").text("确认修改");

// $("#categoryForm").attr(\'action\', \'@Url.Action("Edit")\'); //在javascript 中 只能使用控制器操作方法的如 @* @Url.Action("Delete") ,而不能使用带路由值的Url方法,如@Url.Action("Delete", new { id = currentId })。*@

//使用attr 属性与设置的值必须加引号,单引号或双引号都可以。

// $("#categoryForm").attr(\'action\', \'/Category/Edit\' + currentId);

$("#categoryModal").modal(\'show\');

break;

case \'delete\':

$.post("/Category/GetDeleteCategory", { id: currentId }).success(function (data) {

$("#categoryModal #categoryPreview").html(data); //使用ajax返回的数据使用 jquery的html()获得的数据,只能读取,不能更改。

});

$("#categoryModal #catetoryModalLabel").text("删除项目类别");

$("#categoryModal #operateCategory-btn").text("确认删除");

// $("#categoryForm").attr(\'action\', \'@Url.Action("Delete")\');

$("#categoryModal").modal(\'show\');

/*

$("#categoryModal #operateCategory-btn").click(function () {

$("#categoryModal #CategoryForm").submit();

});

*/

break;

default:

console.debug(\'Unknown action \' + action); //写给浏览器调试用的,按F12键会在浏览器 控制台窗口显示信息。

}

}

3、在相应控制器的视图文件夹中新建一个编辑类别和删除类别的分部视图表单 _EditCategory.Modal.Preview.cshtml和 _DeleteCategory.Modal.Preview.cshtml。只执行客户端验证。

_EditCategory.Modal.Preview.cshtml:

@model UniversalReviewSystem.Models.Category

@using (Html.BeginForm("Edit", "Category", FormMethod.Post,htmlAttributes: new { id = "categoryForm" }))

{

@Html.AntiForgeryToken()

<div class="form-horizontal">

@Html.ValidationSummary(true, "", new { @class = "text-danger" })

@* @Html.HiddenFor(model => model.CategoryID) *@

<input type="hidden" value="@Model.CategoryID" name="id" /> @* 在编辑和删除情况下,由基架自动生成的action和View中 会自动在表单标签的action 属性中生成controller/action/id 的值,默认是此视图的路由值,从而正确提交。但是换成了模态框,不存在了Get 方式的控制器了,因此,hiddenfor 辅助方法生成的name =CategoryID 与默认控制器接收的参数id的值不匹配,会找不到id值。*@

<div class="form-group">

@Html.LabelFor(model => model.CategoryNumber, htmlAttributes: new { @class = "control-label col-md-4" })

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

<p class="form-control-static">@Html.DisplayFor(model => model.CategoryNumber)</p> @*使用静态控件的方法*@

@*@Html.EditorFor(model => model.CategoryNumber, new { htmlAttributes = new { @class = "form-control" } })*@

@*<input type="text" value="@Model.CategoryNumber" class="form-control" disabled name="CategoryNumber" />*@

@Html.ValidationMessageFor(model => model.CategoryNumber, "", new { @class = "text-danger" })

</div>

</div>

<div class="form-group">

@Html.LabelFor(model => model.CategoryName, htmlAttributes: new { @class = "control-label col-md-4" })

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

@Html.EditorFor(model => model.CategoryName, new { htmlAttributes = new { @class = "form-control" } })

@Html.ValidationMessageFor(model => model.CategoryName, "", new { @class = "text-danger" })

</div>

</div>

@*

<div class="form-group">

<div class="col-md-offset-2 col-md-10">

<input type="submit" value="保存" class="btn btn-default" />

</div>

</div>*@

</div>

}

3、定义Edit 和Delete Post版本的操作方法,分别返回编辑和更新的 分部视图表单加载到模态框。

[HttpPost]

[Authorize(Roles = "SuperAdministrator")]

public ActionResult GetEditCategory(string id)

{

if (id == null)

{

return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

}

Category category = db.Categories.Find(id);

if (category == null)

{

return HttpNotFound();

}

return PartialView("_EditCategory.Modal.Preview", category);

}

[HttpPost]

[Authorize(Roles = "SuperAdministrator")]

public ActionResult GetDeleteCategory(string id)

{

if (id == null)

{

return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

}

Category category = db.Categories.Find(id);

if (category == null)

{

return HttpNotFound();

}

return PartialView("_DeleteCategory.Modal.Preview", category);

}

4、通过jquery 事件绑定,点击模态框中的按钮触发表单提交。

on 方法实现,于增加 类别相同。

5、表单提交至Edit 和Delete Post 版本的控制器方法,执行编辑和删除操作。

[HttpPost]

[ValidateAntiForgeryToken]

[ActionName("Edit")]

public ActionResult EditPost(string id)

{

if (id == null)

{

return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

}

var categoryToUpdate = db.Categories.Include(c => c.ReviewIndexItems).Include(c => c.ReviewIndexSystems).Include(c => c.ReviewProjects).SingleOrDefault(c => c.CategoryID == id);

if (TryUpdateModel(categoryToUpdate, new string[] { "CategoryName" }))

//TryUpdateModel或者UpdateModel 与[Bind(Include ="","") model] 的差别的是Bind白名单 使用 模型绑定的参数model去替换数据库中的白名单字段,使用的是参数model对象,如果白名单里面没有某个属性,就为空。因此,如果有构造方法中指定了创建时间,他会使用参数model中的默认创建的值去更新。

// 而TryUpdateModel(model,new string [] {"properties"})) 先在数据库中找到这个模型,然后,只更新这个模型的指定属性。如果不包括某些属性的话,就不会更新。因此,如果有构造方法中指定了创建时间,他也不会去更新。

{

// ValidateCategoryNumberDumplicate(categoryToUpdate); 在编辑操作方法中已经用不着验证编码是否重复了,因为编辑视图不修改类别编码;

if (categoryToUpdate.ReviewIndexItems.Count() >0 ||categoryToUpdate.ReviewIndexSystems.Count() >0 || categoryToUpdate.ReviewProjects.Count() >0)

{

categoryToUpdate.IsUsed = true;

}

else

{

categoryToUpdate.IsUsed = false;

}

//已不需要下面的语句,来设置数据库上下文跟踪实体的状态了,TryUpdateModel 显式绑定 已自动设置跟踪状态的EntityState.Modified标记。

// db.Entry(categoryToUpdate).State = EntityState.Modified;

db.SaveChanges();

if (Request.UrlReferrer != null)

{

var returnUrl = Request.UrlReferrer.ToString();

return new RedirectResult(returnUrl); //由于使用的是表单提交而非Ajax无刷新异步提交。所以使用jquery将表单提交到控制器后,返回Request.UrlReferrer返回到上一个页面将是数据库更新后的状态,筛选、排序、分页都保持不变。

}

return RedirectToAction("Index");

}

return View(categoryToUpdate);

}

// POST: Category/Delete/5

[HttpPost, ActionName("Delete")]

[ValidateAntiForgeryToken]

public ActionResult DeleteConfirmed(string id)

{

Category category = db.Categories.Find(id);

db.Categories.Remove(category);

db.SaveChanges();

if (Request.UrlReferrer != null)

{

var returnUrl = Request.UrlReferrer.ToString();

return new RedirectResult(returnUrl); //由于使用的是表单提交而非Ajax无刷新异步提交。所以使用jquery将表单提交到控制器后,返回Request.UrlReferrer返回到上一个页面将是数据库更新后的状态,筛选、排序、分页都保持不变。

}

return RedirectToAction("Index");

}

这样,就实现了使用模态框的增删改查功能。需要以下步骤就可以修改为模态框 。新建一个模态框并放置在视图中。将单独的页面视图修改成分部视图,定义返回分部视图的控制器方法,定义jqueryl访问控制器方法加载分部视图到模态框,通过模态框按钮提交表彰。

同时,可以删去get版本的方法。