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"> </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>
1: protected override void OnLoad(EventArgs e)
2: {
3: this.sqlDsOrders.SelectParameters["CustomerID"].DefaultValue = this.CustomerId;
4: base.OnLoad(e);
5: }
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: }
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 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: });
- url: URL of the distination I issue a request for (Page or Web Service) attached to is method name I want to invoke.
- 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.
- 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.
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: }
There is something I need to mentione here, when I uploaded my sample to my Web Host, 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);
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
Very nice
ReplyDelete