Tuesday, December 20

Popup Master-Detail using GridView, DetailsView and JQuery with jqModal

Below is the code showing each part.
Products List GridView:
   1: <asp:UpdatePanel ID="updatePanel" runat="server" UpdateMode="Conditional">
   2:     <ContentTemplate>
   3:         <asp:GridView ID="gvProducts" runat="server" OnRowDataBound="RowDataBound" AutoGenerateColumns="False"
   4:             AllowPaging="True" AllowSorting="True" CssClass="datatable" CellPadding="0" BorderWidth="0px"
   5:             GridLines="None" DataSourceID="sqldsProducts" DataKeyNames="ProductID">
   6:             <PagerStyle CssClass="pager-row" />
   7:             <RowStyle CssClass="row" />
   8:             <PagerSettings Mode="NumericFirstLast" PageButtonCount="7" FirstPageText="&#171;"
   9:                 LastPageText="&#187;" />
  10:             <Columns>
  11:                 <asp:BoundField HeaderText="ID" DataField="ProductID" SortExpression="ProductID">
  12:                     <HeaderStyle CssClass="first" />
  13:                     <ItemStyle CssClass="first" />
  14:                 </asp:BoundField>
  15:                 <asp:BoundField HeaderText="Name" DataField="ProductName" SortExpression="ProductName" />
  16:                 <asp:BoundField HeaderText="Quantity" DataField="QuantityPerUnit" SortExpression="QuantityPerUnit" />
  17:                 <asp:BoundField HeaderText="Unit Price" DataField="UnitPrice" SortExpression="UnitPrice"
  18:                     DataFormatString="{0:c}">
  19:                     <ItemStyle CssClass="money" />
  20:                 </asp:BoundField>
  21:                 <asp:BoundField HeaderText="In Stock" DataField="UnitsInStock" SortExpression="UnitsInStock" />
  22:                 <asp:BoundField HeaderText="On Order" DataField="UnitsOnOrder" SortExpression="UnitsOnOrder" />
  23:                 <asp:TemplateField>
  24:                     <ItemTemplate>
  25:                         <input class="button" type="button" value="Edit" 
  26:                             onclick='ShowDetails(<%#Eval("ProductID")%>,<%# Container.DataItemIndex%>)' />
  27:                     </ItemTemplate>
  28:                 </asp:TemplateField>
  29:             </Columns>
  30:         </asp:GridView>
  31:     </ContentTemplate>
  32: </asp:UpdatePanel>
The edit button onclick event invokes ShowDetails client function passing productId as well as item index in the result set. This is different from the mechanism shown in Matt's postbut at the end will perform the same job.
Belwo is the DetailsView Control with another UpdatePanel.
   1: <asp:UpdatePanel ID="updPnlProductDetail" runat="server" UpdateMode="Conditional">
   2:         <ContentTemplate>                                
   3:             <asp:DetailsView ID="dvProductDetail" runat="server" DataSourceID="sqldsProductDetail"
   4:                 GridLines="None" DefaultMode="Edit" AutoGenerateRows="false" Visible="false"
   5:                 Width="100%" DataKeyNames="ProductID">
   6:                 <Fields>
   7:                     <asp:BoundField HeaderText="Product ID" DataField="ProductID" ReadOnly="true" />
   8:                     <asp:TemplateField HeaderText="Product Name">
   9:                         <EditItemTemplate>
  10:                             <asp:TextBox ID="txtProductName" runat="server" Text='<%# Bind("ProductName") %>' />
  11:                             <asp:RequiredFieldValidator ID="rfvProductName" runat="server" ControlToValidate="txtProductName"
  12:                                 ErrorMessage="Required" Display="Static" SetFocusOnError="true" />
  13:                         </EditItemTemplate>
  14:                     </asp:TemplateField>
  15:                     <asp:TemplateField HeaderText="Quantity Per Unit">
  16:                         <EditItemTemplate>
  17:                             <asp:TextBox ID="txtQuantityPerUnit" runat="server" Text='<%# Bind("QuantityPerUnit") %>' />
  18:                             <asp:RequiredFieldValidator ID="rfvQuantityPerUnit" runat="server" ControlToValidate="txtQuantityPerUnit"
  19:                                 ErrorMessage="Required" Display="Static" SetFocusOnError="true" />
  20:                         </EditItemTemplate>
  21:                     </asp:TemplateField>
  22:                     <asp:TemplateField HeaderText="Unit Price">
  23:                         <EditItemTemplate>
  24:                             <asp:TextBox ID="txtUnitPrice" runat="server" Text='<%# Bind("UnitPrice") %>' />
  25:                             <asp:RequiredFieldValidator ID="rfvUnitPrice" runat="server" ControlToValidate="txtUnitPrice"
  26:                                 ErrorMessage="Required" Display="Static" SetFocusOnError="true" />
  27:                         </EditItemTemplate>
  28:                     </asp:TemplateField>
  29:                     <asp:TemplateField HeaderText="Units In Stock">
  30:                         <EditItemTemplate>
  31:                             <asp:TextBox ID="txtUnitsInStock" runat="server" Text='<%# Bind("UnitsInStock") %>' />
  32:                             <asp:RequiredFieldValidator ID="rfvUnitsInStock" runat="server" ControlToValidate="txtUnitsInStock"
  33:                                 ErrorMessage="Required" Display="Static" SetFocusOnError="true" />
  34:                         </EditItemTemplate>
  35:                     </asp:TemplateField>
  36:                     <asp:TemplateField HeaderText="Units On Order">
  37:                         <EditItemTemplate>
  38:                             <asp:TextBox ID="txtUnitsOnOrder" runat="server" Text='<%# Bind("UnitsOnOrder") %>' />
  39:                             <asp:RequiredFieldValidator ID="rfvUnitsOnOrder" runat="server" ControlToValidate="txtUnitsOnOrder"
  40:                                 ErrorMessage="Required" Display="Static" SetFocusOnError="true" />
  41:                         </EditItemTemplate>
  42:                     </asp:TemplateField>
  43:                 </Fields>
  44:             </asp:DetailsView>
  45:             <div class="footer">
  46:                 <asp:Button CssClass="button" ID="btnSave" runat="server" Text="Save" OnClick="OnSave" CausesValidation="true" />
  47:                 <asp:Button CssClass="button" ID="btnClose" runat="server" Text="Close" CausesValidation="false" />
  48:             </div>
  49:         </ContentTemplate>
  50:     </asp:UpdatePanel>
Time to explore client side script. At the begining I did just as Matt and registered a handler for pageLoaded client event of the ASP.NET AJAX. In addition I registered another handler for endRequest.
//  attach to the pageLoaded & endRequest events
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoaded);        
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
The pageLoaded handler task is just to get the updated row and display color animation to indicate the it is updated. We will explore its code later.
The endRequest handler task is to display or hide jqModal window after the record has been retrieved and async request is ended. Just to avoid display empty widnows waiting for the DetailsView to be shown showing data. Also we will explore the details of this handler in a min.
Also I initialize jqModal instance as global on the page:
//Initialize global jqModal instance. Review jqModal Documentation http://dev.iceburg.net/jquery/jqModal/
var $jqm = $('#divProductDetail').jqm({modal: true,overlay: 30, trigger: false, 
                                    onShow: OnShowDetails, onHide: OnHideDetails});
Now I'll explore the client functions starting from the ShowDetails function which will be called when edit button is clicked, Code is commented for better explanation:
   1: function ShowDetails(productId, index)
   2: {
   3:     //Save ProductID in a hidden field to be used by CodeBehind to bind Product Detail DetailsView [dvProductDetail]
   4:     $('#<%=hdnProdId.ClientID %>').val(productId);
   5:     
   6:     //Save Item Index in a hidden field to be used by CodeBehind to RegisterDataItem for using ScriptManager
   7:     //This will be used to get the row that was updated on the GridView to display fading effect indicating that
   8:     //this row was updated.
   9:     $('#<%=hdnEditItemIndex.ClientID %>').val(index);
  10:     
  11:     //Will be used but endRequest to show the DetailsView Modal Window.
  12:     show = true;
  13:     
  14:     //Instruct Update panel to PostBack and update its content which is the Product DetailsView.
  15:     // http://encosia.com/2007/07/13/easily-refresh-an-updatepanel-using-javascript/
  16:     __doPostBack('<%=updPnlProductDetail.ClientID%>', '');
  17: }
As result of calling __doPostBack('<%=updPnlProductDetail.ClientID%>', ''); an AsyncCallBack will be initiationed and when it ends it will rise endRequest event. So its time to explore endRequest event handler:
   1: //Used to show or hide the Modal Popup that display editable Product Detail
   2: function endRequest(sender,args)
   3: {
   4:     if(show)
   5:         $jqm.jqmShow(); //Will rise onShow event of jqModal
   6:     else
   7:         $jqm.jqmHide(); //Will rise onHide event of jqModal
   8:         
   9:     show = false;
  10: }
  11: function OnShowDetails(hash)
  12: {
  13:     //onShow Handler of jqModal. Review jqModal Documentation. http://dev.iceburg.net/jquery/jqModal/
  14:     hash.w.slideDown('slow');
  15: }
  16: function OnHideDetails(hash)
  17: {
  18:     //onHide Handler of jqModal. Review jqModal Documentation. http://dev.iceburg.net/jquery/jqModal/
  19:     hash.w.slideUp('fast',function(){hash.o.remove();});               
  20: }
Call jqmShow or jqmClose will rise onShow and onHide events of jqModal respectively. Event handlers are binded while create the jqModal instance. Event handler are shown in the code above.
Server time processing now should be explored, first I'd show the onLoad event of the Page:
   1: protected override void OnLoad(EventArgs e)
   2: {
   3:     //View and bind DetailsView only if AsyncPostBack is initiated and the initiator is updPnlProductDetail
   4:     if (smDefault.IsInAsyncPostBack && smDefault.AsyncPostBackSourceElementID == updPnlProductDetail.ClientID)
   5:     {
   6:         //  set it to true so it will render
   7:         this.dvProductDetail.Visible = true;
   8:         //  force databinding
   9:         this.dvProductDetail.DataBind();
  10:     }
  11:     base.OnLoad(e);
  12: }
This is going to be excuted each time page loaded. But I placed a condition to check for AsyncPostBack as well as if the AsyncPostBack initiator is the UpdatePanel that contains the DetailsView. Do you remember the __doPostBack('<%=updPnlProductDetail.ClientID%>', ''); this is the key beind the above code.
Final on server side is the Save:
   1: protected void OnSave(object sender, EventArgs args)
   2: {
   3:     if (this.Page.IsValid)
   4:     {
   5:         //  move the data back to the data object
   6:         this.dvProductDetail.UpdateItem(false);
   7:         this.dvProductDetail.Visible = false;
   8:  
   9:         //DataItemIndex to calculate the updated row index in the GridView
  10:         int dataItemIndex = int.Parse(this.hdnEditItemIndex.Value);
  11:         if (dataItemIndex > gvProducts.PageSize)
  12:             dataItemIndex = dataItemIndex - (gvProducts.PageSize * gvProducts.PageIndex);
  13:  
  14:         //  add the css class for our yellow fade
  15:         ScriptManager.GetCurrent(this).RegisterDataItem(
  16:             // The control I want to send data to
  17:             this.gvProducts,
  18:             //  The data I want to send it (the row that was edited)
  19:             dataItemIndex.ToString()
  20:         );
  21:         
  22:         //  refresh the grid so we can see our changed
  23:         this.gvProducts.DataBind();            
  24:         this.updatePanel.Update();
  25:     }
  26: }
This is just a clone from the Matt's sampel with few modifcation to match this example.
Finally and returning back to client side, when the page is loaded again the client event pageLoaded will rise, and it is time to display some animation if the record is updated:
   1: function pageLoaded(sender, args){
   2:     
   3:     //  the data key is the control's ID
   4:     var dataKey = '<%= this.gvProducts.ClientID %>';
   5:     var updatedRowIndex = args.get_dataItems()[dataKey];
   6:     
   7:     //  if there is a datakey for the grid, use it to
   8:     //  identify the row that was updated
   9:     if(updatedRowIndex){
  10:         
  11:         var index = parseInt(updatedRowIndex) + 1;
  12:         //  get the row that was updated
  13:         var $tr = $('#'+dataKey+'>tbody>tr:eq('+index+')');
  14:         
  15:         //Perform Color Animation on the updated row. http://plugins.jquery.com/project/color
  16:         $($tr).animate({}, { queue: false, duration: 7000 }).animate({ backgroundColor: "yellow" }, 1000)
  17:                                                             .animate({ backgroundColor: "white" }, 1000);
  18:     }
  19: }
Conclusion:
The idea of displaying on deman retrieved data on popup is very useful. Here I was showing that same idea can be done in different ways. Giving you options to choose the one that matchs your needs. This is also showing the jQuery can be combined with with ASP.NET AJAX to enhance user experiance.
Feel free to download the sample [51.14 kb] and explore the whole code. I hope that this post was helpful.

No comments:

Post a Comment

Please don't spam, spam comments is not allowed here.

.

ShibashishMnty
shibashish mohanty