Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #78
See Also: other Dr.Bob Examines columns or Delphi articles

AJAX with ASP.NET and ECO
In this paper, I'll describe how I've enhanced my ECO-driven ASP.NET weblog application with AJAX capabilities using Delphi 2006 and hand-written JavaScript code.

Now that I've been using my ECO-driven weblog for a year, the number of posts is growing and growing. Even within some categories, there are more posts than can easily be seen in a single view. So I decided to add a filter. But not a "normal" ASP.NET TextBox with a Button to execute the filter, but a somewhat more sophisticated filter using AJAX techniques so all we have to do is type the first few characters of the search expression, wait half a second, and then have the filter execute behind the scenese and produce the result for us (without having to click on a Button).

AJAX
First of all, using AJAX we can make an asynchronous request to our ASP.NET application, passing the current content of the tbSearch TextBox and ddlSearch DropDownList, receiving as response only a portion of the actual Blogs page. As a result, the effect is not a complete page refresh, but only a refresh of the portion of the page which needs to be changed. For my weblog, that’s the DataGrid.
Instead of updating the ASP.NET DataGrid control, I’m using a placeholder <p> tag, embedding the ASP.NET DataGrid. So in the Blogs.aspx, I’ve added a <p> and </p> tag around the <ASP:DataGrid> tag, with the <p> tag including an id and the runat=”server” attribute so we can access its contents in code:

  <p id="dgAJAX" runat="server">
    <ASP:DataGrid id="dgPosts" runat="server" allowsorting="True" ...
    </ASP:DataGrid>
 </p>
When we assign a value to the dgAJAX.innerHTML, the ASP:DataGrid will be hidden from view by the <p> tag internals (which will be the HTML from the DataGrid rendering itself), until we click on one of the categories in the dgCategories DataGrid on the left, which will reload and redisplay the dsPosts again.
Note that it’s important that the opening <p> appears before the <ASP:DataGrid> tag, and the closing </p> appears after the closing </ASP:DataGrid> tag. That way, assigning something to the innerHTML value of the <p> tag will replace the complete contents, including the previous version of the DataGrid (filled with the original set of rows and columns).

AJAX onkeydown
Next, we should extend add a lbAjax Label and a TextBox control called AJAX in the Blogs page, where the TextBox has a special attribute onkeydown as follows:

  <asp:Label id="lbAjax" runat="server">AJAX Title Filter:</asp:Label>
  <asp:TextBox id="AJAX" onkeydown="KeyClick();" runat="server"></asp:TextBox>
The actual implementation of the onkeydown JavaScript can be placed at the top of the Blogs.aspx page, in the <head> section.

JavaScript
The JavaScript code between the <head> tags is as follows, passing the TextBox value to the Blogs.aspx page, using an synchronous request as follows:

  <script language="JavaScript">
   var timer = null;

   function KeyClick()
   {
     if (timer != null) clearTimeout(timer);
     timer = setTimeout(LoadTable,500);
     lbAjax.innerHTML = "AJAX Title Filter (typing): ";
   }

   function LoadTable()
   {
     lbAjax.innerHTML = "AJAX Title Filter (searching): ";
     if (timer != null) clearTimeout(timer);
     timer = null;
     if (window.XMLHttpRequest)
     {
       var request = new XMLHttpRequest();
     }
     else
     if (window.ActiveXObject)
     {
       var request = new ActiveXObject("Microsoft.XMLHTTP");
     }
     request.open("POST", "http://www.bobswart.nl/Weblog/Blogs.aspx?Filter=" +
       document.forms[0].AJAX.value, false);
     request.send("");
     dgAJAX.innerHTML = "<b>n/a</b>";
     dgAJAX.innerHTML = request.responseText;
     lbAjax.innerHTML = "AJAX Title Filter (done): ";
   }
  </script>
Note that we could also have done an asynchonrous request, but since the request is handled very fast, the fact that the browser window is "blocking" is hardly noticable for this example.

Page_Load
We must now write the ASP.NET code to handle the incoming request. This will be done in the Page_Load event of the Blogs.aspx page. Here, we can see if the Request.Params contains an item with the name “Filter”. If so, then we are inside an AJAX request, and should perform some special processing before ending the response with the HTML needed only for the DataGrid control.

  Filter := Request.Params['Filter'];
  if Assigned(Filter) then
  begin
    ...
  end;
Once we know that we’re inside an AJAX request, we must apply the RowFilter on the OCL Expression, using the Filter value that was passed along.
Then, we need to bind the DataGrid, and create a HtmlWriter to allow the DataGrid to render itself to, as follows:
  Filter := Request.Params['Filter'];
  if Assigned(Filter) then
  begin
    if (Filter <> '') and not User.Identity.IsAuthenticated then
      ehPosts.Expression := ehPosts.Expression +
        '->select(p|p.Title.sqlLikeCaseInsensitive(''%' + Filter + '%''))';
    HtmlWriter := HtmlTextWriter.Create(Response.Output);
    dgPosts.RenderControl(HtmlWriter);
    HtmlWriter.Flush;
    Response.&End
  end
Note that we have to end the response to avoid the entire page from rendering, instead of only the dgPosts DataGrid.

VerifyRenderingInServerForm
Unfortunately, typing a character in the TextBox to start the AJAX filter gives a nasty error about the fact that an ASP.NET DataGrid control can only be rendered in the context of a form (i.e. within a <form> tag). The DataGrid doesn’t know that its resulting HTML will be placed inside a <form> tag, in the innerHTML of our <p> tag, so it gives this error.
We can ignore the error, and allow the DataGrid to render itself by overriding the VerifyRenderingInServerForm method from the ASP.NET Web Form. In the implementation of this method, we should do nothing, allowing all controls to be rendered without the verification (we know what we’re doing, right?).

  procedure TWebForm1.VerifyRenderingInServerForm(control: Control);
  begin
  //inherited;
  end;
ASP.NET 2.0 Error: RegisterForEventValidation
If you deploy the Delphi 2006 ASP.NET 1.1 application on a web server that (only) supports ASP.NET 1.1, then this will be the road to success. However, if you’ve deployed the ASP.NET 1.1 application on a web server that has both the .NET Framework 1.1 and 2.0 installed, and hence supports both ASP.NET 1.1 and ASP.NET 2.0, and ASP.NET 2.0 has been enabled for your virtual directory, then you will now get a new error message, namely: RegisterForEventValidation can only be called during Render();.
This is a new validation, introduced with ASP.NET 2.0, which verifies postback and callback events. Although it’s not recommended to disable it, I see no other way to render the DataGrid (apart from disabling the sorting and paging of the DataGrid, but I don’t want to do that).
We can disable is in the Page directive of the Blogs.aspx page, by adding the EnableEventValidation=”false” to it. This will result in an error insight flag, and the message that the attribute EnableEventValidation is not supported within the active schema, but once deployed in an ASP.NET 2.0 environment, it will work like a charm.

Category specific JavaScript
The AJAX filter now works fine. As long as we don't select a category of posts first, otherwise the filter will be applied on the full set of posts, and not just the posts within the selected category. To be able to use the category specific posts, we need to pass some more information to the call to the Blogs.aspx page: the Id of the RootHandle which is passed in the Request.Params['RootId'].
Since this value can only be determined at run-time, we need to dynamically add the JavaScript to the page, which can be done with the RegisterStartupScript method as follows:

    if not IsStartupScriptRegistered('AJAX') then
      RegisterStartupScript('AJAX', '<script language="JavaScript">' +
        '   var timer = null;' +
        '   function KeyClick()' +
        '   {' +
        '     if (timer != null) clearTimeout(timer);' +
        '     timer = setTimeout(LoadTable,500);' +
        '     lbAjax.innerHTML = "AJAX Title Filter (typing): ";' +
        '   }' +
        '   function LoadTable()' +
        '   {' +
        '     lbAjax.innerHTML = "AJAX Title Filter (searching): ";' +
        '     if (timer != null) clearTimeout(timer);' +
        '     timer = null;' +
        '     if (window.XMLHttpRequest) {' +
        '       var request = new XMLHttpRequest();' +
        '     }' +
        '     else if (window.ActiveXObject) {' +
        '       var request = new ActiveXObject("Microsoft.XMLHTTP");' +
        '     }' +
        '     request.open("POST", "http://www.bobswart.nl/Weblog/Blogs.aspx?RootId=' +
                Request.Params['RootId'] + '&Filter=" + document.forms[0].AJAX.value, false);' +
        '     request.send("");' +
        '     dgAJAX.innerHTML = "<b>n/a</b>";' +
        '     dgAJAX.innerHTML = request.responseText;' +
        '     lbAjax.innerHTML = "AJAX Title Filter (done): ";' +
        '   }' +
        ' </script>')
We can now compile and deploy the ECO-driven weblog again. This time, the AJAX filter will even work within categories.

Finally...
In the end, the application results in the Blogs page where we can enter text inside the search box, and after half a second the DataGrid with the courseware items will automatically be updated, based on the value in the search field .
Finally, note that even after applying the AJAX filter, the DataGrid can still be sorted and paged. The result is indeed a happy combination of ASP.NET and AJAX using Delphi 2006.


This webpage © 2006-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.