UI and Ajax Recipes

The following recipes represent common development tasks when building Ajax-style applications using Volta.

Building User Interfaces

Objective

We want to write a declarative UI in a cross-browser compatible way, in a manner that resembles building WinForms applications.

Rationale

The structure of a WinForms application is very different from that of a browser-based application. Those differences are accidental rather than essential, stemming from the histories of the underlying technologies.

The Recipe

Ultimately, Volta will reduce the differences to zero, such that the exact same application code works on the desktop or in the browser. Until then, we reduce the pain by supporting the browser-supplied HTML DOM APIs directly, without change.

Consider the following HTML fragment. This is layout for a matched pair of text boxes and buttons. Most of the tags specify the positions of these elements in a table.

<h3>Find</h3>

<div>

    <table>

        <tr>

            <td>

                What</td>

            <td>

                <input id="what" type="text" style="height: 20px;" /></td>

        </tr>

        <tr>

            <td>

                Where</td>

            <td>

                <input id="where" type="text" style="height: 20px;" /></td>

        </tr>

        <tr>

            <td colspan="2" align="right">

                <button id="find" style="height: 20px;">Find it!</button>

                <button id="clear" style="height: 20px;">Clear</button>

            </td>

        </tr>

    </table>

</div>

In Visual Studio we could build the UI in a graphic designer or in an HTML editor. The highlighted elements in the figure below correspond to the above HTML fragment—they won’t be highlighted in the final output. However we could use any other HTML editor to build the user interface. We like using the graphical designer because it makes the experience similar to the WinForms experience.

image010

Under Volta, the following code queries the contents of the two textboxes. We recommend manually placing it in the InitializeComponent partial method. The DOM elements must have unique IDs, which we use to query or modify them.

string what = Document.GetById<Input>("what").Value;

string where = Document.GetById<Input>("where").Value;

// processing of user supplied inputs follows

Reacting to User Actions

Objective

GUI applications must respond to user actions, such as clicking a button or selecting an element from a list. WinForms supplies an established programming model, namely .NET events for this task. >

Rationale

We avoid deviating from established models in this area because we don’t wish to introduce more accidental complexity. Consequently we want to amplify the ways in which the WinForms and web programming models are similar. This keeps the marginal incremental concept count for Volta as low as possible.

The Recipe

Fortunately the DOM event model and the WinForms event model are almost identical. Therefore the same coding style works for both.

Consider the following code fragment. It shows wiring the “Find” button to a method that handles the Click event.

findButton.Click += findButton_Click;

//

void findButton_Click()

{

    string what = Document.GetById<Input>("what").Value;

    string where = Document.GetById<Input>("where").Value;

    // processing of user supplied inputs follows

}

Page-level Display Morphing

Objective

In the past most Web applications comprised multiple pages. The trend is away from that. Ajax-style applications avoid full postback to improve the user experience.

Rationale

Legacy Web 1.0 applications break the presentation into multiple, distinct pages. This is a problem even for new applications because developers have been conditioned to design this way.

Recipe

Currently, a Volta application must have a single HTML page. We easily simulate the effect of multiple HTML pages as typical in Ajax applications. This is good news for everyone because the world is moving in that direction.

Volta applications leverage Ajax and CSS to update DOM elements, including the complete contents of the page (e.g., the Page Morphing Ajax pattern). We simulate page flow by selectively showing and hiding elements of the active page. This idea extends far enough to support DHTML-based 3D animation, as shown in the samples installed with Volta.

In the following example none of the elements participating in the page flow are visible in the HTML document. We keep them out of sight through the CSS style attribute (alternatively, we could define styles for visible and invisible elements and use the class attribute).

<div id="defaultSampleCode" style="display: none">

    <pre>

        void ShowDefaultMap()

        {

            map = new Microsoft.LiveLabs.Volta.VirtualEarth.Map(mapDiv);

            map.LoadMap();

        }

    </pre>

</div>

We make elements visible by changing their display style. We could implement these changes with .NET 3.5 extension methods:

static class HtmlElementExtensions

{

    public static void Show(this HtmlElement element)

    {

        element.Style.Display = "block";

    }

 

    public static void Hide(this HtmlElement element)

    {

        element.Style.Display = "none";

    }

}

Finally, we can then simulate navigating from one page to anther (i.e., page flow) by hiding all the elements from the inactive pages, and showing the ones from the active page:

foreach (var element in visibleElements) // hide all "inactive" pages

    element.Hide();

code.Show(); // "active" page

Invoking Services

Objective

Nowadays, it is difficult to write an application that doesn’t employ services. Something as simple as obtaining a weather report is infeasible or even impossible without accessing web services.

Without Volta, there are too many different ways of doing the same thing, depending on the language, the platform, and the tier. For instance, currently, the most common way to invoke services from JavaScript is through XmlHttpRequest, and the most common way to invoke services from .NET is through System.Web.HttpRequest: two different ways of doing the same thing.

Rationale

Why should code that accesses services depend on where the code is running? Why should code that looks up the weather from the browser differ from code that looks up the weather from the server? Any differences are accidental.

Recipe

By stretching the reach of the .NET platform to cover the browser, Volta eliminates the accidental complexity. Volta lets us access the service in the same way everywhere. As a consequence, it allows us to tier split our application without breaking the parts that access the services.

The Volta libraries provide identical XmlHttpRequest object abstractions on both client and server. The Volta libraries preserve the semantics of this object, as typically used in non-Volta Ajax-style applications and documented on MSDN or as the Ajax XmlHttpRequest Call pattern. Therefore, developers already familiar with it don’t need to learn anything new.

Consider the following code fragment where we invoke an external service. The uri variable holds the service location, and the call is synchronous, as signaled by the third argument of the Open method.

var xhr = new Volta.Xml.XMLHttpRequest();

xhr.Open("GET", uri, false);

xhr.Send();

if (xhr.Status == 200)

    return xhr.ResponseText;

else

    return null;

Once the Send method returns, the Status property contains the HTTP status code as documented. It’s just as easy to call the service asynchronously by changing the third argument of Open to true:

var xhr = new Volta.Xml.XMLHttpRequest();

xhr.Open("GET", uri, true);

xhr.ReadyStateChange += delegate

    {

        if (xhr.ReadyState == 4)

        {

            if (xhr.Status == 200)

                // Carry on

            else

                // Error

        }

    };

xhr.Send();

If we are interested in the server’s response, simply add a delegate to the ReadyStateChange property as shown above. Once the ready state changes, the Status and ReadyState properties contain the appropriate values, as documented.

The point here is that Volta gives us one abstraction that works regardless of where we use it.