February 2010

Volume 25 Number 02

Cutting Edge - Predictive Fetch with jQuery and the ASP.NET Ajax Library

By Dino Esposito | February 2010

Last month I discussed the implementation of master-detail views using the new features coming with the ASP.NET Ajax Library. The list of new features includes a syntax for client-side live data binding and a rich rendering component, exemplified by the DataView client control. By putting these features together, you can easily build nested views to represent one-to-many data relationships.

In the ASP.NET Ajax Library, the mechanics of master-detail views are largely defined in the logic of the DataView component and in the way the component handles and exposes its events.

This month I’ll go one step further and discuss how to implement a common and popular AJAX design pattern—predictive fetch—on top of the ASP.NET Ajax Library. Basically, I’ll extend last month’s example—a relatively standard drill-down view into customer details—to automatically and asynchronously download and display related orders, if any exist. In doing so, I’ll touch on some jQuery stuff and take a look at the new jQuery integration API in the ASP.NET Ajax Library. Without further ado, let’s review the context and build a first version of the example.

The Demo to Expand

Figure 1 shows the application scenario on top of which I’ll add predictive fetch capabilities.

image: The Initial Stage of the Sample Application

Figure 1 The Initial Stage of the Sample Application

The menu bar allows the user to filter customers by initial. Once a selection is made, a smaller list of customers is displayed through an HTML bulleted list. This is the master view.

Each rendered item has been made selectable. Clicking on one causes the details of the customer to be displayed in the adjacent detail view. This is where I left off last month. As you can see in Figure 1, the user interface now shows a button to view orders. I proceed from here onward.

The first decision to be made is architectural and relates to the use-case you are considering. How would you load order information? Is it information you have downloaded already along with customer information? Are orders attached to customers? Is lazy loading an option here?

The code we’re considering is expected to run on the client side, so you can’t rely on lazy loading facilities built into some object/relational mapping (O/RM) tools such as Entity Framework or NHibernate. If orders are to be lazy-loaded, then any code is up to you. On the other hand, if you can assume that orders are already available on the client—that is, orders have been downloaded along with the customer information—then you’re pretty much done. All you need to do is bind orders data to some HTML template and go.

Obviously, things get much more interesting if lazy loading is what you want. Let’s work out this scenario, then.

As a side note, you should know that lazy loading is fully supported if you get your data through the AdoNetDataContext object. (I’ll cover this in future articles.) For more information, be sure to look at asp.net/ajaxlibrary/Reference.Sys-Data-AdoNetServiceProxy-fetchDeferredProperty-Method.ashx.

A New Way to Load Script Libraries

For years, Web developers had been left alone to figure out which script files a page would need. That wasn’t a daunting task, because the  limited amount of simple JavaScript code that was used made it quite easy to check whether a required file was missing. Increased amounts of complicated JavaScript code in Web pages introduced the problem of splitting scripts among distinct files and, subsequently, referring to them properly to avoid nasty runtime “undefined object” errors.

Many popular JavaScript libraries have been providing facilities in this regard for years now. For example, the jQuery UI library has a modular design and allows you to download and link only the pieces you really need. This same capability is also offered by the scripts that make up the ASP.NET Ajax Library. The script loader, however, is something more.

The script loader provides a number of extra services and builds on the partitioning of large script libraries into smaller pieces. Once you tell the loader about the libraries you’re interested in, you delegate to the loader any tasks related to the correct ordering of required files. The script loader loads all required scripts in parallel and then executes them in the right order. In this way, the loader saves you from any “missing object” exceptions and provides the fastest way to handle scripts. All you need to do is list the scripts you want.

Hey, wait a moment. If I have to list all scripts I need, what are the benefits of using a loader? Well, what the loader requires is nowhere near to what is required in the well-known process of linking assemblies to a project. You link assembly A and let the Visual Studio 2008 loader figure out any static dependencies. Here’s a code snippet that shows how to deal with the script loader:

Sys.require([Sys.components.dataView, Sys.scripts.jQuery]);

The Sys.require method takes an array of references to the scripts you want to link to your page. In the preceding example, you are instructing the loader to take care of two scripts—dataView and jQuery.

As you can see, however, the call made to the method Sys.require doesn’t include any Web server path to any physical .js files. Where’s the path, then?

Scripts that will work with the ASP.NET Ajax Library loader are required to define themselves to the loader and inform it when they load completely. Registering a script with the loader doesn’t cause any round trip, but is simply a way to let the loader know that it may be called upon to manage a new script. Figure 2 includes an excerpt from MicrosoftAjax.js that shows how jQuery and jQuery.Validate are registered with the loader.

Figure 2 Register jQuery and jQuery.Validate with the Script Loader

loader.defineScripts(null, [
     { name: "jQuery",
       releaseUrl: ajaxPath + "jquery/jquery-1.3.2.min.js",
       debugUrl: ajaxPath + "jquery/jquery-1.3.2.js",
       isLoaded: !!window.jQuery
     },
     { name: "jQueryValidate",
       releaseUrl: ajaxPath + 
                            "jquery.validate/1.5.5/jquery.validate.min.js",
       debugUrl: ajaxPath + "jquery.validate/1.5.5/jquery.validate.js",
       dependencies: ["jQuery"],
       isLoaded: !!(window.jQuery && jQuery.fn.validate)
     }
    ]);

Of course, you can use this approach with custom scripts and client controls as well. In that case, you need to reference the loader-specific definition of the script in addition to your actual script. A loader-specific definition includes release and debug server paths of the script, a public name used to reference it, dependencies and an expression to be evaluated in order to test whether the library loaded correctly.

In order to use the script loader component, you need to reference a new JavaScript file named start.js. Here’s an excerpt from the sample application that uses a mix of old and new script-loading techniques:

<asp:ScriptManagerProxy runat="server" ID="ScriptManagerProxy1">
    <Scripts>
        <asp:ScriptReference Path="~/Scripts/Ajax40/Preview6/start.js"/>
        <asp:ScriptReference Name="MicrosoftAjax.js" 
                       Path="~/Scripts/MicrosoftAjax.js"/>
        <asp:ScriptReference Path=
                                 "~/Scripts/MicrosoftAjaxTemplates.js"/>
     <asp:ScriptReference Path="~/MasterDetail4.aspx.js"/>
   </Scripts>
</asp:ScriptManagerProxy>

You reference the start.js file using a classic <script> element. Other scripts can be referenced using the ScriptManager control, plain <script> elements or the Sys.require method. As you can see from the code snippet above, there’s no reference to the jQuery library. In fact, the jQuery library is referenced programmatically from the page-specific JavaScript file linked via the ScriptManager.

Another interesting feature in the ASP.NET Ajax Library is the availability of jQuery features through the Sys namespace and, conversely, the exposure of Microsoft client components as jQuery plugins. This means, for example, that you can register an event handler for the ready event—a typical jQuery task—using the Sys.onReady function, as shown here:

Sys.onReady(
    function() {
        alert("Ready...");
    }
);

Given all these new features, the typical start-up of a JavaScript file to be used as an extension of a Web page looks like the following:

// Reference external JavaScript files

Sys.require([Sys.scripts.MicrosoftAjax, 

             Sys.scripts.Templates, 

             Sys.scripts.jQuery]);
Sys.onReady(
    function() {
        // Initialize scriptable elements 

        // of the page.
    }
);

An even simpler approach is possible, however. You can use Sys.require to load a control like a DataView instead of the files that implement it. The script loader would load these files automatically based on the dependencies defined for the DataView. Let’s focus on predictive fetch.

Handling the Customer Selection

To obtain the user interface shown in Figure 1, you use HTML templates and attach data to data-bound placeholders using the DataView component. Customer details are automatically shown due to DataView-based data binding when you click on a listed customer. Orders, however, are not bound directly via the DataView. This is because of the requirements we set at the beginning of the article—by design, orders are not downloaded with the customer information.

To fetch orders, therefore, you need to handle the change of selection within the template associated with the DataView. Currently, the DataView doesn’t fire a selection-changed event. The DataView does provide great support for master-detail scenarios, but much of it happens automatically, even though you can create custom commands and handlers (see asp.net/ajaxlibrary/Reference.Sys-UI-DataView-onCommand-Method.ashx). In particular, you set the sys:command attribute to “select” on any clickable elements that can trigger the details view, as shown here:

<li>
   <span sys:command="Select" 
         id="itemCustomer" 

         class="normalitem">
   <span>{binding CompanyName}</span>
   <span>{binding Country}</span>
   </span>        
</li>

When the element is clicked, it fires an onCommand event within the DataView and, as a result, the content of the selectedData property is updated to reflect the selection. Subsequently, any parts of the template that are bound to selectedData are refreshed. Data binding, however, entails updating displayed data, not executing any code.

As mentioned, when a command is fired within the DataView, the onCommand event is raised internally. As a developer, you can register your own handler for the event. Unfortunately, at least with the current prerelease version of the DataView component, the command handler is invoked before the selected index property is updated. The net effect is that you can intercept when the details view is about to show, but you have no clue about the new content being shown. The only goal of the event seems to be giving developers a way to prevent the change of selection should some critical conditions not be verified.

An approach that works today, and that will continue working in the future regardless of any improvements to the DataView component, is the following. You attach an onclick handler to any clickable elements of the master view and bind an extra attribute to contain any key information that is helpful. Here’s the new markup for the repeatable portion of the master view:

<li>
   <span sys:command="Select" 

         sys:commandargument="{binding ID}"

         onclick="fetchOrders(this)"
         id="itemCustomer" 

         class="normalitem">
   <span>{binding CompanyName}</span>
   <span>{binding Country}</span>
   </span>        
</li>

The markup presents two changes. First, it now includes a new sys:commandargument attribute; second, it has a handler for the click event. The sys:commandargument attribute contains the ID of the customer that has been selected. The ID is emitted through data binding. The attribute where you park the ID doesn’t have to be sys:commandargument necessarily; you can use any custom attribute as well.

The click handler is responsible for fetching orders according to whatever loading policy you have set. Figure 3 shows the source code of the orders loader.

Figure 3 The Code to Fetch Orders

function fetchOrders(elem)
{
    // Set the customer ID
    var id = elem["commandargument"];
    currentCustomer = id;
    
    // Check the jQuery cache first
    var cachedInfo = $('#viewOfCustomers').data(id);
    if (typeof (cachedInfo) !== 'undefined') 
        return;

    // Download orders asynchronously
    $.ajax({
         type: "POST",
         url: "/mydataservice.asmx/FindOrders",
         data: "id=" + id,
         success: function(response) {

              var output = response.text;
              $('#viewOfCustomers').data(id, output);
              if (id == currentCustomer)
                  $("#listOfOrders0").html(output);
         }
    });
}

The fetchOrders function receives the DOM element that was clicked. First, it retrieves the value of the agreed attribute that contains the customer ID. Next, it checks whether orders already exist in the jQuery client cache. If not, it finally proceeds with an asynchronous download. It uses a jQuery AJAX method to arrange a POST request to a Web service. I’m assuming in this example that the Web service employs the “HTML Message” AJAX pattern and returns plain HTML ready to be merged with the page. (Note that this is not necessarily the best approach and mostly works in legacy scenarios. From a pure design perspective, querying an endpoint for JSON data would generate a much lighter payload.)

If the request is successful, the orders markup is first cached and then displayed where expected (see Figure 4).

image: Fetching and Displaying Orders

Figure 4 Fetching and Displaying Orders

Figure 4 shows only a screenshot and doesn’t really explain what’s going on. As you click to select a customer to drill down, the request for orders fires asynchronously. Meanwhile, the details of the customer are displayed. As you may recall, there’s no need to download customer information on demand, as that information is downloaded in chunks as the user clicks on the high-level menu of initials.

Downloading orders may take a while and is an operation that doesn’t give (or require) any feedback to the user. It just happens, and it’s completely transparent to the user. The whole point of the predictive fetch pattern is that you fetch information in advance that the user may possibly request. To represent a true benefit, this feature has to be implemented asynchronously and, from a usability perspective, it’s preferable if it’s invisible to the user.

Let’s focus on the most common tasks a user would perform on the user interface in Figure 4. A user would typically click to select a customer. Next, the user would likely spend a few moments reading the displayed information. As the user views the display, the orders for the selected customer are silently downloading.

The user may, or may not, request to view orders immediately. For example, the user may decide to switch to another customer, read the information and then switch back to the first one, or perhaps navigate to yet another. In any case, by simply clicking to drill down on a customer, the user triggers the fetch of related orders.

What happens to downloaded orders? What would be the recommended way of dealing with them upon download?

Dealing with Fetched Orders

Frankly, I can’t find a clearly preferred way to deal with preloaded data in such a scenario. It mostly depends on the input you get from your stakeholders and end users.

However, I would suggest that orders be automatically displayed if the user is still viewing the customer for which the download of orders has just completed.

The $.ajax method works asynchronously and is attached to its own success callback. The callback receives orders downloaded for a given customer, but at the time the callback runs, the displayed customer may be different. The policy I used ensures that orders are displayed directly if they refer to the current customer. Otherwise, orders are cached and made available for when the user comes back and clicks the “View orders” button.

Let’s have a second look at the success callback for the fetch procedure:

function(response) 
{
  // Store orders to the cache

  $('#viewOfCustomers').data(id, response.text);

  // If the current customer is the customer for which orders

  // have been fetched, update the user interface 

  if (id == currentCustomer)
     $("#listOfOrders0").html(response.text);
}

The id variable is local to the $.ajax method and is set with the ID of the customer for which orders are being fetched. The currentCustomer variable, though, is a global variable that is set any time the fetch procedure is executed (see Figure 3). The trick is that a global variable may be updated from multiple points, so the check at the end of the download callback makes sense.

What’s the role of the “View orders” button that you see in Figure 1 and Figure 4? The button is there for users wanting to see orders for a given customer. By design, in this example displaying orders is an option. Hence, a button that triggers the view is a reasonable element to have in the user interface.

When the user clicks to view orders, order information may, or may not, be available at that time. If orders are not available, it means that—by design—the download is pending, or it has failed for some reason. The user is therefore presented with the user interface shown in Figure 5.

image: Orders are Not Yet Available

Figure 5 Orders are Not Yet Available

If the user remains on the same page, orders display automatically as the download completes successfully, as in Figure 4.

The First Displayed Customer

One thing remains to finish the demo of this master-detail scenario enriched with predictive fetch capabilities. The DataView component allows the specification of a particular data item to be rendered in selected mode on display. You control the item to be initially selected via the initialselectedindex attribute of the DataView component. This is shown below:

<ul class="sys-template" sys:attach="dataview" id="masterView"
    dataview:dataprovider="/aspnetajax4/mydataservice.asmx"
    dataview:fetchoperation="LookupCustomers"
    dataview:selecteditemclass="selecteditem" 
    dataview:initialselectedindex="0">

In this case, the first customer retrieved for the selected initial is automatically displayed. Because the user doesn’t need to click, no automatic fetch of orders occurs. You can still access the orders of the first customer by clicking on it again. In this way, the first customer will be processed as any other displayed customer. Is there any way to avoid such behavior?

For the user, clicking to view orders wouldn’t be sufficient for the first customer. In fact, the button handler limits the information that can be displayed to what’s in the cache. This is done to avoid duplicated behavior in code and to try to do everything once and only once. This is shown here:

function display() 
{
    // Attempt to retrieve orders from cache
    var cachedInfo = $('#viewOfCustomers').data(currentCustomer);
    if (typeof (cachedInfo) !== 'undefined')  
        data = cachedInfo;
    else
        data = "No orders found yet. Please wait ...";

    // Display any data that has been retrieved
    $("#listOfOrders0").html(data);
}

The preceding display function has to be slightly improved to trigger order fetches in the case of no current customer selection. This is another reason for having a global currentCustomer variable. Here’s the edited code of the display function:

function display() 
{
    if (currentCustomer == "") 
    {
        // Get the ID of the first item rendered by the DataView
        currentCustomer = $("#itemCustomer0").attr("commandargument");

        // The fetchOrders method requires a DOM element.
        // Extract the DOM element from the jQuery result.
        fetchOrders($("#itemCustomer0")[0]);    
    }

    // Attempt to retrieve orders from cache
    ...

    // Display any data that has been retrieved
    ...
}

If no customer has been manually selected, the sys:commandargument of the first rendered item is read. The quickest way of doing that is leveraging the naming convention for the ID of items rendered through the DataView. The original ID is appended with a progressive number. If the original ID is itemCustomer, then the ID of the first element will be itemCustomer0. (This is an aspect of DataView that may change in the final release version of the ASP.NET Ajax Library.) Note also that fetchOrders requires you to pass in a DOM element. A jQuery query returns a collection of DOM elements. That’s why in the code above you need to add an item selector.

Finally, note that another solution is also possible, if it’s acceptable to you that no customer is initially displayed after data binding. If you set the initialselectedindex attribute of the DataView to -1, no customer would be initially selected. As a result, to see order details, you need to click on any customer, which would trigger the fetch of associated orders.

Wrapping Up

The DataView is a formidable instrument for data binding in the context of Web client applications. It’s specifically designed for common scenarios such as master-detail views. It doesn’t support every possible scenario, however. In this article, I showed some code that extends a DataView solution through the implementation of the “predictive fetch” pattern.

[The ASP.NET Ajax Library beta is available for download at ajax.codeplex.com*. It is expected to be released at the same time as Visual Studio 2010.—Ed.*]


Dino Esposito is the author of the upcoming “Programming ASP.NET MVC” from Microsoft Press and co-authored “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide.

Thanks to the following technical expert for reviewing this article: Stephen Walther