ERP俱乐部
ERP爱好者、ERP从业者互相交流、互相学习的乐园;我们的愿景是成为全球一流的中文ERP(Enterprise Resource Planning)交流平台
网站首页 论坛首页 搜索 用户列表 FAQ 注册 登录  
ERP俱乐部 -> Microsoft专栏 -> ASP.NET -> 微软MVC框架实战:Web开发化繁为简(下)
  微软MVC框架实战:Web开发化繁为简(下)
帖子发起人: 半神   发起时间: 2012-02-14 10:48 上午   回复数: 0
? 上一主题 下一主题 ?
楼主
  2012-02-14, 10:48 上午
半神 离线,最后访问时间: 2013/6/5 18:59:05 半神

发帖数前25位

超级管理员
职务: 超级管理员
80级
等级: 80级
注册: 2008年1月6日
区域: 华南
经验: 1,267
积分: 1,131
精华: 2
发贴: 590
排名: 26
Site AdministratorsGlobal ModeratorsSite ModeratorsSite Registered Users培训学员(MM学员服务区-北京200708班) 培训学员(FI学员服务区-深圳200805班) 每日发帖之星
微软MVC框架实战:Web开发化繁为简(下)
 

验证错误

  我们可通过一个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(initialLineItemsIdea);
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


分享按钮 IP 地址: 已登录   来自: 已登录    返回顶部
 第 1 页 总共 1 页 [共有 1 条记录]
ERP俱乐部 -> Microsoft专栏 -> ASP.NET -> 微软MVC框架实战:Web开发化繁为简(下)
(C)Copyright 2005-2020 www.erpclub.org All Rights Reserved.
Tel:+86-755-26444630
Email:webmaster@yok.com.cn