Go Beyond HTML Forms With AJAX

Dino Esposito | June 2009 | MSDN Magazine

 

Beyond the hype of improved user experience, more interactivity, reduced page flickering, quicker data transfer and all the other improvements AJAX promises, the key change that AJAX brought to the table is that it allows you total control over the submission of data to the server.

In a pure AJAX application, any browser-to-server data submission passes through the developer's code. As the developer, you decide how to post data and when. Furthermore, you can even decide whether or not a form should be used.

To gain such a level of control over the data submission process in a browser-based application, you're required to write a lot of JavaScript code. In fact, in previous installments of this column, I discussed the main features of a very popular JavaScript library—the jQuery library—that can offer you a great deal of help when writing serious JavaScript code.

This month, I'll examine a few scenarios involving forms in the context of AJAX applications. I'll examine various approaches to implementing features such as auto-saving, just-in-time validation, and submission throttling. Let's begin with popup forms.

Last month I presented a subset of the jQuery library, the jQuery UI Library, which was designed specifically to simplify the building of rich user interfaces through modal and modeless windows and advanced and powerful widgets such as accordions and tabs.

In a Web context, however, a popup form may not be the best solution for displaying data that results from drill-down operations. The popup window is normally created using the browser API—typically, the window.open method—and is subject to the popup blocker policy that the user has in place for that site. The following code snippet shows how to force the browser to display a popup window:

window.open(url, 'Sample', 'width=400,height=300,scrollbars=yes');

The popup will display the page specified in the URL and can post data to any other page in the application. You can pass data to the popup using the query string; as an alternative, you can retrieve input fields in the parent form through the opener property of the window object. You call the window object from within the page displayed in the popup, like so:

var text = window.opener.document.Form1["TextBox1"].value;

From the opener property you access the object model of the opener form and read the content of a particular form via the HTML DOM.

The abuse of pop-up and pop-under forms in Web advertising led browser developers to introduce features to control the proliferation of such windows. As a result, even the legitimate use of popups to convey information to the user is losing appeal and becoming nearly impossible.

Today, popup forms in Web applications are mostly topmost DIV content displayed in a modal way, after disabling the ability to click on elements underneath. The user interface of the popup doesn't necessarily belong to a separate page; more often than not, it is just part of the page that displays it. It can be an initially hidden DIV tag that becomes visible when the proper script code is run.

From such a pseudo popup window, you can easily access any element in the page because the content of the pseudo popup window is already part of the page. Initializing input elements, therefore, is no longer an issue.

Powerful libraries such as the jQuery UI also offer you facilities to control the look of such popups to make them look like classic Windows dialog boxes. (See Figure 1.)

The markup you place in the popup may or may not include a form tag. For sure, it cannot include any additional server-side form elements as long as the host page is an ASP.NET page. An HTML form element (devoid of the runat=server attribute) can be inserted whether the page is classic ASP.NET or ASP.NET MVC. If the popup contains a form tag, then you can take advantage of the browser's submission capabilities to post data to the action URL of choice. Otherwise, posting is still possible, but you must write the code.

Figure 1 A Sample Dialog Box Created with jQuery UI 1.7
Figure 1: A Sample Dialog Box Created with jQuery UI 1.7

You can loop through input fields, prepare the body of an HTTP request, and post it using the XMLHttpRequest object directly or any abstraction of it (such as the $.ajax function in jQuery or the Sys.Net.WebRequest object in the Microsoft AJAX client library.)

Finally, to keep the content of the popup physically separated from the rest of the ASP.NET page, you might want to define it within an ASCX user control, like so:

<div id="DialogBox1"> <cc1:YourUserControl runat="server" ID="DialogBox1_Content" /> </div>

This approach can be taken in both classic ASP.NET and in ASP.NET MVC pages.

Just-in-Time Validation

It is fairly common that a Web form is required to notify the user of any incorrect values before submission. Notifying the user of improper input is a job that validator controls in ASP.NET do quite well. The ValidatorCalloutExtender in the AJAX Control Toolkit then can also make invalid input warnings attractive, as you see in Figure 2.

Figure 2 Validator Extenders in a UI
Figure 2: Validator Extenders in a UI

ASP.NET validator controls work by validating the entire contents of the form when it is submitted. In the current version of ASP.NET, there's no asynchronous custom validator that enables client-side code to validate fields against a (custom) server rule. Such a component, though, may be released in future versions of ASP.NET.

In summary, to detect and report incorrect values to the user you have two possible approaches. First, you may associate an asynchronous validator with any input field and report error messages as the value of the field changes. Second, you may validate the contents of the form as a whole. This second approach may be implemented in a couple of different ways: you can simply submit the form in the usual way via the browser and wait for the server response, or you can asynchronously and programmatically submit the contents of the form, even partial contents, to have early feedback. Let's review the various approaches and see how to take advantage of AJAX capabilities.

In ASP.NET, there are a number of predefined validator controls that apply fixed rules to the field contents and determine whether the input is valid or not. Sample validators perform checks such as whether a field is left empty, a date falls within a given range, or some content equals a given value or matches a given regular expression. All these checks cover common validation needs. However, the most common check must be performed on the server and requires a database connection, a workflow, or perhaps a Web service. For all these cases, ASP.NET provides the CustomValidator control.

CustomValidator is an excellent control that allows you to specify both a client JavaScript function and a server method to check the value and report feedback to the UI level. Properly wrapped up in an AJAX update-panel, the CustomValidator control can also return its response asynchronously, avoiding a full-page refresh. The only drawback of the CustomValidator is that, just like any other validator, it won't check the value until the form is submitted. Let's consider the code below:

<asp:UpdatePanel runat="server" ID="UpdatePanel1"> <ContentTemplate> <asp:TextBox ID="TextBox1" runat="server" AutoPostBack="true" /> <asp:CustomValidator ID="CustomValidator1" runat="server" Display="Dynamic" ControlToValidate="TextBox1" SetFocusOnError="true" ErrorMessage="Invalid ID" OnServerValidate=" CustomValidator1_Validate" > </asp:CustomValidator> </ContentTemplate> </asp:UpdatePanel> <asp:Button ID="Button1" runat="server" Text="Submit" />

The page posts back as the user edits the contents of the text box. A CustomValidator instance is associated with the text box and knows how to validate the content. The update-panel ensures that a postback will generate only a partial refresh. Unfortunately, this code doesn't produce the expected results. The page posts back as the text box is edited, but no validation ever occurs. To trigger the custom validator, the user needs to push the submit button.

The problem is that no code path leads to the invocation of the Validate method on the Page class outside the classic form submission process. To remedy this, you must check whether an asynchronous postback is happening and invoke Validate yourself, as shown below:

protected void Page_Load(object sender, EventArgs e) { if (ScriptManager1.IsInAsyncPostBack) { this.Validate(); } }

The Validate method, in turn, will loop through any validators in the page and report about any incorrect values in the page.

If your goal is to report input errors as soon as possible, the only alternative to the previous approach is to add some JavaScript code to critical input fields. Here's an example:

<asp:TextBox ID="TextBox1" runat="server" onchange="__doValidation(this)" />

In a textbox, the onchange event is not fired while the user is typing, but only when the user tabs out of the text box and implicitly commits the changes. In the preceding code snippet, a JavaScript handler is executed to perform a remote validation via XMLHttpRequest. Here's the code for the JavaScript handler:

<script type="text/javascript"> function __doValidation(textBox) { var text = textBox.value; PageMethods.PerformValidation(text, onComplete); } function onComplete(results) { if (results == false) $get("Label1").innerHTML = "Invalid ID"; else $get("Label1").innerHTML = ""; } </script>

As you can see, the example uses an ASP.NET page method to define the HTTP endpoint to be invoked. Any such endpoint is fine, including a WCF service method, a Web service method, or a REST service. The response you get from the HTTP endpoint may be either a Boolean value or a piece of HTML markup. In the former case, the logic that updates the user interface lives on the client (and it may be really simple); in the latter case, more sophisticated logic is required on the server.

If you don't need to validate input fields individually and can wait for feedback until the entire form is submitted, then the simplest thing you can do is use validators within an ASP.NET UpdatePanel control. In this case, keep in mind that both validators and input controls must be placed in the same panel.

As an alternative to partial rendering for posting and validating the contents of a form, you can consider an AJAX library such as jQuery in combination with the Form and Validation plugin. An example is discussed in the article "Combining JQuery Form Validation and Ajax Submission with ASP.NET."

Overall, I believe that individual checks are preferable because you catch incorrect values immediately and you can adjust the form dynamically to reflect current input. For the time being, you'll need to use JavaScript or perhaps do partial rendering with auto-postback controls.

If you intend to validate the entire form asynchronously, I still believe that using classic ASP.NET validators with partial rendering is the simplest option. Equally effective is any solution that serializes the content of the form to a string and posts that to an HTTP endpoint using the AJAX library of choice. As you'll see in a moment, custom serialization and posting of a form provides for more advanced features.

Submission Throttling

With AJAX, you no longer have just one way to post the contents of a form to the Web server. You can certainly do it explicitly through a submit button, or programmatically via the submit method of the HTML DOM Form object. More importantly, though, you can consider submitting content to the server in chunks and in multiple steps rather than all at once. Pattern-wise, if you opt for sending all the content of a form at once, then you are applying the "Explicit Submission" pattern; otherwise, you are throttling your submission via the "Submission Throttling" (ST) pattern.

The ST pattern proves helpful for smoothing the impact of client operations that put pressure on the server, including operations that result in frequent round-trips. This makes the ST pattern the key strategy for implementing nice features such as auto-saving forms.

Simply put, the ST pattern suggests you cache the content to submit in a browser-side buffer and submit it piecemeal, in multiple steps. The canonical example is the auto-completion engine. As the user types into a textbox, a request is made to the server for suggestions. It's easy to figure out that the number of requests that hit the Web server at peak times is quite high.

To reach a reasonable compromise between responsiveness of the application and Web server workload, you might want to take control over the submission process. To understand throttling, let's examine the source code of the Microsoft's AutoComplete extender, which you'll find in the AJAX Control Toolkit.

The auto-complete extender sends a request for suggestions as the user types in the input box. Based on this, one would expect a handler for the keypress event that calls the specified Web service. However, a closer look at the source code reveals that it works differently, as illustrated below.

// Handler for the textbox keydown event onKeyDown: function(ev) { // A key has been pressed, so let's reset the timer this._timer.set_enabled(false); // Is it a special key? if (k === Sys.UI.Key.esc) { ... } else if (k === Sys.UI.Key.up) { ... } ... else { // It's a text key, let's start the timer again this._timer.set_enabled(true); } }

The extender uses an internal timer configured to an interval of one second (by default). The timer is started when the text box gets the input focus. As soon as a key is pressed down, the timer is stopped and an analysis of the key begins. If the key is one with a special meaning, such as Enter, ESC, Tab, or an arrow key, then the extender proceeds with its own code. If the key pressed indicates content to be entered in the text box buffer, then the timer is started.

The timer is stopped and restarted every time an actual key is typed. If you stop typing for one second (or whatever interval you configure), then on the tick of the timer, the actual content of the input field is submitted to receive suggestions. It's not a big change for users, but it's great news for the server because no postback occurs until the user types. If the user takes a one-second break, the timer expires and the postback is then made.

Timers are a powerful tool in JavaScript because their inherent asynchronous behavior can be leveraged to simulate multithreading. By creating a timer in JavaScript, you are telling the interpreter to run the associated code as soon as possible, but not immediately. If the code is simple enough, and the timer interval appropriate, then the interpreter will be able to schedule execution properly, thus advancing multiple tasks simultaneously.

Auto-Saving Forms

It is not unusual in AJAX applications that the user spends a lot of time working on a single form, but without posting any data to the Web server for a while. The Web server has no way to figure out what's happening on the client. Subsequently, if it doesn't receive requests for a time, it may just have the server session time out. Therefore, when the user finally posts the completed form back to the server, she will get an unpleasant surprise (a timeout message).

To avoid that, the client application sends a "heartbeat" message via XMLHttpRequest at an appropriate frequency. A heartbeat message can take any form and content that is suitable for the particular scenario. For example, it can be an empty request that just shows that the client is still working and has the sole objective of keeping the server session up and running. (In this case, the interval must be of any length that is shorter than the session timeout configured for the application.)

The heartbeat may even contain the current, possibly partial, contents of the form. Such contents are uploaded periodically and cached on the server waiting for the proper time to process it. The overall idea is not much different from the auto-save feature of Microsoft Office Word, where the open document periodically saves back to disk to avoid loss of data.

Over the Web, auto-saving forms have a much less defined behavior, and their internal implementation is largely left to the creativity and needs of the developer. Auto-saving forms are essentially forms endowed with the ability to periodically post their contents to a URL. The receiving URL will then handle data and typically cache it for the current user. An example is shown below:

<script type="text/javascript"> var _myTimerID = null; var _userID = null; function enableAutoSave(id) { _userID = id; _myTimerID = window.setInterval(__doAutoSave, 10000); } function __doAutoSave() { var body = "ID='" + _userID + "'&" + "TextBox2='" + $get("TextBox2").value + "'&" + "TextBox3='" + $get("TextBox3").value + "'&" + "TextBox4='" + $get("TextBox4").value + "'"; PageMethods.AutoSave(_userID, body, onComplete); } function onComplete(results) { $get("Msg").innerHTML = "<i>Form auto-saved for user '" + _userID + "' at " + results + ".</i>"; } </script> <body> ... </body> // In the code-behind class ... protected void Button1_Click(object sender, EventArgs e) { string user = TextBox1.Text; // Populate input fields if any previously entered // data is available string body = AutoSaveAPI.Read(user); if (!String.IsNullOrEmpty(body)) { Dictionary<string, object> data = body.ParseToDictionary(); TextBox2.Text = data["TextBox2"] as string; TextBox3.Text = data["TextBox3"] as string; TextBox4.Text = data["TextBox4"] as string; } // Re-enable auto-save for this editing session string script = "enableAutoSave('" + user + "');"; ScriptManager.RegisterStartupScript(this, this.GetType(), "myTimer", script, true); } [WebMethod] public static string AutoSave(string id, string body) { AutoSaveAPI.Save(id, body); return DateTime.Now.ToString(); }

Once the user navigates to the form, the codebehind class loads any data previously entered for that form. Typically, the data is read from the cache or from any other source of choice. In the example above, the storage is hidden behind the interface of the AutoSaveAPI class. Any information read is used to populate the input fields in the form.

To enable auto-saving in a new data entry session, you emit a line of script that triggers a timer. When the timer ticks, the callback collects the current values in the various fields and builds a string. The string is then uploaded using XMLHttpRequest. You can upload it to a Web service, a plain URL using a REST approach, or even to a page method. Because an auto-saving form is a specific component of a given page, using a page method is the ideal solution because it is page-limited, efficient, and easy to code. Figure 6 shows an auto-saving form in action.

Figure 6: An Auto-Saving Form in Action
Figure 6: An Auto-Saving Form in Action

AJAX And Beyond

With AJAX, the old HTML form tag needs some revision. With the upcoming HTML 5.0 standard, a new set of inputs including URL, date, and e-mail will be handled natively by browsers. This will reduce the number of cases for which scripting and validators are required. This evolution of HTML will let you focus on server-side validation, which must be asynchronous in AJAX applications.

Send your questions and comments for Dino to cutting@microsoft.com.

CommentsRSS

About the Author

Dino is the author of "Programming ASP.NET MVC" for Microsoft Press and also coauthored the bestseller "Microsoft .NET: Architecting Applications for the Enterprise" (Microsoft Press 2008). A long time author and experienced consultant and trainer, Dino lives in Italy (when not traveling) and plays tennis (when not injured).

More from Dino:

Videos

Articles