Grid Inline Editing Using ASP.NET AJAX and Web Services

Introduction

If you ask people what is the most important thing for web based application UI most of them will answer “I want to edit data really fast. Like in Excel!” That a tough request. It means that person wants to click on a row, edit some data, then click on another row and have all data saved into database. We’ve found a solution during TargetProcess v.2.6 development. TargetProcess is a great agile project management software (if you are not aware 😉

Problem

ASP.NET framework provides inline editing functionality in GridView. But sometimes you get into situation when you cannot use this functionality:

  • You cannot use it with disabled View State
  • Performance is really bad. When you click button to switch the row into edit state, post back to the server will be initiated. Post back will happen when you save or cancel edit mode as well. Even if you put your grid into UpdatePanel, AJAX.NET still will update whole grid instead of one row and size of the date in post back remains almost the same (and large).

With bad performance it will not be like Excel, right? So we have to look for solution that:

  • Will not initiate post back to the server
  • Will work with disabled view state
  • Will be as fast as Excel (at least less than a second to make the action)

Solution

The Inline Edit Controller (IEC) is javascript solution that resolve all our problems (you will see it really soon). It has the following advantages:

  • It’s a cross-platform solution, you can use it in any environment ASP, ASP.NET, PHP, JSP etc.
  • It’s a cross-browser solution
  • It’s a very extensible solution, it’s pretty simple to extend the basic behavior by adding your custom handlers.
  • It can be easy integrated into existing applications without almost any rework. You can define custom saving methods to put edited values into database.
  • Basic principles

IEC is a plain javascript solution that allows editing rows in grid. In order to have editable row you should mark the row in some specific manner. Let’s call this stuff “Inline Edit Panel”. It contains a button that enables inline editing as well as buttons that save change and cancel editing. In fact the better way to go is to enable editing on double click and cancel it on Escape key. That will be shown in more advanced examples. So far we have simple buttons.

<asp:GridView GridLines="none" runat="server" AllowSorting="false" AutoGenerateColumns="false" ID="uxOrders">

  <asp:TemplateField>
       <ItemTemplate> 
            <span runat="server" 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>
... 

Cells that are going to be editable must be located in a specific place holder as well. Let’s call this stuff “Edit Area”.

<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>

As you see, “Inline Edit Panel” has 3 buttons with an extra “action” attribute. Each action attribute fires corresponding event. IEC handles all these events and makes corresponding changes in DOM. Let’s review all editing process:

1. User clicks Edit button

  • At this stage the row switches to editable state and input and select boxes appended to “Edit Areas”;
  • Usual text labels become invisible. It means that Freight label in the code above becomes invisible and input text field added to the span with id=”FreightIdPrefix777″

2. User changes some values and clicks Save button.

  • At this stage IEC extracts all values from editable fields (input, select, other if any);
  • Replace the labels text with new values from editable fields;
  • Create javascript object that holds the edited (new) values.
  • It’s time to save changes into database. IEC has no idea how to save new data, so it just passes the javascript object that holds new values into saving method.

3. User pushes Cancel button

  • At this stage IEC removes editable fields (“input”, “select”);
  • Labels become visible again

Usage Example

Idea looks really simple. But maybe the solution is hard to use, who knows… Only real example can help us to judge the solution.

Let’s take ASP.NET example with:

  • GridView control to present a data.
  • Web Service to retrieve the data and save modified data.
  • AJAX.NET to generate web methods that can be used from javascript.

1) For example, we have a pretty simple Order class. We want to show all orders in a grid.

2) GridView initialization is no-brainer as well. OrderService is a class that can retrieve orders from database (we don’t care how, maybe GetAllOrders method uses NHiberate or maybe old plain SQL)

protected override void OnLoad(EventArgs e)
{
  if (!IsPostBack)
  {
     OrderService orderService = new OrderService();
     Order[] orders = orderService.GetAllOrders();
     uxOrders.DataSource = orders;
     uxOrders.DataBind();
...

3) IEC mapping. As mentioned above, we should add columns with inline editing controls (Inline Edit Panel).

<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> 

And insert all required “Edit Areas”.

<asp:GridView 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> 
...

As you see, the “Edit Areas” have composite id that consists of two parts “Edit Area prefix + itemID”. Such id is required to make each row unique.

4) Now we should create IEC instance and the EditAreaSettings instances (see below) for each “Edit Area”.

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);

new EditAreaSettings( areaName, areaPrefix , dataSourceControlID, isFocused)

arguments:
areaName – used by IEC to map edited values to the retObj that is passed into onSaveEventHandler
areaPrefix – used by IEC to find the editArea
dataSourceControlID – data source control id that has a collection of predifined values

isFocused – boolean value that specifies whether edit Area will have a focus.

onRenderPriorityValue and onSelectPriorityValue are custom handlers that implemented outside of IEC. It impossible to implement all cases of editing and for specific situations you have to create custom handlers. For example, Priority column is an image. When row is in usual state it shows the priority icon, but when the row is in editable state it should show the select box.

The code of onRenderPriorityValue, onSelectPriorityValue handler are out of scope for this article, it will be described in Part II (architecture, extensibility and stuff like that).

5) We are getting closer to the final. Let’s add saving handler. IEC is quite agnostic to data saving and does not care how you will implement that. All it needs is a method call that do all the job.

function onSaveEventHandler(retOb)
 { 
    retObj.OrderID  = retObj.itemID;
    TargetProcess.DaoTraining.BusinessLogicLayer.OrderService.UpdateOrder(retObj,
                                       onRequestComplete,
                                       onErrorRequest);
 }

UpdateOrder method will be fired on save event. It has only one input parameter retObj which holds all edited values. IEC maps the edited values into retObj using the keys from EditAreaSettings (for example, shipName, Freight ). Fortunately, I maped the “Edit Areas” for IEC to be consistent with Order class except retObj.itemID that is created by IEC. As a result retObj can be casted to Order class. I had to do one extra assignment to make retObj completely consistent with Order

retObj.OrderID = retObj.ItemID

onSaveEventHandler handler is completely consistent with web method.

[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 + "'.";
   }
 …

I’m using the web service to save the order, but it’s not necessary. It’s a pretty flexible approach that allows other mechanism to save the data.

In part II I will show inner architecture of Inline Edit Controller as well as some specific features and customizations.

TargetProcess v.2.6 Released! (Using your “senses” to Iteration Planning? You bet! Now available in TargetProcess)

TargetProcess v.2.6 released today! We were targeting better user experience in the latest release. We took time to polish things and enhance the user interface by making it cleaner and easier to navigate. New iteration planning concept is just great, we are very excited about it! It visualizes the most important parameters of user stories and bugs such as effort and priority, thus providing real enjoyment during iteration planning sessions. You really feel user story effort and business value when making a decision about the assignment to iteration.

Inline editing in lists is an outstanding productivity feature. Double click on a row, change required info and simply hit Enter. No page reloads, no waiting! TargetProcess in many aspects is getting closer to usual desktop applications, which is far from reality in most of the web based apps out there.

There are many other smaller features that makes life easier like new filters, customizable inner lists, new chart, etc. We are continuing to improve TargetProcess usability to make it as easy to use as possible.