GridView Grouping Master/Detail Drill Down using AJAX and jQuery

by mosessaur 4/19/2008 3:34:20 PM

Introduction:
Last month I posted about Building a grouping Grid with GridView and JQuery. And I got feedbacks about how to do the same thing using AJAX (on demand retrieving of detail data). In fact I was thinking of that too, and I had couple of ideas in mind. One of them it to use nested update panels, or use AJAX Data Controls with page method/web service method calls along with ASP.NET AJAX. You can view the [demo here].

I didn't like the nested update panel idea, although it is the easiest I think. And was started to think about the second idea but I was about to check another way other than ASP.NET AJAX as long as I'm using jQuery. So I was thinking of using jQuery AJAX. That was just after reading Dave Ward's post Using jQuery to consume ASP.NET JSON Web Services.

GridViewDrillDownJQueryAjax00  GridViewDrillDownJQueryAjax01

Prerequisites:
I recommend to read Dave's post Using jQuery to consume ASP.NET JSON Web Services before you proceed. And not only the post but also the comments, because the post itself contains the core information while the comments contain issues with resolutions. And myself I used one of the techniques discussed on the post's comments which is Page Method calls using jQuery AJAX. Another post I wish you to read also from Dave's blogwhich is Why do ASP.NET AJAX page methods have to be static?. Also Dave posted about Using jQuery to directly call ASP.NET AJAX page methods. You may wish to review the above posts first.

Implementation:
I modified the sample I provided in my post Building a grouping Grid with GridView and jQuery to apply the new technique I provide here. Simply when the user click on the master (Customer name) the details (Customer Orders) are populated on demaned and displayed underneath on a sliding DIV using jQuery.

I didn't build web service to retrieve the data instead I used a Page Method. Also I used a technique Dave used in his sample. So I'll start from this point. I'll explore the Page Method along with the technique used to retrieve the data.

I built a User Control that is responsible for retireving and displaying the detail data (Customer Order). The user control only contain a SqlDataSource and Repeater control:

   1: <asp:SqlDataSource ID="sqlDsOrders" runat="server" ConnectionString="<%$ ConnectionStrings:Northwind %>"
   2:     SelectCommand="SELECT [OrderID], [OrderDate], [RequiredDate], [Freight], [ShippedDate] FROM [Orders] WHERE ([CustomerID] = @CustomerID)">
   3:     <SelectParameters>
   4:         <asp:Parameter Name="CustomerID" Type="String" DefaultValue="" />
   5:     </SelectParameters>
   6: </asp:SqlDataSource>
   7: <asp:Repeater ID="List" DataSourceID="sqlDsOrders" runat="server">
   8:     <HeaderTemplate>
   9:         <table class="grid" cellspacing="0" rules="all" border="1" style="border-collapse: collapse;">
  10:             <tr>
  11:                 <th scope="col">&nbsp;</th>
  12:                 <th scope="col">Order ID</th>
  13:                 <th scope="col">Date Ordered</th>
  14:                 <th scope="col">Date Required</th>
  15:                 <th scope="col" style="text-align: right;">Freight</th>
  16:                 <th scope="col">Date Shipped</th>
  17:             </tr>
  18:     </HeaderTemplate>
  19:     <ItemTemplate>
  20:         <tr class='<%# (Container.ItemIndex%2==0) ? "row" : "altrow" %>'>
  21:             <td class="rownum"><%#Container.ItemIndex+1 %></td>
  22:             <td style="width: 80px;"><%Eval("OrderID") %></td>
  23:             <td style="width: 100px;"><%Eval("OrderDate","{0:dd/MM/yyyy}") %></td>
  24:             <td style="width: 110px;"><%Eval("RequiredDate", "{0:dd/MM/yyyy}")%></td>
  25:             <td style="width: 50px; text-align: right;"><%# Eval("Freight","{0:F2}") %></td>
  26:             <td style="width: 100px;"><%# Eval("ShippedDate", "{0:dd/MM/yyyy}")%></td>
  27:         </tr>
  28:     </ItemTemplate>
  29:     <FooterTemplate>
  30:         </table>
  31:     </FooterTemplate>
  32: </asp:Repeater>

Below is the OnLoad event handler of the User Control:

   1: protected override void OnLoad(EventArgs e)
   2: {
   3:     this.sqlDsOrders.SelectParameters["CustomerID"].DefaultValue = this.CustomerId;
   4:     base.OnLoad(e);
   5: }

I didn't use GridView instead of Repeater because it produced an exception and I didn't investigate much around this issue. Also I'm still using VS.NET 2005 & .Net 2.0 so I didn't yet switch to VS.NET 2008 to use the new features.

As you might notice, the User Control has a property called CustomerId. During the call of the page method, I pass CustomerId to the called mathod which in turn set this property. Now to make the idea fully complete you need to view the Page Method:

   1: [System.Web.Services.WebMethod()]
   2: public static string GetOrders(string customerId)
   3: {
   4:     System.Threading.Thread.Sleep(500);
   5:     Page page = new Page();
   6:     CustomerOrders ctl = (CustomerOrders)page.LoadControl("~/CustomerOrders.ascx");
   7:     ctl.CustomerId = customerId;
   8:     page.Controls.Add(ctl);
   9:     System.IO.StringWriter writer = new System.IO.StringWriter();
  10:     HttpContext.Current.Server.Execute(page, writer, false);
  11:     string output = writer.ToString();
  12:     writer.Close();
  13:     return output;
  14: }

The above code is exactly taken from David's Sample, I just adjust it to suite my requirements of course like passing and setting CustomerId. Simply create new Page Class (IHttpHandler), load the user control add to the page and finally Execuse the page using Server.Execute. This way I didn't need to spend much time prepare the way to display my data because I already have my HTML ready for render.

That was all about the Server Side code. Its time to explore the client side and how AJAX call is initiated. Each item of the Master GridView (Customers) is displayed like this:

   1: <div class="group" style="display:inline" id='<%#String.Format("customer{0}",Container.DataItemIndex) %>' 
   2:     onclick='showhide(<%#String.Format("\"#customer{0}\"",Container.DataItemIndex) %>,
   3:                       <%#String.Format("\"#order{0}\"",Container.DataItemIndex) %>,
   4:                       <%#String.Format("\"{0}\"",Eval("CustomerID")) %>)'>
   5:     <asp:Image ID="imgCollapsible" CssClass="first" ImageUrl="~/Assets/img/plus.png"
   6:         Style="margin-right: 5px;" runat="server" /><span class="header">
   7:             <%#Eval("CustomerID")%>:
   8:             <%#Eval("CompanyName")%>(<%#Eval("TotalOrders")%> Orders) </span>
   9: </div>                                                        
  10: <div id='<%#String.Format("order{0}",Container.DataItemIndex) %>' class="order"></div>

The first DIV is for Master Item (Customer), second DIV is for Detail Items (Orders). When user click on the first DIV, AJAX request is initiated and returned HTML is set for the second DIV then its slided down displaying the detail items.

The first DIV has onclick client event handler called showhide(div1Id,div2Id,customerId). The handler Initiate the AJAX Request and like the following:

   1: $.ajax({
   2:         type: "POST", //POST
   3:         url: "GridViewDrillDownjQueryQAjax.aspx/GetOrders", //Set call to Page Method
   4:         data: params, // Set Method Params
   5:         beforeSend: function(xhr) {
   6:             xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");},
   7:         contentType: "application/json; charset=utf-8", //Set Content-Type
   8:         dataType: "json", // Set return Data Type
   9:         success: function(msg, status) {
  10:             $('#progress').css('visibility','hidden');
  11:             $(master).children()[0].src = src;
  12:             $(detail).html(msg);
  13:             $(detail).slideToggle("normal"); // Succes Callback
  14:             },
  15:         error: function(xhr,msg,e){
  16:             alert(msg);//Error Callback
  17:             }
  18:         });

Because I'm using the same technique explained in Dave's post, there is no much to mention here. However I'm going to explore each parameter passed to the ajax method of jQuery:

  • type: type of the request. I use POST method. return to David's post for more details.
  • url: URL of the distination I issue a request for (Page or Web Service) attached to is method name I want to invoke.
  • data: Data to be sent to the server: '{customerId:"ALFKI"}'. You can also review comments on Dave's post regarding this option or review the documentation.
  • beforeSend: Return to Dave's post for more details or to the jQuery.ajax documentation.
  • contentType: When sending data to the server, use this content-type. Default is "application/x-www-form-urlencoded", which is fine for most cases. I recommend that you checkout Dave's post's comments as it contains resolution for an issue related to IE. As a summary, we use beforeSend to set the content type of the request, for some reasons IE use the default content type still. So we add this option contentType to resolve the issue.
  • dataType: The type of data that you're expecting back from the server. Review documentation and return to Dave's post for more details
  • success: A function to be called if the request succeeds. The function gets passed two arguments: The data returned from the server, formatted according to the 'dataType' parameter, and a string describing the status. I used it to display the sliding DIV and set the returned data to the DIV HTML.
  • error: A function to be called if the request fails. The function gets passed three arguments: The XMLHttpRequest object, a string describing the type of error that occurred and an optional exception object, if one occurred.

The following show the complete JavaScript call for the showhide method:

   1: //master: id of div element that contains the information about master data
   2: //details: id of div element wrapping the details grid
   3: //customerId: id of the customer to be send as parameter to web method
   4: function showhide(master,detail,customerId)
   5: { 
   6:     //First child of master div is the image
   7:     var src = $(master).children()[0].src;
   8:     //Switch image from (+) to (-) or vice versa.
   9:     if(src.endsWith("plus.png"))
  10:         src = src.replace('plus.png','minus.png');
  11:     else
  12:         src = src.replace('minus.png','plus.png');
  13:     //if the detail DIV is empty Initiate AJAX Call, if not that means DIV already populated with data             
  14:     if($(detail).html() == "")
  15:     {
  16:         //Prepare Progress Image
  17:         var $offset = $(master).offset();
  18:         $('#progress').css('visibility','visible');
  19:         $('#progress').css('top',$offset.top);
  20:         $('#progress').css('left',$offset.left+$(master).width());                    
  21:         //Prepare Parameters
  22:         var params = '{customerId:"'+ customerId +'"}';                    
  23:         //Issue AJAX Call
  24:         $.ajax({
  25:                 type: "POST", //POST
  26:                 url: "GridViewDrillDownjQueryQAjax.aspx/GetOrders", //Set call to Page Method
  27:                 data: params, // Set Method Params
  28:                 beforeSend: function(xhr) {
  29:                     xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");},
  30:                 contentType: "application/json; charset=utf-8", //Set Content-Type
  31:                 dataType: "json", // Set return Data Type
  32:                 success: function(msg, status) {
  33:                     $('#progress').css('visibility','hidden');
  34:                     $(master).children()[0].src = src;
  35:                     $(detail).html(msg);
  36:                     $(detail).slideToggle("normal"); // Succes Callback
  37:                     },
  38:                 error: function(xhr,msg,e){
  39:                     alert(msg);//Error Callback
  40:                     }
  41:                 });
  42:     }
  43:     else
  44:     {
  45:         //Toggle expand/collapse                   
  46:         $(detail).slideToggle("normal");
  47:         $(master).children()[0].src = src;
  48:     }
  49: }

NOTE:
There is something I need to mentione here, when I uploaded my sample to my hosting, I received an error while testing. When I viewed the error using FireBug, I noticed that the Content-Length is not send with the request header and it is mandatory. That never happen in my development environement. So I had to add the following line in the beforeSend function:

xhr.setRequestHeader("Content-length", params.length);

Conclusion:
I didn't need a real Web Service to actually retrieve the data. I thought that using Page Method is just enough and satisfy my needs. I think, not everything should be made as Web Service, my requirements do not specify that the Gustomer Orders should be exposed through a Web Service, specially that I wish to return a formatted HTML fragment in JSON form. So, Page Method was ideal for me.

Everytime I work with jQuery I reliaze how far it is powerful and easy to use. You can download the demo project (62.21 kb) to explore the whole code and view the demo to see how it is work.

I hope you find this post helpful with value.

kick it on DotNetKicks.com

Currently rated 4.3 by 8 people

  • Currently 4.25/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

ASP.NET | Client Side

Comments

4/10/2008 3:37:26 AM

Stevef

Not really sure I see the logic in returning html in json, defeats the point of a lightweight response if you bloat it with html tags. Why not just have a simple template html fragment which you copy for each item returned in the ajax response, would be much quicker and response time would be better.

Stevef gb

4/10/2008 3:39:34 AM

Stevef

P.S, take a look at the inbuilt getJSON method that jquery has
http://docs.jquery.com/Ajax/jQuery.getJSON

Stevef gb

4/10/2008 10:26:08 PM

Stevef

Moses, what did you do with my comments?

Stevef gb

4/12/2008 6:32:36 PM

mosessaur

Sorry Steve, I just read your comments and they were waiting for approval.
You might be right that this will break the lightweight response which JSON guarantee.
But myself I do trade offs. What is the size of my response?! And where I'm going to use it? It is already not too much and I'm not going to use to render a full page. And is the performance really affected?! It is just a way that can be improved. Thank you for the link. And I would appreciate if you could demonstrate your solution!

mosessaur eg

4/15/2008 8:31:58 PM

Dave

This is a great application of the technique, Moses. Nice work.

@Stevef: Due to the content-type requirement for ASP.NET JSON requests, jQuery.getJSON unfortunately won't work here.

Dave us

4/16/2008 7:44:19 PM

mosessaur

Thank you Dave, I just used your method to modify my sample.
I read the documentation of jQuery.getJSON too, and yes it won't work for this technique.

mosessaur eg

4/18/2008 11:09:19 AM

soohyung

Can you please show me the one using nested update panel, which is the easist one.
Thanks,

soohyung us

4/19/2008 6:35:32 PM

mosessaur

@soohyung sorry for being late to answer, I was on a trip!
Actually I didn't try it myself, as I implemented the way I did here!
Not sure actually if I can provide that soon! but I will try, but I cannot promis

mosessaur eg

4/23/2008 3:53:03 AM

Cristiano

I try to run your application but i get the error "Interface not supported" executing "this.appendChild(elem)" in jquery-1.2.3.min.js. What is the problem?
Sorry but i'm new with javascript and jquery.
Thanks

Cristiano it

4/23/2008 2:03:50 PM

mosessaur

which browser you are using what is its version?!
The sample is tested with IE 7.0, FF 2.0.0.1x, Opera 9.2x and Safari 3.1.1 for Windows! and it is working just fine!

mosessaur eg

4/24/2008 4:21:03 AM

Cristiano

I use IE 7.0

Cristiano it

4/24/2008 3:46:58 PM

mosessaur

Ok did you tried the online demo?! if yes do you face the same issue with it? or only locally?
And could you please test it with FF or Opera, I guess something is disabled on your IE or something is wrong with IE

mosessaur eg

6/10/2008 5:47:00 PM

Walt

Hi Moses, thanks for the great example! It's just what I was looking for. One thing I ran into, when you return the data on the $.ajax call, you used $(detail).html(msg); to insert the result into the div. That didn't work for me, and I ended up using $(detail).html(msg.d); (which I got from Dave's great post). What does the ".d" refer to? Thanks again.

Walt us

6/10/2008 8:02:01 PM

mosessaur

I'm not sure what does "d" refers to!! It is true that Dave's post was my first reference, but I returned to the Ajax documentation for jQuery too. And I didn't notice the "d" in Dave's post.
You could as him, Myself will do that to checkout what it the issue with it! how come this don't work with you while it works with me and what does "d" property refers to.

mosessaur eg

6/15/2008 1:39:24 PM

Rainmaker

Sir, My user control had a GridView, when call PageMethod HttpContext.Current.Server.Execute(page, writer, false);
I will get the error below,
System.Web.HttpException: Control 'xxx_WidgetGridView1' of type 'GridView' must be placed inside a form tag with runat=server.
Any one can solve it? thanks.

Rainmaker tw

6/16/2008 3:25:27 AM

mosessaur

Actually no! Because GridView must be placed inside form tag that is decorated with runat="server"
That is why it is better to use ListView and Repeater

mosessaur eg

6/26/2008 2:25:47 PM

mosessaur

@Walt: Please check Dave response at his blog entry:
encosia.com/.../

mosessaur eg

6/29/2008 11:53:59 PM

MillerTime

Do you know of a way to get a third level of detail or detail grid or repeater inside your order line?

MillerTime us

7/1/2008 5:52:36 AM

mosessaur

You mean to display each order detail on the same way?!
Well, I'm this can be done, but maybe need to think of a way to put it live! I'll think about it

mosessaur eg

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.4.0.0
Theme by Mads Kristensen

About the author

Name of author
mosesofegypt logo
Muhammad M. Mosa Soliman
Software Engineer.
MCT, MCSD.NET,
MCTS: .Net 2.0 Web, Windows, Distributed Applications
MCTS: WSS 3.0, MOSS 2007 Configuration & App Dev
MCPD: Enterprise Application Developer

E-mail me Send mail | Live Space My Live Space


Calendar

<<  July 2008  >>
MoTuWeThFrSaSu
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

View posts in large calendar

Recent Comments

Comment RSS

Community Credit

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008