使用 ASP.NET AJAX 和 Web 服务进行光速行内编辑:第一部分






4.81/5 (18投票s)
JavaScript + AJAX 解决方案,用于网格中的行内编辑。
引言
如果你问人们,他们对基于 Web 应用程序 UI 最重要的需求是什么,大多数人会回答“我希望像 Excel 一样快速编辑数据!”这是一个艰难的要求。这意味着用户希望点击一行,编辑一些数据,然后点击另一行,并将所有数据保存到数据库中。我们在 TargetProcess v.2.6 开发期间找到了解决方案。TargetProcess 是一款出色的敏捷项目管理软件(如果你不了解的话;)
问题
ASP.NET 框架在 GridView
中提供了行内编辑功能。但有时,你会遇到无法使用此功能的情况。
- 你无法在禁用 View State 的情况下使用它。
- 性能非常差。当你点击按钮将行切换到编辑状态时,会启动到服务器的回发。当你保存或取消编辑模式时,也会发生回发。即使你将网格放入
UpdatePanel
中,AJAX.NET 仍然会更新整个网格,而不是仅仅一行,并且回发中的数据大小几乎保持不变(并且很大)。
性能不佳,就无法像 Excel,对吧?所以,我们必须寻找一个解决方案,它
- 不会启动到服务器的回发
- 可以在禁用视图状态的情况下工作
- 像 Excel 一样快(至少,执行一个动作不到一秒)
解决方案
行内编辑控制器(IEC)是解决我们所有问题的 JavaScript 解决方案(你很快就会看到)。它具有以下优点
- 它是一个跨平台解决方案,你可以在任何环境中使用它:ASP、ASP.NET、PHP、JSP 等。
- 它是一个跨浏览器解决方案。
- 它是一个高度可扩展的解决方案;通过添加自定义处理程序来扩展基本行为非常简单。
- 它可以轻松集成到现有应用程序中,几乎无需任何返工。你可以定义自定义保存方法,将编辑过的值放入数据库中。
基本原则
IEC 是一个纯 JavaScript 解决方案,允许在网格中编辑行。为了使行可编辑,你应该以某种特定方式标记该行。我们称之为“行内编辑面板”。它包含一个启用行内编辑的按钮,以及保存更改和取消编辑的按钮。实际上,更好的方法是双击启用编辑,然后按 Escape 键取消编辑。这将在更高级的示例中显示。到目前为止,我们有简单的按钮
<asp:GridView CellPadding="0" CellSpacing="0" Width="1px"
GridLines="none" runat="server" AllowSorting="false"
AutoGenerateColumns="false" ID="uxOrders">
<asp:TemplateField>
<ItemTemplate>
<span runat="server" style="white-space: nowrap"
class="inlineEditIcon" inlineeditattribute="true"
rowid='<%# Eval("OrderID")%>'>
<img runat="server" action="edit"
title="Inline Edit" src="~/img/edit.gif" />
<img runat="server" style="display: none"
action="save" title="Save" src="~/img/save.gif" />
<img runat="server" style="display: none"
action="cancel" title="Cancel" src="~/img/cancel.gif" />
</span>
</ItemTemplate>
</asp:TemplateField>
...
要可编辑的单元格也必须位于特定的占位符中。我们称之为“编辑区域”。
<asp:TemplateField HeaderText="Freight">
<ItemTemplate>
<span id="FreightIdPrefix<%# Eval("OrderID")%>">
<asp:Label ID="Label1" runat="server"
Text='<%#Eval("Freight")%>'></asp:Label>
</span>
</ItemTemplate>
</asp:TemplateField>
如你所见,“行内编辑面板”有三个按钮,带有一个额外的“action
”属性。每个 action 属性都会触发相应的事件。IEC 处理所有这些事件并在 DOM 中进行相应的更改。现在让我们回顾一下编辑过程。
1. 用户点击编辑按钮
- 在此阶段,行切换到可编辑状态,并且输入框和选择框被附加到“编辑区域”。
- 通常的文本标签变得不可见。这意味着上面代码中的 Freight 标签变得不可见,并且输入文本字段被添加到
id="FreightIdPrefix777"
的 span 中。
2. 用户更改一些值并点击保存按钮
- 在此阶段,IEC 从可编辑字段(输入、选择、其他(如果有))中提取所有值。
- 用可编辑字段中的新值替换标签文本。
- 创建一个 JavaScript 对象,用于保存编辑过的(新)值。
- 现在是时候将更改保存到数据库了。IEC 不知道如何保存新数据,所以它只是将保存新值的 JavaScript 对象传递给保存方法。
3. 用户按下取消按钮
- 在此阶段,IEC 删除可编辑字段(“input”、“select”)。
- 标签再次可见。
使用示例:
这个想法看起来真的很简单。但也许解决方案很难使用,谁知道呢……只有真实的例子才能帮助我们判断解决方案。
让我们以一个 ASP.NET 示例为例
- 一个
GridView
控件来呈现数据。 - 一个 Web 服务来检索数据和保存修改后的数据。
- AJAX.NET 生成可以从 JavaScript 使用的 Web 方法。
- 例如,我们有一个非常简单的
Order
类。我们希望在网格中显示所有订单。 GridView
初始化也很简单。OrderService
是一个可以从数据库检索订单的类(我们不关心如何;也许GetAllOrders
方法使用 NHibernate,或者只是普通的 SQL)。protected override void OnLoad(EventArgs e) { if (!IsPostBack) { OrderService orderService = new OrderService(); Order[] orders = orderService.GetAllOrders(); uxOrders.DataSource = orders; uxOrders.DataBind(); ...
- IEC 映射。如上所述,我们应该添加带有行内编辑控件(行内编辑面板)的列。
<asp:TemplateField> <ItemTemplate> <span id="Span1" runat="server" style="white-space: nowrap" class="inlineEditIcon" inlineeditattribute="true" rowid='<%# Eval("OrderID")%>'> <img id="Img1" runat="server" action="edit" title="Inline Edit" src="~/img/edit.gif" /> <img id="Img2" runat="server" style="display: none" action="save" title="Save" src="~/img/save.gif" /> <img id="Img3" runat="server" style="display: none" action="cancel" title="Cancel" src="~/img/cancel.gif" /> </span> </ItemTemplate> </asp:TemplateField>
并插入所有必需的“编辑区域”。
<asp:GridView CellPadding="0" CellSpacing="0" Width="1px" GridLines="none" runat="server" AllowSorting="false" AutoGenerateColumns="false" ID="uxOrders"> <Columns> <asp:TemplateField> <ItemTemplate> <span priorityname='<%#Eval("Priority")%>' id="PriorityIdPrefix<%# Eval("OrderID")%>"> <span> <%#GetPriorityHTML(Container.DataItem)%> </span </span> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Freight"> <ItemTemplate> <span id="FreightIdPrefix<%# Eval("OrderID")%>"> <asp:Label ID="Label1" runat="server" Text='<%#Eval("Freight")%>'></asp:Label> </span> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Ship Name"> <ItemTemplate> <span id="ShipNameIdPrefix<%# Eval("OrderID")%>"> <asp:Label ID="Label1" runat="server" Text='<%#Eval("ShipName")%>'></asp:Label> </span> </ItemTemplate> </asp:TemplateField> ...
如您所见,“编辑区域”具有一个复合 ID,由两部分组成:“编辑区域前缀 + itemID”。此 ID 是使每一行唯一所必需的。
- 现在,我们应该为每个“编辑区域”创建一个 IEC 实例和
EditAreaSettings
实例(见下文)。var editAreasSettings = new Array(); //ShipName is a property of Order class //ShipNameIdPrefix is an id of span element //(EditArea of ShipName property) var shipNameEditAreaSettings = new EditAreaSettings("ShipName", "ShipNameIdPrefix",null, true); editAreasSettings.push(shipNameEditAreaSettings); var freightSettings = new EditAreaSettings("Freight","FreightIdPrefix"); freightSettings.onSelectValue = onSelectFreightValueHandler; editAreasSettings.push(freightSettings); var prioritySettings = new EditAreaSettings("Priority", "PriorityIdPrefix","uxPriorities"); prioritySettings.onRenderEditedValue = onRenderPriorityValue; prioritySettings.onSelectValue = onSelectPriorityValue; editAreasSettings.push(prioritySettings); var inlineEditController = new InlineEditController('<%=uxOrders.ClientID%>', editAreasSettings, onSaveEventHandler, true);
EditAreaSettings( areaName, areaPrefix , dataSourceControlID, isFocused)
参数
areaName
– IEC 用于将编辑过的值映射到传递给onSaveEventHandler
的retObj
。areaPrefix
– IEC 用于查找编辑区域。dataSourceControlID
– 包含预定义值集合的数据源控件 ID。- isFocused - 一个布尔值,指定编辑区域是否会获得焦点。
onRenderPriorityValue
和onSelectPriorityValue
是在 IEC 之外实现的自定义处理程序。不可能实现所有编辑情况,对于特定情况,你必须创建自定义处理程序。例如,优先级列是一个图像。当行处于通常状态时,它显示优先级图标,但当行处于可编辑状态时,它应该显示选择框。onRenderPriorityValue
和onSelectPriorityValue
的代码超出了本文的范围;它将在第二部分(架构、可扩展性以及类似内容)中描述。 - 我们离完成越来越近了。让我们添加保存处理程序。IEC 对数据保存相当不关心,也不关心你将如何实现它。它所需要的只是一个完成所有工作的方法调用。
function onSaveEventHandler(retOb) { retObj.OrderID = retObj.itemID; TargetProcess.DaoTraining.BusinessLogicLayer.OrderService.UpdateOrder(retObj, onRequestComplete, onErrorRequest); }
UpdateOrder
方法将在保存事件时触发。它只有一个输入参数retObj
,其中包含所有编辑过的值。IEC 使用EditAreaSettings
中的键(例如shipName
、Freight
)将编辑过的值映射到retObj
。幸运的是,我为 IEC 映射的“编辑区域”与Order
类保持一致,除了由 IEC 创建的retObj.itemID
。因此,retObj
可以转换为Order
类。我不得不进行一次额外的赋值,以使retObj
与Order
完全一致。retObj.OrderID = retObj.ItemID
onSaveEventHandler
处理程序与 Web 方法完全一致。[ScriptService] public class OrderService : WebService { [WebMethod] public string UpdateOrder(Order order) { OrderDao OrderDao = DaoFactory.GetDaoFactory().GetOrderDao(); bool isUpdated = OrderDao.UpdateOrder(order); if (isUpdated) return "The order '" + order.OrderID + "' is successfully updated."; else return "Unable to find order '" + order.OrderID + "'."; } …
我正在使用 Web 服务来保存订单,但这并非必需。这是一种非常灵活的方法,允许其他机制来保存数据。