May 2009

Volume 24 Number 05

Cutting Edge - Build Rich User Interfaces with jQuery

By Dino Esposito | May 2009

Contents

The jQuery UI Library
The Sample Scenario
Preparing a Tabbed Pane
Preparing a Dialog Box
Filling the Dialog Box
Initialization Before Display
Posting Data to the Controller
Upgrading the Web

A dialog box is a fundamental element of a graphical user interface. As you probably know, it can be modal or modeless. It is modal if its presence prevents further interaction with the application until the user addresses the question in the dialog box. If the user simply dismisses the dialog box, then no further action will occur that could be triggered by the dialog box.

In Windows development, dialog boxes are a common way of building a user-to-application interaction. In particular, dialog boxes are frequently used to drill down into the content of some displayed items or to edit the content. A familiar pattern in many Windows applications requires the user to click to edit some content and the application displays a modal dialog box with input fields for you to enter updated data.

This pattern has never been popular in Web applications. Browsers have supported pop-up windows forever, but functionally speaking they're not exactly the same as Windows dialog boxes. A pop-up window is simply a child Web browser window lacking some window features such as the address bar or the toolbar. The window displays a Web page, which is great for showing additional content on demand. However, posting data from a pop-up to the server has never been easy for a number of reasons. Add to this that pop-up blockers are usually running on users' machines, preventing the pop-ups from opening. As a result, if you want dialog boxes in your Web app, pop-up windows are not the answer.

In this article, I'll build modal and modeless dialog boxes in jQuery and explain how to post data from them to the Web server.

Like many other libraries and frameworks, the jQuery library doesn't use the browser's pop-up windows to implement dialog boxes. Instead, jQuery relies on script code to pop up a fragment of the Document Object Model (DOM) and dismiss it when the user confirms the content or the abort operation is performed. Because the displayed content is part of the DOM of the current page, there are no postback problems and, more importantly, AJAX capabilities can be implemented.

The jQuery UI Library

Support for dialog boxes is not part of the core jQuery library, whose version 1.3 is now available. To get dialog boxes as well as other nice UI widgets, such as accordion, tabs, and a date picker, you need to resort to an extension of jQuery—the jQuery UI library. More information and download instructions can be found at ui.jquery.com. Some documentation is available at docs.jquery.com/UI.

The jQuery UI library consists of the components listed in Figure 1and Figure 2.

Figure 1 Components in the jQuery UI Library
Component Description
Accordion Renders a collapsible content panel filled with any HTML content you wish
Datepicker Renders a pop-up date picker to select a date into an input field
Dialog Renders a modeless or modal floating confirmation dialog box with features that make it look like a Windows dialog box
Progressbar Renders a progress bar
Slider Renders an input element that works and looks like a slider
Tabs Renders a tabbed content pane
Figure 2 Interactions
Interaction Description
Draggable Enables you to drag items around the browser window using the mouse
Droppable Enables you to drop draggable items onto the element that becomes a drop target
Sortable Enables you to reorder a list of items using the mouse
Selectable Enables you to select items in a list using either the mouse or keyboard
Resizable Enables you to resize a displayed item

An interaction is essentially an aspect that can be applied to DOM elements selected in a wrapped set. An interaction represents a cross-cutting behavior and is designed to make target elements more interactive. For example, by combining the Dialog widget with the Draggable and Resizable interactions, you can build a Windows-like dialog box that can be moved around and resized at will.

As the name suggests, widgets are individual components that represent UI components, such as tabs or dialog boxes that are ready to use in your pages.

As mentioned, the jQuery UI library must be downloaded separately from the core library and has dependencies on the version of the core library. This article is based on version 1.5.3 of jQuery UI and version 1.2.6 of the core library. By the time you read this, version 1.7 of jQuery UI, which is based on latest core library version 1.3.2 will be available.

Now, without further ado, let's see an example that combines tabs and dialog boxes. I'll use an ASP.NET MVC sample application. Although the ASP.NET MVC framework ships with the core jQuery library, there's no special dependency between jQuery and ASP.NET MVC. You can achieve the same performance and functionality by using jQuery with classic ASP.NET Web Forms.

The Sample Scenario

Let's consider a sample page with a list of customers. Each listed customer is associated with a couple of buttons through which the user can delete or edit customer information (see Figure 3).

fig03.gif

Figure 3 A List of Customers to Edit in Place

The idea is that users click on the Edit button to make changes in place. However, the table in Figure 3won't swap labels with text boxes as classic server controls like DataGrid and GridView do in in-place editing mode. In this case, a dialog box will pop up showing a prefilled form for you to edit and save.

For simplicity, all of the UI elements are stuffed into the home page of the sample ASP.NET MVC application. To edit the markup of this page, you need to resort to the associated view file. By default, it is index.aspx located under views\home.

At least with the default view factory (based on the Web Forms rendering engine), creating views for an ASP.NET MVC page results in a mix of plain HTML literals and C# or Visual Basic .NET code blocks. HTML helpers exist to make the HTML generation a smoother and quicker process. However, no official HTML helper exists for producing a classic DataGrid without your writing every single line of markup. A few sample HTML helpers for a grid exist. If you're interested, have a look at MVCContrib.

In this example, for old times' sake I used a GridView control to generate the table in Figure 3. (Having written so much about DataGrids in the recent past, let me point out that ASP.NET server controls are mostly gone once you opt for ASP.NET MVC.) The traditional ASP.NET server controls work with ASP.NET MVC, but they should be viewed only as simple HTML factories with no postback capabilities. In ASP.NET MVC, any interaction between the client and the Web server occurs over HTTP calls that target a controller action—a radically different model from Web Forms postback. Figure 4shows the source code of the home page of the sample application.

Figure 4 The View for the Home Page

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %> <%@ Import Namespace="DataModel" %> <asp:Content ID="indexContent" runat="server" ContentPlaceHolderID="MainContent" > <h2><%= Html.Encode(ViewData["Message"]) %></h2> <script type="text/javascript"> $(document).ready(function() { $("#AppConsole > ul").tabs(); }); </script> <div id="AppConsole"> <ul> <li><a href="#tabCustomers"><b>Customers</b></a></li> <li><a href="#tabOrders"><b>Orders</b></a></li> <li><a href="#tabOther"><b>Other</b></a></li> </ul> <div id="tabCustomers"> <span style="font-size:125%"><b>Customers</b></span> <hr style="height: 2px;border-top:solid 1px black;" /> <% // Tells a user control to render the grid Html.RenderPartial("uc_ListCustomers", ViewData.Model); %> </div> <div id="tabOrders"> <span style="font-size:125%"><b>Orders here</b></span> <hr style="height: 2px;border-top:solid 1px black;" /> </div> <div id="tabOther"> <span style="font-size:125%"><b>Other content here</b></span> <hr style="height: 2px;border-top:solid 1px black;" /> </div> </div> <%-- This is the fragment of markup that identifies the content of the dialog box --%> <% Html.RenderPartial("uc_dlgEditCustomer"); %> </asp:Content>

The sample page is made of a series of DIV tags, each of which represents a tab. All tabs are linked from a sequence of list-item tags. This markup is transformed into a tab pane by a piece of script code. In particular, the jQuery's tabs() method takes all elements in the wrapped set and renders them as tab panes. The resulting user interface contains three panes—Customers, Orders, and Other.

The Customers tab pane is mostly based on the content generated by the uc_ListCustomers MVC user control. The user control receives data to display from the parent view. Data to display is referenced by the ViewData.Model expression and consists of a List<Customer> object. Needless to say, in this context Customer is an entity class defined in your domain model created with the ADO.NET Entity Framework, LINQ to SQL, or whatever else. The controller action responsible for the home page is in charge of retrieving this data and passing it along, as you see here:

public ActionResult Index() { ViewData["Title"] = "Home Page"; ViewData["Message"] = "In-place Editing with jQuery UI"; // Get data to fill the grid List<Customer> customers = DataModel.Repository.GetAllCustomers(); return View("index", customers); }

To receive the list of customers, the user control must declare its base class accordingly, as you see in Figure 5.

Figure 5 Declare the Base Class

public class uc_ListCustomer : ViewUserControl<List<Customer>> { void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { GridView1.DataSource = ViewData.Model; GridView1.DataBind(); } } void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { // Further customize the grid here if required } }

If you use a data-bound server control for generating the markup, then you bind the data to it in Page_Load. Figure 6shows the markup for the grid with links to JavaScript functions using jQuery.

Figure 6 The Markup for the Grid

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" HeaderStyle-BackColor="LightBlue" OnRowDataBound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="CompanyName" HeaderText="Company" /> <asp:BoundField DataField="Country" HeaderText="Country" /> <asp:TemplateField> <ItemTemplate> <a onclick="return confirm('[<%#Eval("CompanyName") %>]\n Are you sure want to delete this customer?');" href="/home/deletecustomer/<%#Eval("CustomerID") %>"> <img style="border:0" src="delete.jpg" /> </a> </ItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemTemplate> <img style="border:0" onclick="fnEditCustomer('<%#Eval("CustomerID")%>', '<%#Eval("CompanyName")%>', '<%#Eval("ContactName")%>', '<%#Eval("Address")%>', '<%#Eval("City")%>')" src="edit.jpg" /> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>

As you can see, each displayed customer is bound to a JavaScript function named fnEditCustomer. This function is responsible for displaying the dialog box and governs further updates. I'll return to this in a moment.

Preparing a Tabbed Pane

To create a tabbed content pane, you call the tabs() method on any element in a wrapped set. All widgets in the jQuery UI library need a DOM subtree to use as a template plus some optional configuration. Typically, you provide the default configuration settings upon page loading and wait to apply other settings later based on the context. The following code shows how to prepare a tabbed pane.

<script type="text/javascript"> $(document).ready(function() { $("#AppConsole > ul").tabs(); }); </script>

The tabs method works on all <ul> child elements of the element with the AppConsole ID. Each <li> within the <ul> becomes a tab. The <li> must then be linked to a <div> within the page that provides the content.

The tabs method has settings to indicate which tabs are disabled, what happens when the user clicks, and if any tab must be selected upon display. The following code shows how to configure the tab widget to disable the third tab, switch tabs on mouse hovering, and use some animation when switching between tabs.

<script type="text/javascript"> $(document).ready(function() { $("#AppConsole > ul").tabs( { disabled: [2], event: 'mouseover', fx: { opacity: 'toggle'} } ); }); </script>

In order to successfully use the tab widget in your pages, you need to link the following script files in addition to the core jQuery library file.

<script type="text/javascript" src="ui.core.js"></script> <script type="text/javascript" src="ui.tabs.js"></script>

The tabs method features a richer API for you to add, remove, or select tabs and download content dynamically via AJAX.

Preparing a Dialog Box

The dialog box has a similar API, based on a method to invoke on a wrapped set and some options to configure it at will.

Unlike tabbed panes, a dialog box needs more work before it can be displayed. Only dialog boxes expected to show some static content can be fully configured in the ready event on the document. Most of the time, though, you want to display a dialog box whose content reflects the current state of the page and perhaps the currently selected item. Let's start with the definition of a jQuery dialog box.

The content of the dialog box is defined in a <div> tag located somewhere in the page. In Figure 4, the content of the dialog box is inserted as a user control in the DOM of the page that will call it.

<!-- This is the fragment of markup that identifies the content of the dialog box --> <% Html.RenderPartial("uc_dlgEditCustomer"); %>

The dialog template is rendered along with the view when the host page is first loaded. At this time, you may not know the data to display within the dialog. You have basically two options to fill the dialog box with context-sensitive data. First, you pass actual data to the JavaScript function that will actually pop up the dialog. Second, you make an AJAX call to the Web server that retrieves (and ideally caches) any required data just before displaying the dialog box. In this article, I opted for the former approach.

The following JavaScript code runs within the ready event handler for the page DOM and creates and initializes the dialog box, but doesn't display it.

$("#__dlgEditCustomer").dialog( { autoOpen: false, modal: true, overlay: { opacity: 0.2, background: "cyan" }, width: 550, height: 400, position: [600, 250] } );

As the code shows, you'll get a modal dialog box with a given width and height that displays at a given position with a semi-transparent overlay. The autoOpen=false setting prevents the dialog box from showing up as the page loads.

Some script files are necessary in order to use dialog boxes. At the very minimum, you need the following two script files:

<script type="text/javascript" src="ui.core.js"></script> <script type="text/javascript" src="ui.dialog.js"></script>

In addition, you need to link the JavaScript files necessary for two common interactions, such as draggability and resizability:

<script type="text/javascript" src="ui.draggable.js"></script> <script type="text/javascript" src="ui.resizable.js"></script>

Note, though, that the draggable and resizable aspects can be disabled through settings. In this case, you don't need the preceding script files any longer.

The dialog() method creates a dynamic IFRAME and copies the content of the dialog template in it. If the content exceeds the maximum area, scrollbars will automatically appear. The resulting dialog box has a title bar and an X close button in the top-right corner. In addition, a bottom button bar can be configured so that the dialog can offer the usual combination of buttons such as OK/Cancel and Yes/No/Cancel in the context of an overall user experience that is very similar to the desktop. Figure 7shows the typical style of a standard jQuery dialog box.

fig07.gif

Figure 7 A jQuery Draggable and Resizable Dialog Box

The cyan background results from the overlay settings defined in the dialog constructor.

overlay: { opacity: 0.2, background: "cyan" }

The dialog box is placed on top of every other element in the page and a layer separates the dialog box from other clickable elements in the page. Any user clicks outside the dialog box are simply lost. The dialog can be resized by dragging handles on the sides.

The appearance of the jQuery widgets depends on the selected theme. In this example, I'm simply using the default theme for jQuery UI 1.5. In any case, a jQuery theme consists of a bunch of files to put in the project and a single CSS file to reference in the master page. You can get free jQuery themes, and even create your own, from the ui.jquery.com/themerollerWeb site.

Filling the Dialog Box

As mentioned, the dialog box is primarily a DOM subtree. Subsequently, to show users an input form with text boxes, drop-down lists, and checkboxes, you start by creating an HTML form and a table-based layout to present input fields with descriptive labels. Figure 8shows an excerpt of the dialog box's internal markup.

Figure 8 Markup for a Dialog Box

<% Html.BeginForm("UpdateCustomer", "Home"); %> <table> <tr> <td><b>ID</b></td> <td><input id="ID" name="ID" type="text" disabled="disabled" /></ td> </tr> <tr> <td><b>Company</b></td> <td><input id="CompanyName" name="CompanyName" type="text" /></td> </tr> <tr> <td><b>Country</b></td> <td> <%= Html.DropDownList("Country", new SelectList(ViewData.Model) %> </td> </tr> : </table> <% Html.EndForm(); %>

You can use ASP.NET MVC helpers to generate the markup for the <form> tag. The BeginForm helper takes at least two parameters—the controller action to fire on submit and the controller name. It goes without saying that if you want a dialog box to post to the server, then you need to have an HTML form within the dialog.

The form contains input fields laid out the way you like and with the features and styles you prefer. When defining the layout of the dialog box, you don't pay much attention to the actual data to display. The only explicit data you put in the dialog at this time is constant data. For example, each customer has a country name in his record so you might want to display a drop-down list with all countries. In this example, though, I'm making it a bit more restrictive. You can change the country of a customer but you can't specify a new country where you have no other customers. This means that the list of customers must be passed to the user control by its parent.

The parent of the dialog box user control is the Index.aspx view. As you saw earlier, this view receives a list of customers from the controller action. If you want users to pick up a country from a list, then you need to pass the list of available countries along with the list customers. The Index.aspx view class is shown in Figure 9.

Figure 9 The Index.aspx View Class

public partial class Index : ViewPage<HomeIndexViewData> { } public class HomeIndexViewData { public HomeIndexViewData() { Customers = new List<Customer>(); Countries = new List<string>(); } public List<Customer> Customers { get; set; } public List<string> Countries { get; set; } }

The HomeIndexViewData is a custom class that groups together all the information to be passed along to the Index.aspx view. Using an ad hoc class results in code that is neater than it would be using the untyped ViewData general-purpose container.

The dialog box will receive the list of countries via the following code:

<% Html.RenderPartial("uc_dlgEditCustomer", ViewData.Model.Countries); %>

To create a drop-down list, you can use the built-in DropDownList helper. In this case, though, you must wrap the enumerable object with the item list into a view specific SelectList object.

<%= Html.DropDownList("Country", new SelectList(ViewData.Model) %>

Figure 10 shows the final dialog box.

fig10.gif

Figure 10 The Dialog Box in Action

Initialization Before Display

All that remains to do is explain how you could fit customer data into a dialog box. As you can see in Figure 6, the dialog box gets displayed as the user clicks on an image button in the same row of the grid. The onclick event of the image button is linked to the fnEditCustomer JavaScript function. This function is ultimately responsible for initializing and displaying the dialog box (see Figure 11).

Figure 11 JavaScript Edit Customer

function fnEditCustomer(custID, custName, custContact, custAddress, custCity, custCountry) { $("#__dlgEditCustomer").data('title.dialog', "Customer: [" + custName + "]"); $("#LastUpdate").datepicker(); $("#__dlgEditCustomer input[@id=CustomerID]").val(custID); $("#__dlgEditCustomer input[@id=CompanyName]").val(custName); $("#__dlgEditCustomer input[@id=ContactName]").val(custContact); $("#__dlgEditCustomer input[@id=Address]").val(custAddress); $("#__dlgEditCustomer input[@id=City]").val(custCity); $("#__dlgEditCustomer select > option[@text=" + custCountry + "]").attr("selected", "selected"); $("#__dlgEditCustomer").dialog("open"); }

The first line of code in the body of the function sets the caption of the dialog box to a context-sensitive information. Next, a date-picker widget is attached to the textbox destined to receive a date. Other instructions serve to initialize the various input fields to values passed to the function. You see the true power of jQuery CSS selectors in those lines. Consider the following line:

$("#__dlgEditCustomer input[@id=CompanyName]").val(custName);

The line reads like this: select all input fields within an element named _dlgEditCustomer whose ID property equals "CompanyName" and set their value to the specified content. Needless to say, in this context the use of jQuery is arbitrary. Classic DOM code enriched with facilities from the Microsoft AJAX client library would work as well.

The power of jQuery selectors also becomes evident when selecting an element in a drop-down list when the index is not known in advance. When it comes to editing a customer record, you know the country she lives in; but all you have in your hands is a string. At the DOM level, to make a selection in a SELECT element you need to specify the index. How can you match, say, the string "Italy" to the corresponding index in the list? You can write a loop yourself and find a matching element, or you can rely on jQuery selectors:

$("#__dlgEditCustomer select > option[@text=" + custCountry + "]").attr("selected", "selected");

The expression reads like this: within an element named _dlgEditCustomer, find all OPTION element, child of a SELECT, where the text property equals the value of the specified variable. For any matching element, you set the selected attribute.

Finally, once all initialization work has been done, you display the dialog box. A call to the dialog method with the "open" command string will suffice.

Posting Data to the Controller

The final step is posting data to the Web server and precisely to the specified controller method—UpdateCustomer on the Home controller.

In ASP.NET MVC, you might want to make each URL significant and fully representative of the target resource. To update customer FRANS, you should use a URL such as /Home/UpdateCustomer/FRANS rather than a common /Home/UpdateCustomer URL with some code working out the details from the request body. This means that you should update the form's action URL in the dialog box each time you pop up the dialog. Here's how to do it:

$("#__dlgEditCustomer form").attr("action", "/home/updatecustomer/" + custID);

As you can see, jQuery selectors strike back again. You select the form within an element named __dlgEditCustomer and for that form you change the action attribute to the URL of choice. Figure 12shows that fresh data is really submitted to the Web server and handled by the right controller method. (Look at Figure 12and Figure 7to spot any differences.)

fig08.gif

Figure 12 The Dialog Posted Updated Data to the Controller Method

Upgrading the Web

If you want more interaction over the Web, then you need effects, widgets, and aspects; and all rigorously written in JavaScript. The jQuery library has become an extremely popular choice for Web developers all around the world and Microsoft itself ships the latest version 1.3 with the just-released ASP.NET MVC framework.

The jQuery library, though, is in no way bound to ASP.NET MVC. It is simply JavaScript, so wherever JavaScript is accepted, the library is OK. In the past two installments of this column at " Explore Rich Client Scripting With jQuery, Part 2" and " Explore Rich Client Scripting With jQuery, Part 1," I covered the basics of jQuery, including selectors, wrapped sets, events, and methods. This month, I focused on a separate leg of the library—the jQuery UI library—that specializes in widgets and interactions. This month you saw how jQuery makes building modal dialog boxes in Web pages much easier.

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

Dino Esposito is an architect at IDesign and the coauthor of Microsoft .NET: Architecting Applications for the Enterprise(Microsoft Press, 2008). Based in Italy, Dino is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.