繼上一篇"MVC無限級分類01,分層架構,引入緩存,完成領域模型與視圖模型的映射",本篇開始MVC無限級分類的增刪改查部分,源碼在github。javascript
顯示和查詢css
使用datagrid顯示數據,向控制器發出請求的時候,datagrid默認帶上了當前頁page和頁容量rows這2個參數。若是項目中的其它界面也用datagrid展現數據,咱們能夠把page和rows封裝成一個基類:html
namespace Car.Test.Portal.Models.QueryParams
{
public class QueryParamsBase
{
public int PageSize { get; set; }
public int PageIndex { get; set; }
}
}
而當輸入搜索條件時,datagrid的queryParams屬性能接收json格式的搜索條件,這時候,datagrid向控制器發出請求,不單單有page和rows這2個參數,還包括了搜索條件中的鍵值對。能夠把與每一個模型對應的搜索條件封裝成一個繼承QueryParamsBase的類:java
using System;
namespace Car.Test.Portal.Models.QueryParams
{
public class CarCategoryQueryParams : QueryParamsBase
{
public string Name { get; set; }
public DateTime? JoinStartTime { get; set; }
public DateTime? JoinEndTime { get; set; }
}
}
與搜索和顯示相關的html部分爲:node
<!--查詢開始-->
<div id="query" class="query" >
<span>類名:</span>
<input id="txtName" style="line-height:22px;border:1px solid #ccc" maxlength="10">
<span>提交時間從:</span>
<input type="text" name="startSubTime" id="startSubTime" style="line-height:22px;border:1px solid #ccc" />
<span>至:</span>
<input type="text" name="endSubTime" id="endSubTime" style="line-height:22px;border:1px solid #ccc" />
<a href="#" class="easyui-linkbutton" plain="false" id="btnSearch">搜索</a>
</div>
<!--查詢結束-->
<!--表格開始-->
<div class="ctable" id="ctable">
<table id="tt"></table>
</div>
<!--表格結束-->
js部分須要完成的工做包括:
●限制搜索條件中類名的長度
●搜索條件中的時間用jqueryui的datapicker來實現,當在起始時間選擇了一個日期,結束日期的選擇範圍相應變成該天之後
●起始日期和結束日期只能經過datapicker設置,因此要把2個文本框設置爲只讀
●點擊搜索按鈕,把搜索添加封裝成json對象傳遞個datagrid的queryParams屬性jquery
<script type="text/javascript">
$(function() {
//限制類名的長度
limitInputLength($('#txtActionName'));
//限制時間搜索爲只讀
$('#startSubTime').attr('readonly', true);
$('#endSubTime').attr('readonly', true);
//起始和結束日期
fromDateToDate();
//顯示列表
initData();
//搜索
$('.query').on("click", "#btnSearch", function () {
initData(initQuery());
});
});
//限制文本框長度
function limitInputLength(_input) {
var _max = _input.attr('maxlength');
_input.bind('keyup change', function () {
if ($(this).val().length > _max) {
($(this).val($(this).val().substring(0, _max)));
}
});
}
//起始和結束日期
function fromDateToDate() {
$('#startSubTime').datepicker({
dateFormat: "yy-mm-dd",
changeMonth: true,
changeYear: true,
numberOfMonths: 2,
onClose: function (selectedDate) {
$("#endSubTime").datepicker("option", "minDate", selectedDate);
}
});
$('#endSubTime').datepicker({
dateFormat: "yy-mm-dd",
changeMonth: true,
changeYear: true,
numberOfMonths: 2,
onClose: function (selectedDate) {
$("#startSubTime").datepicker("option", "maxDate", selectedDate);
}
});
}
//獲取查詢表單的值組成json
function initQuery() {
var queryParams = {
txtName: $('#txtName').val(),
startSubTime: $('#startSubTime').val()
};
return queryParams;
}
//顯示分類
function initData(params) {
$('#tt').datagrid({
url: '@Url.Action("GetCarCategoryJson","Home")',
title: '分類列表',
//width: 800,
height: 390,
fitColumns: true,
nowrap: true,
showFooter: true,
idField: 'ID',
loadMsg: '正在加載信息...',
pagination: true,
singleSelect: false,
queryParams: params,
pageSize: 10,
pageNumber: 1,
pageList: [10, 20, 30],
//toolbar: '#query',
columns: [
[
{ field: 'ck', checkbox: true, align: 'center', width: 30 },
{ field: 'ID', title: '編號' },
{ field: 'Name', title: '類名' },
{ field: 'PreLetter', title: '前綴字母' },
{ field: 'ParentID', title: '父級ID' },
{ field: 'Level', title: '層級' },
{ field: 'IsLeaf', title: '是否葉節點' },
{
field: 'DelFlag',
title: '刪除狀態',
formatter: function (value, row, index) {
if (value == "0") {
return '使用中';
} else if (value == "1") {
return '邏輯刪除';
} else {
return '物理刪除';
}
}
},
{
field: 'SubTime',
title: '修改時間',
formatter: function (value, row, index) {
return eval("new " + value.substr(1, value.length - 2)).toLocaleDateString();
}
}
]
],
toolbar: [
{
id: 'btnAdd',
text: '添加',
iconCls: 'icon-add',
handler: function () {
showAdd();
}
}, '-', {
id: 'btnUpdate',
text: '修改',
iconCls: 'icon-edit',
handler: function () {
var rows = $('#tt').datagrid("getSelections");
if (rows.length != 1) {
$.messager.alert("提示", "只能選擇一個分類進行編輯");
return;
}
//修改方法
showEdit(rows[0].ID);
}
}, '-', {
id: 'btnDelete',
text: '刪除',
iconCls: 'icon-remove',
handler: function () {
var rows = $('#tt').datagrid("getSelections");
if (rows.length < 1) {
$.messager.alert("提示", "請選擇要刪除的分類");
return;
}
$.messager.confirm("提示信息", "肯定要刪除嗎?", function (r) {
if (r) {
var strIds = "";
for (var i = 0; i < rows.length; i++) {
strIds += rows[i].ID + '_'; //1_2_3
}
$.post("@Url.Action("DeleteCarCategories", "Home")", { ids: strIds }, function (data) {
if (data.msg == "no") {
$.messager.alert("提示", "刪除失敗!");
$('#tt').datagrid("clearSelections");
return;
} else if (data.msg) {
$.messager.alert("提示", "刪除成功");
initData(initQuery());
$('#tt').datagrid("clearSelections");
}
});
}
});
}
}
],
OnBeforeLoad: function (param) {
return true;
}
});
}
</script>
控制器部分,把接收到的有關分頁或查詢的參數封裝成一個類做爲參數交給服務層的一個方法,這裏爲了方便,把本該在服務層的方法直接寫在了控制器內。最後把獲得的集合封裝成json對象,並知足datagrid所指望的格式傳遞到前臺視圖。git
private string txtName = string.Empty;
private DateTime? startSubTime = null;
private DateTime? endSubTime = null;
public ICarCategoryRepository CarCategoryRepository { get; set; }
public HomeController(ICarCategoryRepository carCategoryRepository)
{
this.CarCategoryRepository = carCategoryRepository;
}
public HomeController():this(new CarCategoryRepository()){}
#region 顯示分類
public ActionResult Index()
{
return View();
}
//顯示全部分類並考慮查詢和分頁
public ActionResult GetCarCategoryJson()
{
//獲取datagrid傳來的2個參數
int pageIndex = int.Parse(Request["page"]);
int pageSize = int.Parse(Request["rows"]);
//獲取搜索參數
if (!string.IsNullOrEmpty(Request["txtName"]))
{
txtName = Request["txtName"];
}
if (!string.IsNullOrEmpty(Request["startSubTime"]))
{
startSubTime = DateTime.Parse(Request["startSubTime"]);
}
if (!string.IsNullOrEmpty(Request["endSubTime"]))
{
endSubTime = DateTime.Parse(Request["endSubTime"]);
}
//初始化查詢實例
var temp = new CarCategoryQueryParams
{
PageIndex = pageIndex,
PageSize = pageSize,
Name = txtName,
JoinEndTime = endSubTime,
JoinStartTime = startSubTime
};
//獲取全部知足條件的數據,並得到總記錄數
int totalNum = 0;
var allCarCategory = LoadPageCarCategoryData(temp, out totalNum);
//投影出須要傳遞到前臺的數據
var result = from c in allCarCategory
select new
{
c.ID,
c.Name,
c.IsLeaf,
c.Level,
c.ParentID,
c.PreLetter,
c.Status,
c.DelFlag,
c.SubTime
};
//構建datagrid所須要的json格式
var jsonResult = new { total = totalNum, rows = result };
return Json(jsonResult, JsonRequestBehavior.AllowGet);
}
//根據查詢條件得到分頁數據
private IEnumerable<CarCategory> LoadPageCarCategoryData(CarCategoryQueryParams param, out int total)
{
var allCarCategories =
CarCategoryRepository.LoadEntities(
c => c.Status == (short) StatusEnum.Enable && c.DelFlag == (short) DelFlagEnum.Normal);
if (!string.IsNullOrEmpty(param.Name))
{
allCarCategories = allCarCategories.Where(c => c.Name.Contains(param.Name));
}
if (!string.IsNullOrEmpty(param.JoinStartTime.ToString()) &&
!string.IsNullOrEmpty(param.JoinEndTime.ToString()))
{
allCarCategories =
allCarCategories.Where(c => c.SubTime >= param.JoinStartTime && c.SubTime <= param.JoinEndTime);
}
total = allCarCategories.Count();
IEnumerable<CarCategory> result = allCarCategories
.OrderByDescending(c => c.ID)
.Skip(param.PageSize*(param.PageIndex - 1))
.Take(param.PageSize);
return result;
}
#endregion
添加github
在主視圖,即datagrid所在頁面視圖,放置顯示添加彈出窗口的div,裏面有一個iframe。ajax
在視圖剛加載完的時候,先把添加div隱藏掉。json
$(function() {
//隱藏元素
initialHide();
});
//隱藏元素
function initialHide() {
$('.addContent').css("display", "none");
$('.editContent').css("display", "none");
}
當點擊datagrid添加按鈕,向控制器方法/Home/AddCarCategory發送一個ajax的get請求,若是請求到數據,ifram指向到新的視圖,顯示添加div,並以模態窗口的形式彈出。
//添加對話框
function showAdd() {
var url = "/Home/AddCarCategory";
$.ajax({
cache: false,
url: url,
contentType: 'application/html;charset=utf-8',
type: "GET",
success: function (result) {
if (result) {
$("#frameAdd").attr("src", url);
$("#addContent").css("display", "block");
$('#addContent').dialog({
toolbar: [],
maximizable: false,
draggable: true,
resizable: false,
closable: false,
modal: true,
cache: false,
//height: '100%',
width: 560,
height: 480,
top:50,
title: "添加類別",
onOpen: function () {
},
buttons: []
});
}
},
error: function (xhr, status) {
alert("加載內容失敗" + status);
}
});
}
當iframe指向的視圖執行完畢後仍是要返回到datagrid所在視圖界面的,能夠把iframe指向的視圖頁看做子窗口,從datagrid主視圖跳出的模態窗口看做是父窗口,讓子窗口執行完畢後,調用父窗口的方法。父窗口被子窗口調用的添加成功或取消添加的方法爲:
//添加成功後
function refreshAfterAdd() {
initData();
$('#tt').datagrid("clearSelections");
$('.addContent').dialog("close");
}
//取消添加
function cancelAdd() {
$('.addContent').dialog("close");
}
添加div中iframe指向的視圖頁面,在邏輯上能夠分紅2部分,一部分是以視圖模型以及模型驗證有關的,咱們把它方在一個強類型部分視圖中,再經過jquery異步加載它。另一部分就是添加按鈕和取消,點擊它們,調用父窗口的方法。
控制器中的 AddCarCategory()用來顯示添加div中iframe指向的視圖頁面,AddCarCategory(CarCategoryVm carCategoryVm)用來處理添加,AddModel()用來顯示強類型部分視圖,在AddCarCategory.cshtml視圖中經過jquery異步加載。控制器部分爲:
#region 添加分類
public ActionResult AddCarCategory()
{
//return PartialView(new CarCategoryVm());
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCarCategory(CarCategoryVm carCategoryVm)
{
if (ModelState.IsValid)
{
carCategoryVm.Status = (short)StatusEnum.Enable;
carCategoryVm.DelFlag = (short)DelFlagEnum.Normal;
carCategoryVm.SubTime = DateTime.Now;
CarCategory carCategory = AutoMapper.Mapper.DynamicMap<CarCategoryVm, CarCategory>(carCategoryVm);
CarCategoryRepository.AddEntity(carCategory);
CarCategoryRepository.SaveChanges();
return Json(new {msg = true});
}
else
{
//return PartialView(carCategoryVm);
return PartialView("AddModel", carCategoryVm);
}
}
//[ChildActionOnly]
public ActionResult AddModel()
{
return PartialView(new CarCategoryVm());
}
#endregion
#region 顯示全部分類的樹
public string LoadAllCategories()
{
var temp =
CarCategoryRepository.LoadEntities(
c => c.DelFlag == (short) DelFlagEnum.Normal && c.Status == (short) StatusEnum.Enable);
var result = from c in temp
select new {c.ID, c.Name, c.ParentID};
//return Json(result, JsonRequestBehavior.AllowGet);
return JsonSerializeHelper.SerializeToJson(result);
}
#endregion
AddModel.cshtml強類型部分視圖,咱們但願點擊id="getCategory"的input的時候,顯示zTree樹,而點擊zTree節點的時候,id="getCategory"的input顯示節點名稱,把真正的ID保存到id="categoryId" name="ParentID"的隱藏域中。
@model Car.Test.Portal.Models.CarCategoryVm
@using (Html.BeginForm("AddCarCategory", "Home", FormMethod.Post, new { id = "addForm" }))
{
@Html.AntiForgeryToken()
<ul>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.Name)
</span>
@Html.EditorFor(c => c.Name)
@Html.ValidationMessageFor(c => c.Name)
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.PreLetter)
</span>
@Html.EditorFor(c => c.PreLetter)
@Html.ValidationMessageFor(c => c.PreLetter)
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.ParentID)
</span>
<input id="getCategory" type="text" value="==請選擇分類==" />
@Html.ValidationMessageFor(c => c.ParentID)
<input type="hidden" id="categoryId" name="ParentID"/>
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.Level)
</span>
@Html.EditorFor(c => c.Level)
@Html.ValidationMessageFor(c => c.Level)
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.IsLeaf)
</span>
@Html.DropDownListFor(c => c.IsLeaf,new[]
{
new SelectListItem(){Text = "是",Value = bool.TrueString},
new SelectListItem(){Text = "否",Value = bool.FalseString}
},"==選擇是否爲葉節點==",new {id="ld"})
@Html.ValidationMessageFor(c => c.IsLeaf)
</li>
</ul>
}
AddCarCategory.cshtml視圖中,js和css部分需注意:
● zTree所須要的1個css文件和2個js文件一個都不能少
● zTree須要在ul的class必定是ztree,即<ul id="tree" class="ztree"...,不然會影響樹的顯示,本人就吃過這個苦頭,擅自把ztree改爲tree,爲這個問題糾結了一天!
● 關於jquery驗證的jquery.validate.js文件和jquery.validate.unobtrusive.js順序千萬不能顛倒。
● zTree的屬性較多,setting中的屬性,本人也調了不少次,如今看到的還不錯。
● 要把zTree所在的div設置成絕對定位,由於但願在顯示的時候經過js控制,讓它跑到對應的文本框正下方。
AddCarCategory.cshtml視圖主要工做包括:
● 頁面加載完畢,向控制器發送異步請求,把zTree樹先加載上,因爲隱藏了zTree所在的div,這時候還看不到
● 頁面加載完畢,項控制器發送異步請求,把強類型部分視圖加載到本頁。因爲是動態加載的內容,因此須要$.validator.unobtrusive.parse("form")此語句,讓動態部分視圖內容恢復客戶端驗證功能。
● 點擊取消按鈕,調用父窗口的取消添加方法
● 點擊添加按鈕,調用父窗口添加成功方法
● 因爲生成的是動態內容,根據"冒泡原理",須要不添加按鈕up等的點擊事件註冊到它的父級元素,相似這樣:$('#operate').on("click", "#up", function() {}
● 點擊部分視圖的id="getCategory"的input的時候,顯示zTree樹,而點擊zTree節點的時候,id="getCategory"的input顯示節點名稱,把真正的ID保存到id="categoryId" name="ParentID"的隱藏域中。
展開
修改
在主視圖,即datagrid所在頁面視圖,放置顯示修改彈出窗口的div,裏面有一個iframe。
在視圖剛加載完的時候,先把修改div隱藏掉。
$(function() {
//隱藏元素
initialHide();
});
//隱藏元素
function initialHide() {
$('.addContent').css("display", "none");
$('.editContent').css("display", "none");
}
當點擊datagrid修改按鈕,向控制器方法"/Home/EditCarCategory?發送一個ajax的get請求,若是請求到數據,ifram指向到新的視圖,顯示修改div,並以模態窗口的形式彈出。
//修改對話框
function showEdit(carCatId) {
var url = "/Home/EditCarCategory?id=" + carCatId;
$.ajax({
cache: false,
url: url,
contentType: 'application/html;charset=utf-8',
type: "GET",
success: function (result) {
if (result) {
$("#frameEdit").attr("src", url);
$("#editContent").css("display", "block");
$('#editContent').dialog({
toolbar: [],
maximizable: false,
draggable: true,
resizable: false,
closable: false,
modal: true,
cache: false,
//height: '100%',
width: 560,
title: "修改類別",
onOpen: function () {
},
buttons: []
});
}
},
error: function (xhr, status) {
alert("加載內容失敗" + status);
}
});
}
當iframe指向的視圖執行完畢後仍是要返回到datagrid所在視圖界面的,能夠把iframe指向的視圖頁看做子窗口,從datagrid主視圖跳出的模態窗口看做是父窗口,讓子窗口執行完畢後,調用父窗口的方法。父窗口被子窗口調用的修改爲功或取消修改的方法爲:
//修改爲功後
function refreshAfterEdit() {
initData();
$('#tt').datagrid("clearSelections");
$('.editContent').dialog("close");
}
//取消修改
function candelEdit() {
$('#tt').datagrid("clearSelections");
$('.editContent').dialog("close");
}
修改的控制器部分把從前臺視圖拿到的參數id存放到ViewData中,傳到到編輯子窗口,異步加載強類型部分視圖的時候再用到這個id。
#region 編輯分類
public ActionResult EditCarCategory(int id)
{
ViewData["id"] = id;
return View();
}
public ActionResult EditModel(int id)
{
var carCategoryDb = CarCategoryRepository.LoadEntities(c => c.ID == id).FirstOrDefault();
CarCategoryVm carCategoryVm = AutoMapper.Mapper.DynamicMap<CarCategory, CarCategoryVm>(carCategoryDb);
return PartialView(carCategoryVm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditCarCategory(CarCategoryVm carCategoryVm)
{
if (ModelState.IsValid)
{
carCategoryVm.Status = (short)StatusEnum.Enable;
carCategoryVm.DelFlag = (short)DelFlagEnum.Normal;
carCategoryVm.SubTime = DateTime.Now;
CarCategory carCategory = AutoMapper.Mapper.DynamicMap<CarCategoryVm, CarCategory>(carCategoryVm);
CarCategoryRepository.UpdateEntity(carCategory);
CarCategoryRepository.SaveChanges();
return Json(new {msg = true});
}
else
{
return PartialView("EditModel", carCategoryVm);
}
}
#endregion
編輯強類型部分視圖中, @Html.HiddenFor(c => c.ParentID,new {id = "categoryId"})隱藏域用來顯示由控制器方法傳遞的視圖模型中的ParentID值。而且,會根據這個ParentID把對應的節點名稱顯示到<input id="getCategory" type="text" value="==請選擇分類==" />上。
@model Car.Test.Portal.Models.CarCategoryVm
@using (Html.BeginForm("EditCarCategory", "Home", FormMethod.Post, new { id = "editForm" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(c => c.ID)
<ul>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.Name)
</span>
@Html.EditorFor(c => c.Name)
@Html.ValidationMessageFor(c => c.Name)
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.PreLetter)
</span>
@Html.EditorFor(c => c.PreLetter)
@Html.ValidationMessageFor(c => c.PreLetter)
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.ParentID)
</span>
<input id="getCategory" type="text" value="==請選擇分類==" />
@Html.ValidationMessageFor(c => c.ParentID)
@Html.HiddenFor(c => c.ParentID,new {id = "categoryId"})
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.Level)
</span>
@Html.EditorFor(c => c.Level)
@Html.ValidationMessageFor(c => c.Level)
</li>
<li>
<span class="fdwith">
@Html.LabelFor(c => c.IsLeaf)
</span>
@Html.DropDownListFor(c => c.IsLeaf,new[]
{
new SelectListItem(){Text = "是",Value = bool.TrueString},
new SelectListItem(){Text = "否",Value = bool.FalseString}
},"==選擇是否爲葉節點==",new {id="ld"})
@Html.ValidationMessageFor(c => c.IsLeaf)
</li>
</ul>
}
編輯視圖的原理與添加視圖相似,只不過要根據從控制器方法傳遞過來的視圖模型中的ParentID所對應的節點名稱顯示到強類型部分視圖的<input id="getCategory" type="text" value="==請選擇分類==" />上。
展開
批量刪除
在前臺視圖中,把拿到的id拼接成"1,2,3,"形式,而後在控制器方法中,把最後的逗號去掉,再Split成數組,把其中的元素放到List<int>集合中做爲服務層方法的參數,對領域模型實施邏輯刪除。視圖部分在查詢和顯示部分已有,控制器部分以下:
[HttpPost]
public ActionResult DeleteCarCategories()
{
var strIds = Request["ids"];
strIds = strIds.Substring(0, strIds.Length - 1);
string[] ids = strIds.Split('_');
List<int> list = new List<int>();
foreach (var item in ids)
{
int id = int.Parse(item);
list.Add(id);
}
if (DeleteCarCategoriesByBatch(list) > 0)
{
return Json(new {msg = true});
}
else
{
return Json(new { msg = "no"});
}
}
private int DeleteCarCategoriesByBatch(List<int> ids)
{
var carCategories = CarCategoryRepository.LoadEntities(c => ids.Contains(c.ID)).ToList();
for (int i = 0; i < carCategories.Count(); i++)
{
carCategories[i].DelFlag = (short)DelFlagEnum.Delete;
carCategories[i].SubTime = DateTime.Now;
}
return CarCategoryRepository.SaveChanges();
}
#endregion