65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (18投票s)

2007年10月19日

CPOL

6分钟阅读

viewsIcon

142230

downloadIcon

4263

JavaScript + AJAX 解决方案,用于网格中的行内编辑。

Screenshot - grid.gif

引言

如果你问人们,他们对基于 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 方法。
  1. 例如,我们有一个非常简单的 Order 类。我们希望在网格中显示所有订单。

    Screenshot - orderCRC.jpg

  2. 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();
    ...
  3. 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 是使每一行唯一所必需的。

  4. 现在,我们应该为每个“编辑区域”创建一个 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 用于将编辑过的值映射到传递给 onSaveEventHandlerretObj
    • areaPrefix – IEC 用于查找编辑区域。
    • dataSourceControlID – 包含预定义值集合的数据源控件 ID。
    • isFocused - 一个布尔值,指定编辑区域是否会获得焦点。

    onRenderPriorityValueonSelectPriorityValue 是在 IEC 之外实现的自定义处理程序。不可能实现所有编辑情况,对于特定情况,你必须创建自定义处理程序。例如,优先级列是一个图像。当行处于通常状态时,它显示优先级图标,但当行处于可编辑状态时,它应该显示选择框。

    Screenshot - priority.jpg

    onRenderPriorityValueonSelectPriorityValue 的代码超出了本文的范围;它将在第二部分(架构、可扩展性以及类似内容)中描述。

  5. 我们离完成越来越近了。让我们添加保存处理程序。IEC 对数据保存相当不关心,也不关心你将如何实现它。它所需要的只是一个完成所有工作的方法调用。
    function onSaveEventHandler(retOb)
    { 
        retObj.OrderID  = retObj.itemID;
        TargetProcess.DaoTraining.BusinessLogicLayer.OrderService.UpdateOrder(retObj,
                                           onRequestComplete,
                                           onErrorRequest);
    }

    UpdateOrder 方法将在保存事件时触发。它只有一个输入参数 retObj,其中包含所有编辑过的值。IEC 使用 EditAreaSettings 中的键(例如 shipNameFreight)将编辑过的值映射到 retObj。幸运的是,我为 IEC 映射的“编辑区域”Order 类保持一致,除了由 IEC 创建的 retObj.itemID。因此,retObj 可以转换为 Order 类。我不得不进行一次额外的赋值,以使 retObjOrder 完全一致。

    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 服务来保存订单,但这并非必需。这是一种非常灵活的方法,允许其他机制来保存数据。

© . All rights reserved.