Being an ASP.NET developer I think this is one of the most common of the requirements that we encounter in our day-to-day projects i.e., bulk editable grid. The gridview provided my Microsoft out-of-the-box will only allow editing one row at a time which in some cases is enough but there could be some scenarios where the complete grid needs to be editable. We have an implementation in ASP.NET Real world controls – BulkEditGridView but it seemed to me as way more complicated for regular usage in projects. Its licensed under Microsoft Public License which in Scott Hanselman’s words is “…is a VERY relaxed license that basically says ‘have fun, and don’t call if there’s trouble.’“ so we can use it but in case of any specific issues it requires us to go through the complicated code to figure out things. For sure its excellent and works perfectly well in most of the cases but I was thinking of having a much simpler implementation which is sufficient in most of the cases. One challenge here was detecting only the rows that were changed because its unnecessary as well as waste of time for processing unchanged rows.
Now for the implementation, its going to be direct usage of GridView with ItemTemplate and primary key fields as labels with css-style as hidden. Also, we would be having another hidden field which will detect the row that was edited. It’s important that we have our primary keys as labels (although hidden) because asp.net renders labels as <span> and hence that data won’t be present in the POST message back to server. This will save us from edits with tools like Fiddler.I have used the Northwind database Categories table for the sake of this blog post.
Beginning with aspx file we would be having a grid view with itemtemplate as follows for the Categories table
<asp:GridView ID="grdEditableGrid" runat="server" AutoGenerateColumns="false" OnRowDataBound="grdEditableGrid_RowDataBound"> <Columns> <asp:TemplateField HeaderText="CategoryID" ItemStyle-CssClass="hiddencol" HeaderStyle-CssClass="hiddencol" > <ItemTemplate> <asp:Label runat="server" ID="lblCategoryID" Text='<%# DataBinder.Eval(Container.DataItem, "CategoryID") %>' /> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="CategoryName"> <ItemTemplate> <asp:TextBox runat="server" ID="txtCategoryName" Text='<%# DataBinder.Eval(Container.DataItem, "CategoryName") %>' /> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Description"> <ItemTemplate> <asp:TextBox runat="server" ID="txtDescription" Text='<%# DataBinder.Eval(Container.DataItem, "Description") %>' /> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Edit Status" ItemStyle-CssClass=hiddencol HeaderStyle-CssClass=hiddencol> <ItemTemplate> <asp:HiddenField runat="server" ID="hdnEditStatus" Value='' EnableViewState="true" /> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
We will also have the following CSS class
<style type="text/css"> .hiddencol { display: none; } </style>
We also have the following javascript function which, as we will see, will play a key role in identifying the edited rows.
<script type="text/javascript"> function updateEditStatus(clientID) { var hiddenEditControl = document.getElementById(clientID); if (hiddenEditControl != null) { hiddenEditControl.value = "true"; } } </script>
Now going to the code behind we will have the same boiler-plate code to load data into the gridview but as seen in the aspx file we made the primary key column as label and set to css class which makes it hidden. Also, we have added another column Edit Status with as hidden field control named hdnEditStatus which will hold the edit status of each row. The code. Apart from the regular code that we write to populate the gridview control, for the purpose of editable gridview we need to do some more work in RowDataBound event. In this event we are going to add attributes for onkeyup client-side event for the textbox controls. We may add attributes related to other controls like dropdown list for client-side which will be used for detecting any change in the control’s data. These events will be calling the javascript function updateEditStatus so that the particular row on which action has been taken will have the hidden field marked as true so that on the server side we can consider them and process only those rows. This method can be used on any editable control that will be put on the gridview as long as it has client-side event to capture that the data in that control has changed.
protected void grdEditableGrid_RowDataBound(object sender, GridViewRowEventArgs e) { TextBox categoryName = e.Row.FindControl("txtCategoryName") as TextBox; if (categoryName != null) { categoryName.Attributes.Add("onkeyup", "updateEditStatus(\"" + e.Row.FindControl("hdnEditStatus").ClientID + "\");"); } TextBox description = e.Row.FindControl("txtDescription") as TextBox; if (description != null) { description.Attributes.Add("onkeyup", "updateEditStatus(\"" + e.Row.FindControl("hdnEditStatus").ClientID + "\");"); } }
Now upon button click we can easily capture the rows that were changed and a generic method getDirtyRows has been written so that it can be called easily multiple times to get the changed rows ids or may be the complete set of details. In the below implementation it returns all the CategoryIDs that were changed. This can be customized as per our requirement.
private DataTable getDirtyRows() { DataTable dtDirtyRows = new DataTable(); dtDirtyRows.Columns.Add("CategoryID"); foreach (GridViewRow item in grdEditableGrid.Rows) { HiddenField editStatus = item.FindControl("hdnEditStatus") as HiddenField; if (editStatus != null) { if (editStatus.Value == "true") { var lblCategoryID = item.FindControl("lblCategoryID") as Label; if (lblCategoryID != null) { DataRow dr = dtDirtyRows.Rows.Add(); dr["CategoryID"] = lblCategoryID.Text; } } } } return dtDirtyRows; }
The complete source code for this is located at github
That’s all folks!!
Hope you people will find this useful.
Thanks,
Sai Pavan Viswanath Upadhyayula