2009

Volume 24 Number 0

Extreme ASP.NET Makeover - Web Site Improvements Using jQuery and jQuery UI

By James Kovacs | 2009

Welcome back for the fifth installment of the multipart article series: Extreme ASP.NET Makeover. In Part 4 , we examined the lingua franca of the Web— JavaScript—as well as jQuery, a JavaScript library that simplifies browser-based development. This article, Part 5, will focus on advanced jQuery techniques and how to enhance the visual elements of a Web site using jQuery UI, a browser-based UI framework built on top ofjQuery.

Previously on Extreme ASP.NET Makeover…

Back in Part 3 , I refactored the Cascading Style Sheets (CSS) of ScrewTurn Wiki’s Default theme to remove redundancy. I mentioned that I used the CSS Refactoring Helper, a tool that I wrote using jQuery. The basic idea is to swap the original and the refactored style sheets back and forth rapidly to visually detect any differences introduced by your refactoring efforts. (Remember that the intention of refactoring is to improve the implementation without changing the behaviour.) Any visual differences would appear as flashing page elements, changing in size, or moving on the page. To use the CSS Refactoring Helper, the page needs references to jQuery and CssRefactoringHelper.js, as well as this code snippet:

$(document).ready(function() {
  var stylesheets = ["styles-original.css", "styles-updated.css"];
  $.cycleCssRapidlyAndLeakMemory(stylesheets);
});

The CSS Refactoring Helper is a jQuery extension that adds the cycleCssRapidlyAndLeakMemory function to the jQuery object, which can be denoted by “jQuery” or its alias “$.” The method is a bit tongue-in-cheek because Firefox, Internet Explorer, and Chrome (the browsers that I tested) all leaked memory when swapping style sheets. So I wouldn’t recommend this technique on a production site, but it is a useful development tool for detecting style sheet differences.

So how do we go about swapping style sheets? First we have to find them. jQuery can be used to find any HTML DOM element, not just visual ones. Style sheets are link elements:

var links = $('link');

This will find all link elements, not just style sheets. (Other common link elements include the favicon and auto-detected RSS feeds.) We can narrow the results using a XPath-like expression on the attributes. Style sheets are defined by the rel=”stylesheet” attribute on a link:

var stylesheets = $('link[rel=stylesheet]');

Now we just have the style sheets. Note that although XHTML requires quotes around the attribute value, jQuery doesn’t require it. To find a particular style sheet, we can add additional criteria such as the media type (e.g., screen, print, …), stylesheet URL, title, or any other attribute. We will use the stylesheet URL:

var updatedStylesheet = $('link[rel=stylesheet][href=styles-updated.css]');

The CSS Refactoring Helper can swap between N different style sheets. We start by removing all but the first style sheet in the list. (Other style sheets not in the swap list are left untouched.)

var stylesheetTag = $('link[rel=stylesheet][href=' + stylesheets[0] + ']');
for (var i = 1; i < stylesheets.length; i++) {
  $('link[rel=stylesheet][href=' + stylesheets[i] + ']').remove();
}

We register a callback function on an interval timer and cycle the ‘href’ attribute of the first style sheet through the list of style sheets:

var count = 0;
window.setInterval(function() {
  count %= stylesheets.length;
  var stylesheet = stylesheets[count];
  stylesheetTag.attr({ href: stylesheet });
  count++;
}, 100);

The jQuery attr function sets the attribute value. (Note that jQuery selectors return an array of matched elements, which is why we use the attr function to set the attribute value rather than using the HTML DOM.) The second argument to setInterval is the number of milliseconds between callbacks. So we are switching style sheets every 100 milliseconds.

Unfortunately this doesn’t work quite as expected because browsers cache downloaded files, including style sheets. The style sheets are swapped, but the ones in the browser cache, not the ones being actively edited. So we need to prevent the browser from caching the style sheets. We can do this by appending a changing query string, which is ignored by the file system or web server, but makes the browser think that it is a new file. The easiest way to generate the unique query string is using the current time. To minimize the browser memory leak caused by rapidly swapping style sheets, I truncated the time to seconds. With two style sheets, each will be displayed five times during a second, but only be downloaded once. The completed cycleCssRapidlyAndLeakMemory function is shown in Figure 1:

Figure 1 Completed cycleCssRapidlyAndLeakMemory function

jQuery.cycleCssRapidlyAndLeakMemory = function(stylesheets) {
  var stylesheetTag = $('link[rel=stylesheet][href=' + stylesheets[0] + ']');
  for (var i = 1; i < stylesheets.length; i++) {
    $('link[rel=stylesheet][href=' + stylesheets[i] + ']').remove();
  }
  var count = 0;
  window.setInterval(function() {
    count %= stylesheets.length;
    var stylesheet = stylesheets[count] + "?" + currentId();
    stylesheetTag.attr({ href: stylesheet });
    count++;
  }, 100);
  function currentId() {
    var now = new Date();
    return now.getTime() - now.getMilliseconds();
  }
};

The same result could be achieved using only HTML DOM methods, but it would require a lot more code than the jQuery version above. jQuery made it easy to find the style sheets on the page and manipulate them.

Improving User Interaction

Imagine the following scenario: With the assistance of the CSS Refactoring Helper, we have created an aesthetically pleasing XHTML/CSS-based site. We have spent days—nay, weeks—toiling away to get the site layout and images just right. We’re working on an edit page and we want to warn the user if they try to navigate away without saving their changes. So we display a confirm dialog, using the JavaScript confirm(message) function and we’re presented with the dialog box shown in Figure 2.


Figure 2 Confirm Dialog

How unsightly! Unfortunately, there is no way to style the appearance of the JavaScript confirm dialog. (A JavaScript alert and prompt dialogs are equally impossible to style and just as ugly.) Another option is to use a popup window, but you have little to no control over the window chrome (or titlebar/border.) On the plus side, you can style the contents of the popup window using XHTML/CSS. On the downside, many users have a popup blocker enabled, which limits their utility.

So, how can we display a confirmation dialog that is aesthetically pleasing and integrates with the overall look and feel of our site? One way is to use the Dialog widget from jQuery UI. jQuery UI is a collection of user interface components built on top of jQuery. jQuery UI is available in a variety of themes and consists of a CSS file, a JavaScript file, and a set of images. It includes a variety of interactions (e.g., draggable, resizable), widgets (e.g., dialog, tabs, datepicker), and effects(e.g., bounce, highlight, shake). You can view a quick introductory tour to jQuery UI here.

 

Integrating jQuery UI into ScrewTurn Wiki

You’ve probably already guessed that ScrewTurn Wiki has an ugly confirmation dialog somewhere. The place to find it is in Edit.aspx. Simply log in as the admin (the default username/password is admin/password) and click the “Edit this Page” link. Click on any link to navigate away from the Edit.aspx page and you’re presented with the box shown in Figure 3.


Figure 3 Confirmation Dialog

This confirmation dialog is displayed by hooking the window’s onbeforeunload event and passing a warning message to display:

function __UnloadPage(e) {
  if (!submitted) {
    e.returnValue = " ";
  }
}

ScrewTurn Wiki passes a single space as the warning message, which is the empty line that appears between “Are you sure…” and “Press OK to continue…” in the confirmation dialog. The onbeforeunload event is not a standard HTML DOM event and has problematic support across browsers. ScrewTurn Wiki’s event hook mechanism only works on Internet Explorer and Firefox.

We’ll replace this unsightly confirmation dialog using jQuery UI’s Dialog widget. First, we can simply delete the onbeforeunload-related JavaScript from Edit.aspx and Edit.aspx.cs. Next, we’ll download a customized version of jQuery UI. (There is no option to download an uncustomized version because at a minimum, you need to select a theme.) From the jQuery UI download page, you can choose your desired components and a theme. We’ll use the UI Lightness theme, as it fits with the overall look and feel of ScrewTurn Wiki’s Default and Elegant themes.

The downloaded zip file contains quite a few files, but we’ll only need the contents of the js and css folders. (The development-bundle folder contains demos, documentation, un-minified CSS and JavaScript, and development tools. If you want to learn how jQuery UI works, this is a good place to look.) The js folder includes jquery-1.3.2.min.js and jquery-ui-1.7.1.custom.min.js and both files can be added to ScrewTurn Wiki’s JS folder. The css folder contains theUIi-lightness theme, which can be added to ScrewTurn Wiki’s Themes folder. If you want to use multiple jQuery UI themes on your Web site, only the contents of the css folder changes when switching themes. Jquery-ui-1.7.1.custom.min.js remains the same, so long as you don’t change the selected components.

To use jQuery UI, we need to add references to the jQuery and jQuery UI JavaScript files as well as the jQuery UI theme style sheet. ScrewTurn Wiki automatically includes all JavaScript files in the JS directory. So we need only add the link to the CSS file in MasterPageSA.master, Edit.aspx’s master page:

<link rel="stylesheet" type="text/css" href="Themes/ui-lightness/jquery-ui-1.7.1.custom.css" />

In Edit.aspx, we will call the enablePendingChangesWarning JavaScript function, which we will define in ScrewTurnWiki.js. By placing the function in ScrewTurnWiki.js, we can make this functionality reusable across the application. The dialog itself is defined by a simple div tag:

<div id='confirmDialog' title='Confirm'>
  <p>
    <span class='ui-icon ui-icon-alert' 
          style='float:left; margin: 0 7px 20px 0;'></span>
    Are you sure you want to abandon your changes?
  </p>
</div>

The elements of the dialog should be immediately obvious, except for the span tag. The span with CSS classes is the way that jQuery UI defines themable icons. So, “ui-icon” specifies that this span is an icon and “ui-icon-alert” selects a particular icon.

Now we could insist that any page that wants to display a pending changes warning has to include this <div id=’confirmDialog’/> on the page. This would error prone and jQuery provides a better way. The first step in enablePendingChangesWarning is to use jQuery to manipulate the HTML DOM and append the confirmDialog div to the body:

$('body').append("<div id='confirmDialog' title='Confirm'>
<p><span class='ui-icon ui-icon-alert' style='float:left; margin:0 7px 20px 0;'></span>
Are you sure you want to abandon your changes?</p></div>");

Once the added, we need to call the jQuery UI dialog function to change our ordinary div into a jQuery UI Dialog widget, as shown in Figure 4.

Figure 4 Call to the jQuery UI Dialog Function

$('#confirmDialog').dialog({
  autoOpen: false,
  modal: true,
  overlay: {
    backgroundColor: '#000',
    opacity: 0.5
  },
  buttons: {
    Ok: function() {
      var href = $(this).dialog('option', 'href');
      window.location.href = href;
    },
    Cancel: function() {
      $(this).dialog('close');
    }
  }
});

We’re creating an initially hidden “autoOpen: false” modal dialog. Notice the button definitions. Cancel simply closes the dialog. We’ll come back and talk about the OK button in just a second.

The dialog has to be displayed in response to the user navigating away from the page unintentionally. This is going to happen when a link is clicked. We could manually hook up the onclick events on the page, but this would again be error prone. jQuery once again provides us with a better way:

$('a').click(function(event) {
  event.preventDefault();
  $('#confirmDialog').dialog('option', 'href', this.href).dialog('open');
});

We use jQuery to select all anchors (<a/>) on the page and register a click event handler for them. When the href is clicked, the confirmDialog is displayed. Event.preventDefault prevents the default action of following the link from occurring. Otherwise, the link would be followed as soon as the confirmDialog is displayed. We then pass the href of the anchor link to the confirmDialog as an option. Now you can figure out what the OK button does. If OK is pressed, this href is retrieved from the dialog’s options and used to set the window.location.href, which causes the browser to navigate to the destination page. This gives us a confirmation dialog that fits into the site’s theme, as we see in Figure 5.


Figure 5 Improved Conformation Dialog

Notice the greyed out page effect behind the dialog, which was achieved using the opacity overlay. We only have one minor problem to deal with. The edit page has a few anchor tags that don’t navigate away from the page, but instead refresh the page attachments list or show a list of content templates. We don’t want to display the Confirm Dialog for these links, which look like <a href=’javascript:__doPostBack(‘mangledControlName’,’’). Rather than simply selecting all anchor tags, we can use an advanced jQuery selector to filter out the undesired elements:

$('a:not(a[href^=javascript])').click(function(event) {
  event.preventDefault();
  $('#confirmDialog').dialog('option', 'href', this.href).dialog('open');
});

Note that a[href^=javascript] selects all anchor tags whose href attribute starts with “javascript”. Also, $= matches attributes ending with a particular string. (The use of ^ and $ come from regular expressions where ^ denotes the start of the line and $ the end.) The :not filter removes the href=javascript anchors from the collection of all anchors, which were selected with the first “a.” You can find a list of all selectors in the jQuery Selector documentation. You can see the completed jQuery UI Dialog widget in action in the following video.

 

Re-Theming ScrewTurn Wiki


Figure 6 Default and Elegant Themes

ScrewTurn Wiki ships with two themes – Default and Elegant, as shown in Figure 6. The themes reside in the Theme folder and consist of a set of Cascading Style Sheets and images. As we saw in Part 3 , the CSS for a theme is quite long and involved. The two user-contributed themes available on the ScrewTurn Wiki site include the “Updated Default Theme,” which is a slight alteration of the Default Theme, and the “GreyBoxed Theme,” which is described as a cross between Elegant and Default. This does not speak well for the ease of custom theme creation for ScrewTurn Wiki.

jQuery UI includes the jQuery UI CSS Framework, which is used to create themes for widgets. It defines a set of standard CSS classes that themes can override to provide a custom look and feel. In addition to the 17 pre-built themes, jQuery UI also has the ThemeRoller, which allows you tocreate your own custom themes for jQuery UI widgets. You can take a quick tour of jQuery UI Themes and ThemeRoller.

 

So I got to thinking. It is hard to create ScrewTurn Wiki Themes. It is dead easy to create jQuery UI Themes. Could jQuery UI Themes be adapted to theme ScrewTurn Wiki? I decided to find out and it turned out to be a lot easier than I ever expected.

jQuery UI Themes are designed to style widgets, which typically have header and content areas. ScrewTurn Wiki has a similar layout with headers and content areas for the navigation bar, main content area, and other sections. jQuery UI Themes don’t include overall site layout because they are intended for widgets, which fit into an existing site layout. I created a generic (predominantly) layout-based CSS file to be shared across themes called /Themes/Main.css.

The three most important jQuery UI CSS Framework-related CSS classes for theming ScrewTurn Wiki are ui-widget, ui-widget-header, and ui-widget-content. ui-widget is used for the widget container and defines the font used for widget elements. The other two classes obviously define header and content areas. Armed with this knowledge, I applied the ui-widget and ui-widget-content CSS classes to the body tags in the master pages to provide the font and background style. I then added the ui-widget-header CSS class to h1 through h6 tags in Core\Formatter.cs. Last stop was Default.aspx, where I made the following two changes:

<h1 class="pagetitle ui-widget-header">

and

<div id="PageContentDiv" class="ui-widget-content">

You can view the following video for a tour of the changes and results.

 

Or take a look at the final result, shown in Figure 7, of switching the selected theme to UI Lightness and UI Darkness, which are the unmodified jQuery UI Themes added to ScrewTurn Wiki’s /Themes folder.


Figure 7 Lightness and Darkness Themes

The layout could use a little tweaking here and there and I haven’t updated all the pages to use jQuery UI Themes, but the technique works. We can now use ThemeRoller to build custom ScrewTurn Wiki Themes. If you use Firefox, you can use the ThemeRoller Firefox Bookmarklet to design custom themes right on your own wiki! Once you’re satisfied with the results, just download your new custom theme and deploy it to your /Themes directory. Watch the following video to see it in action.

 

Conclusion

This article has focused on using advanced jQuery techniques and the jQuery UI to give ScrewTurn Wiki better visual consistency and snap. Using the jQuery UI CSS Framework, we were able to re-implement ScrewTurn Wiki’s Themes to leverage jQuery UI Themes and existing tools, such as ThemeRoller, for creating new ScrewTurn Wiki Themes. Next time, we will turn our attention to HTTP Handlers/Modules and show how they can simplify serving up Web content.