验证错误 我们可通过一个CSS类以显示验证错误信息。CSS会循环遍历JSON返回的INPUT控件对象,验证其输入值是否合法如有错误则用红色标记高亮。代码如下: for (var val in result.ValidationErrors) {
var element = "#" + val;
$(element).addClass('validation-error');
}

Oder Entry Details视图 – Knockout 模版 在完成Order Shipping Information的编辑之后,用户可查看订单详细列表,并可向订单中添加产品。下面的Order Details View使用Knockout模版功能,实现了无需post–back的前提下,逐行编辑每一个line item。 Knockout 模版可轻松实现复杂的UI,例如不断重复与嵌套的Block。Knockout模版将模版渲染之结果填充至关联的DOM元素。 
预渲染与格式化数据 通常情况下,数据在前后端的结构与模式所有不同,特别是对于日期,货币等字段,此时就免不了数据的重新格式化。在传统的ASP.NET Web表单中,多数控件是通过预渲染或数据绑定事件,来实现数据到达给用户之前的重新格式化。在MVC中,我们可以抓取View Model数据,调用服务器端代码,实现在View开始阶段做预渲染操作。下例中,拿到重新格式化的数据后,我们生成了一个订单明细列表。 @model NorthwindViewModel.OrderViewModel
@{
ViewBag.Title = "Order Entry Detail";
ArrayList orderDetails = new ArrayList();
foreach (var item in Model.OrderDetailsProducts)
{
var orderDetail = new
{
ProductID = item.OrderDetails.ProductIDFormatted,
ProductName = item.Products.ProductName,
Quantity = item.OrderDetails.Quantity,
UnitPrice = item.OrderDetails.UnitPriceFormatted,
QuantityPerUnit = item.Products.QuantityPerUnit,
Discount = item.OrderDetails.DiscountFormatted
};
orderDetails.Add(orderDetail);
}
}
待数据完成格式化后,我们使用DIV标签加载编码后的JSON对象。稍后,JavaScript将访问该JSON对象,将数据绑定至knockout模版。 <div id="OrderDetailsData" style="visibility: hidden; display: none"> @Html.Raw(Json.Encode(orderDetails)); </div>
我们创建一个Knockout模版,如下。Script标签的类型为text/html,包含各种内容与数据绑定标签。 <!--====== Template ======-->
<script type="text/html" id="OrderDetailTemplate"> <tr data-bind="style: { background: viewModel.SetBackgroundColor($data) }"> <td style="height:25px"><div data-bind="text:ProductID"></div></td> <td><div data-bind="text: ProductName"></div></td> <td> <div data-bind="text: Quantity, visible:DisplayMode "></div> <div data-bind="visible: EditMode" > <input type="text" data-bind="value: Quantity" style="width: 50px" /> </div> </td> <td><div data-bind="text:UnitPrice"></div></td> <td><div data-bind="text: QuantityPerUnit"></div></td> <td><div data-bind="text: Discount, visible:DisplayMode "></div> <div data-bind="visible: EditMode" > <input type="text" data-bind="value:Discount" style="width:50px" /> </div> </td> <td> <div data-bind="visible:DisplayDeleteEditButtons"> <div style="width:25px;float:left"><img alt="delete" data-bind="click:function() { viewModel.DeleteLineItem($data) }" title="Delete item" src="@Url.Content("~/Content/Images/icon-delete.gif")"/> </div> <div style="width:25px;float:left"><img alt="edit" data-bind="click:function() { viewModel.EditLineItem($data) }" title="Edit item" src="@Url.Content("~/Content/Images/icon-pencil.gif")"/> </div> </div>
<div data-bind="visible:DisplayCancelSaveButtons"> <div style="width:25px;float:left"><img alt="save" data-bind="click: function() {viewModel.UpdateLineItem($data) }" title="Save item" src="@Url.Content("~/Content/Images/icon-floppy.gif")"/> </div> <div style="width:25px;float:left"><img alt="cancel edit" data-bind="click:function() { viewModel.CancelLineItem($data) }" title="Cancel Edit" src="@Url.Content("~/Content/Images/icon-pencil-x.gif")"/> </div> </div>
</td> </tr> </script>
要想将Knockout模版添加至HTML中,只需要使用data-bind模版标签与一个foreach语句即可。 <!--====== Container ======--> <table border="0" cellpadding="0" cellspacing="0" style="width:100%"> <tr class="DataGridHeader"> <td style="width:10%; height:25px">Product ID</td> <td style="width:30%">Product Description</td> <td style="width:10%">Quantity</td> <td style="width:10%">Unit Price</td> <td style="width:15%">UOM</td> <td style="width:10%">Discount</td> <td style="width:15%">Edit Options</td> </tr> <tbody data-bind='template: {name: "OrderDetailTemplate", foreach:LineItems}'> </tbody> </table>
JavaScript eval函数可作JSON对象的解析。不过,由于JavaScript eval可编译并运行任何JavaScript程序,会导致安全性问题。因此,较安全的做法是使用JSON解析器。JSON解析器只识别JSON文本,而 不会执行任何潜在风险的脚本。json.org网站中提供了许多JavaScript编写的JSON解析器。 使用JSON解析器,我们可以解析初始加载的订单明细数据,这些数据会与Knockout View Model实现绑定。当创建多个details line items时,我们需要创建一个数组,供Knockout监听。 <script language="javascript" type="text/javascript">
initialLineItems = jsonP***($("#OrderDetailsData").text());
var viewModel = { LineItems: ko.observableArray() }
ko.applyBindings(viewModel);
for (i = 0; i < initialLineItems.length; i++) { var newLineItem = CreateLineItem(initialLineItems ); viewModel.LineItems.push(newLineItem); }
var lineItemDisplay = function () {
this.ProductID; this.ProductName; this.Quantity; this.UnitPrice; this.QuantityPerUnit; this.Discount; this.OriginalQuantity; this.OriginalDiscount; this.EditMode; this.DisplayMode; this.DisplayDeleteEditButtons; this.DisplayCancelSaveButtons;
};
function CreateLineItem(LineItem) {
var lineItem = new lineItemDisplay();
lineItem.ProductID = ko.observable(LineItem.ProductID); lineItem.ProductName = ko.observable(LineItem.ProductName); lineItem.Quantity = ko.observable(LineItem.Quantity); lineItem.OriginalQuantity = ko.observable(LineItem.Quantity); lineItem.OriginalDiscount = ko.observable(LineItem.Discount); lineItem.UnitPrice = ko.observable(LineItem.UnitPrice); lineItem.QuantityPerUnit = ko.observable(LineItem.QuantityPerUnit); lineItem.Discount = ko.observable(LineItem.Discount); lineItem.BackgroundColor = ko.observable(LineItem.BackgroundColor); lineItem.EditMode = ko.observable(false); lineItem.DisplayMode = ko.observable(true); lineItem.DisplayDeleteEditButtons = ko.observable(true); lineItem.DisplayCancelSaveButtons = ko.observable(false);
return lineItem;
}
</script>
Knockout映射插件 上例中,我们采取的是自定义创建View Model的方式。另一种方式是采用Knockout映射插件,选择合适的映射规则,直截了将JavaScript对象与View Model绑定。 编辑,更新与删除Template Items 完整的页面Knockout View Model包含有line item的编辑,更新,删除。 <script language="javascript" type="text/javascript">
var viewModel = {
LineItems: ko.observableArray(), MessageBox: ko.observable(), AddNewLineItem: ko.observable(false),
SetBackgroundColor: function (currentLineItemData) { var rowIndex = this.LineItems.indexOf(currentLineItemData); var colorCode = rowIndex % 2 == 0 ? "White" : "WhiteSmoke"; return colorCode; },
EditLineItem: function (currentLineItemData) { var currentLineItem = this.LineItems.indexOf(currentLineItemData); this.LineItems()[currentLineItem].DisplayMode(false); this.LineItems()[currentLineItem].EditMode(true); this.LineItems()[currentLineItem].DisplayDeleteEditButtons(false); this.LineItems()[currentLineItem].DisplayCancelSaveButtons(true); },
DeleteLineItem: function (currentLineItemData) { var currentLineItem = this.LineItems.indexOf(currentLineItemData); var productName = this.LineItems()[currentLineItem].ProductName(); var productID = this.LineItems()[currentLineItem].ProductID();
ConfirmDeleteLineItem(productID, productName, currentLineItem); },
DeleteLineItemConfirmed: function (currentLineItem) { var row = this.LineItems()[currentLineItem]; this.LineItems.remove(row); },
CancelLineItem: function (currentLineItemData) {
currentLineItem = this.LineItems.indexOf(currentLineItemData); this.LineItems()[currentLineItem].DisplayMode(true); this.LineItems()[currentLineItem].EditMode(false); this.LineItems()[currentLineItem].DisplayDeleteEditButtons(true); this.LineItems()[currentLineItem].DisplayCancelSaveButtons(false);
this.LineItems()[currentLineItem].Quantity(this.LineItems() [currentLineItem].OriginalQuantity()); this.LineItems()[currentLineItem].Discount(this.LineItems() [currentLineItem].OriginalDiscount()); },
UpdateLineItem: function (currentLineItemData) {
currentLineItem = this.LineItems.indexOf(currentLineItemData); var lineItem = this.LineItems()[currentLineItem]; UpdateOrderDetail(lineItem, currentLineItem); },
UpdateOrderDetailComplete: function (currentLineItem, discount) {
this.LineItems()[currentLineItem].DisplayMode(true); this.LineItems()[currentLineItem].EditMode(false); this.LineItems()[currentLineItem].DisplayDeleteEditButtons(true); this.LineItems()[currentLineItem].DisplayCancelSaveButtons(false); this.LineItems()[currentLineItem].OriginalQuantity(this.LineItems() [currentLineItem].Quantity()); this.LineItems()[currentLineItem].OriginalDiscount(discount); this.LineItems()[currentLineItem].Discount(discount); } }
选择一个line item,点击铅笔编辑图标,EditLineItem函数会触发onclick事件,line item处于编辑模式。如下: EditLineItem: function (currentLineItemData) {
var currentLineItem = this.LineItems.indexOf(currentLineItemData);
this.LineItems()[currentLineItem].DisplayMode(false); this.LineItems()[currentLineItem].EditMode(true); this.LineItems()[currentLineItem].DisplayDeleteEditButtons(false); this.LineItems()[currentLineItem].DisplayCancelSaveButtons(true);
},
借助Knockout模版与Knockout绑定技术,我们可以创建类似ASP.NET Web Forms DataGrid控件的完整in-line编辑grid。

点击Add Line Item按钮,打开一个line item,可将一个item添加至order中。

使用modal popup窗口,可搜索一个Product Item。在一个新的line item上点击Search按钮,弹出product search 窗口。
The Modal Popup Product Search 窗口
Modal 弹出窗口是AJAX调用与Partial View的结合。AJAX 请求调用Product Inquiry partial view,返回product search的内容,最后填充至DIV标签。
<div id="dialog-modal" title="Product Inquiry"> <div id="ProductInquiryModalDiv"> </div> </div> Modal 弹出窗口是一个具有dialog功能的JQuery插件。
function ShowProductInquiryModal() {
var url = "/Products/BeginProductInquiry";
$.post(url, null, function (html, textStatus) { ShowProductInquiryModalComplete(html); });
}
function ShowProductInquiryModalComplete(productInquiryHtml) {
$("#ProductInquiryModalDiv").html(productInquiryHtml); $("#dialog-modal").dialog({ height: 500, width: 900, modal: true }); // // execute Product Inquiry query after the initial page content has been loaded // setTimeout("ProductInquiryInitializeGrid()", 1000);
}
Product Inquiry Search窗口 – UID生成机制
Product Inquiry Search窗口本身是一个Partial View。由于该窗口与Order Order页面加载的DOM一样,因此所有的HTML控件与动态创建的JavaScript函数及变量均要求名字独一无二。在渲染页面内容之前,该 Partial View实例化自定义的PageIDGeneration类,调用GenerateID方法,生成独一无二的控件ID,JavaScript函数名,以及 变量名。PageIDGeneration类通过设置unique Guid数目,保证生成ID的唯一性。
@model NorthwindViewModel.ProductViewModel @using NorthwindWebApplication.Helpers; @{
NorthwindWebControls.PageIDGeneration webControls = new NorthwindWebControls.PageIDGeneration();
string txtProductID = webControls.GenerateID("ProductID"); string txtProductDescription = webControls.GenerateID("ProductName"); string btnSearch = webControls.GenerateID("BtnSearch"); string btnReset = webControls.GenerateID("BtnReset"); string messageBox = webControls.GenerateID("MessageBox"); string productResults = webControls.GenerateID("ProductResults");
}
<div class="SearchBar"> <div style="float:left; width:200px"> Product ID </div> <div style="float:left; width:200px"> Product Description </div> <div style="clear:both;"></div> <div style="float:left; width:200px"> <input id="@txtProductID" type="text" value="" style = "width:150px" /> </div> <div style="float:left; width:200px "> <input id="@txtProductDescription" type="text" value="" style = "width:150px" /> </div> <input id="@btnSearch" type="button" value="Search" /> <input id="@btnReset" type="button" value="Reset"/> </div> <div style="clear:both;"></div> <div id="@productResults"></div> <div id="@messageBox"></div>
@Html.RenderJavascript(webControls.RenderJavascriptVariables("ProductInquiry_"))
<script language="javascript" type="text/javascript">
$(ProductInquiry_BtnSearch).click(function() { ProductInquiryInitializeGrid(); });
$(ProductInquiry_BtnReset).click(function() { $(ProductInquiry_ProductID).val(""); $(ProductInquiry_ProductName).val(""); ProductInquiryInitializeGrid(); });
function ProductInquiryRequest() { this.CurrentPageNumber; this.PageSize; this.ProductID; this.ProductName; this.SortDirection; this.SortExpression; this.PageID; };
function ProductInquiry(currentPageNumber, sortExpression, sortDirection) {
var url = "/Products/ProductInquiry";
var productInquiryRequest = new ProductInquiryRequest();
productInquiryRequest.ProductID = $(ProductInquiry_ProductID).val(); productInquiryRequest.ProductName = $(ProductInquiry_ProductName).val(); productInquiryRequest.CurrentPageNumber = currentPageNumber; productInquiryRequest.SortDirection = sortDirection; productInquiryRequest.SortExpression = sortExpression; productInquiryRequest.PageSize = 10; productInquiryRequest.PageID = $(ProductInquiry_PageID).val();
$.post(url, productInquiryRequest, function (data, textStatus) { ProductInquiryComplete(data); }); };
function ProductInquiryComplete(result) {
if (result.ReturnStatus == true) { $(ProductInquiry_ProductResults).html(""); $(ProductInquiry_ProductResults).html(result.ProductInquiryView); $(ProductInquiry_MessageBox).html(""); } else { $(ProductInquiry_MessageBox).html(result.MessageBoxView); }
}
function ProductInquiryInitializeGrid() { ProductInquiry(1, "ProductName", "ASC"); }
function ProductSelected(productID) { GetProductInformation(productID); }
</script>
总结 ASP.NET MVC是一个适用于大型Web应用开发的日益成熟的Web框架。MVC的架构思想是注重分离,对于具有Trial、Error、Discovery的 Web应用开发而言,MVC的学习曲线就显得与众不同。MVC与我们过去一直使用的ASP.NET Web Forms技术与Web Form post-back model技术完全不同。在未来,MVC开发者需要更加注重新兴框架与开源库,增强型MVC的开发。 本文重点关注 的是开源JavaScript库Knockout与JQuery,以及用于交换视图与控制器数据的JSON。建议MVC开发者也多多关注其它的开发工具与 框架,特别是Backbone与JavaScriptMVC。作为比较,后续的文章将会在示例程序Northwind中引入Backbone与 JavaScriptMVC。
开源时代的到来,对与技术人员是一个巨大的考验
QQ:876162454
|