Archive for the 'User Experience' Category

Combo Select Boxes In jQuery

Although the standard <select> input for HTML forms supports a “multiple” attribute which allows users to select multiple options by control- or command- clicking, it’s easily one of the most confusing concepts for users to grasp. It’s also fairly clumsy, especially when the list of options is long. It’s difficult for users to see which options they have already selected, and if their previous selections scroll out of site, they get nervous that those choices will be lost when they make additional selections.

A more friendly and intuitive solution is to allow the user to create a new list of selected items by moving them from the original list into the new list. This keeps their (usually much smaller) list of selected items in constant view, and removes those items from the original list, making it shorter and easier to scan for any additional items they might wish to add.

I’ve been using these “combo select boxes” for many years, but a recent project provided me with the opportunity to recreate them with better markup, and using jQuery to control the behavior.

The Markup

I wanted to keep the markup as light and simple as possible, so I stuck with basic form elements. Classes are used as the “hooks” for the javascript so that you can manage multiple lists in a single page.

  • There are two <select> inputs, classed “left” and “right” — users will move items from the left (original) list to the right (new) list.
  • There are two <input> elements of type “button”, classed “add” and “remove”, to control the movement of items between the lists.
  • The buttons are wrapped in a <fieldset> so that they can be positioned as a group via CSS.
  • The grouped buttons and the two lists are wrapped in an additional <fieldset>, with an associated <label> element, so that the entire control can be treated as a single element when integrated with the rest of your form.
  • The one slightly unusual bit of markup here is the addition of an ID on each <option> element. This helps us keep the lists sorted as items are moved between them (explained below).
<label for="letters">Letters</label>
<fieldset id="letters">
	<select id="lettersleft" name="lettersleft" class="left" multiple="multiple">
		<option id="opt1" value="1">AAA</option>
		<option id="opt2" value="2">BBB</option>
		<option id="opt3" value="3">CCC</option>
		<option id="opt4" value="4">DDD</option>
		<option id="opt5" value="5">EEE</option>
		...etc...
		<option id="opt26" value="26">ZZZ</option>
	</select>
	<fieldset>
		<input type="button" class="add" value=" > " />
		<input type="button" class="remove" value=" < " />
	</fieldset>
	<select id="lettersright" name="lettersright" class="right" multiple="multiple">
	</select>
</fieldset>

Adding Behavior

jQuery’s built-in DOM manipulation methods made short work of the basic functionality. I used .parent() to traverse back up the tree to the master <fieldset>, and .find() to target the left and right lists respectively. Moving <option> elements between the two lists is a snap with .append():

$('input.add').click(function(){
	var left = $(this).parent().parent().find('select.left option:selected');
	var right = $(this).parent().parent().find('select.right');
	right.append(left);
	sortBoxes();
});

$('input.remove').click(function(){
	var left = $(this).parent().parent().find('select.left');
	var right = $(this).parent().parent().find('select.right option:selected');
	left.append(right);
	sortBoxes();
});

You’ll notice the call to a sortBoxes() function. The usability of this type of control is greatly enhanced if the lists are sorted as each change is made. For that, I used Guillaume Andrieu’s Selso plugin <http://plugins.jquery.com/project/selso>. A cool feature of this plugin is that it can simultaneously sort multiple lists with the given criteria, since it sorts individual elements within their respective parents. So, all I had to do is select all of the <option> elements from the left and right lists, and tell the plugin how I wanted them sorted. I also deselected any selected elements:

function sortBoxes(){
	$('select.left, select.right').find('option').selso({
		type: 'alpha',
		extract: function(o){ return $(o).text(); }
	});

	// clear all highlighted items
	$('select.left, select.right').find('option:selected').removeAttr('selected');
}

And that’s it! As usual with jQuery, not a lot of custom code required. I considered wrapping this up into a plugin, but I think I’ll wait until I’ve used it in a few more places to see what options would be the most useful. You can try out a demo here: <http://devblog.jasonhuck.com/assets/comboselect/>.

Error Management Techniques for Lasso

An important aspect of web application design is error management. Many factors outside a developer’s control can cause problems, even if the deployed code is free of syntax errors, from changes in the underlying subsystems to edge cases in user input that haven’t been accounted for, to remote services that stop responding. It’s the responsibility of the developer to prepare for the unexpected and attempt to handle whatever errors may crop up over time. The following techniques take advantage of the error management capabilities built into Lasso Professional to trap and log errors, notify system administrators that a problem has occurred, and provide a friendly message to end users.

Set the Error Reporting Level to Full

There are three levels of error reporting available for Lasso: Full, Minimal, and None. The error reporting level can be set globally from the administration interface, and/or adjusted at the page level through the use of the [lasso_errorreporting] tag.

Lasso developers are often advised to set the error reporting level to Minimal or None in production in order to reduce the amount of debugging information shown to site visitors when an error occurs, and for good reason. Legitimate users have no use for the debugging data, and the less information you divulge to would-be hackers about the architecture of your system, the better.

So why do I recommend setting the error reporting level to Full? Simple. We’re going to use a custom error page anyway (see below) and we’ll control the display of debugging information there. Also, since we want to log as much information as possible about the error, we’ll want the contents of [error_msg] to be as detailed as we can make it. The only way to get [error_msg] to return a full stack trace for an error is to set the error reporting level to Full.

If your application follows a “onefile” type of methodology where you have a master controller file and/or a global configuration file that’s loaded as part of any and all requests, I would recommend leaving the server-wide setting at Minimal or None, and overriding it to Full from within your application. That way you don’t have to worry about visitors seeing errors created by other apps on the same server, such as the built-in Admin apps, or some utility script you may have forgotten about.

Merge File Errors with Regular Errors

One of the more peculiar idiosyncracies of Lasso is the fact that errors thrown by file manipulation tags are handled separately from all other types of errors. File-specific errors won’t be reported by [error_code] or [error_msg] tags (or any of their brethren in the error_ namespace), nor will they trigger [handle_error] blocks. Thus, file errors won’t cause an error page to be displayed. Instead, you must check [file_currenterror] when working with the file tags. It’s easy to overlook this separate error tag, which can lead to confusion when debugging.

Fortunately, we can alter this behavior so that file errors are reported as regular errors. In the following code snippet, a [handle] block is defined that checks for the existence of a file error. If a file error is detected*, a [fail] tag is used to trigger a normal error, and the normal error code and error message are set to match the code and message that [file_currenterror] reports.

handle(file_currenterror( -errorcode));
    fail(
        file_currenterror( -errorcode),
        file_currenterror
    );
/handle;

* Here’s how this works: [file_currenterror( -errorcode)] will return just the current error number. If there’s no error, that number will be zero. Zero evaluates to false. All other numbers evaluate to true. Thus, the handle block will only be triggered if there is an actual file error.

Provide a Custom Error Page

Lasso makes implementing a custom error page so easy there’s no excuse for not having one. By simply placing a file named “error.lasso” at the root level of your web site, you can replace the default “blue screen” error page with anything you like. It’s a good idea to match the look and feel of the rest of your application, but otherwise keep the page as simple as possible. Attempting to use a lot of complex logic within a custom error page can quickly lead to problems, since errors in your error management code can create a recursive failure (even though Lasso will bail out of the loop when the recursion limit is reached). Nonetheless, there are a few simple things worth doing, which are outlined below.

Capture the Error Code and Message Right Away

Since we are going to be doing a bit of code within our custom error.lasso page, let’s make sure that we don’t accidentally reset the error code or message that we’ll be working against:

var(
    'code' = error_code,
    'msg' = error_msg
);

Inline Errors vs. Full Page Errors

If an error occurs within an [include], Lasso will display the contents of error.lasso inline with the rest of the calling page, whereas otherwise the entire calling page will be replaced with the contents of error.lasso. This can be problematic if your custom error.lasso page uses the same template as the rest of your site, since inline errors will display the entire template twice: once for the calling page and once for the area where an [include] threw an error.

Ideally, we want to detect whether a given error came from within an include or from the calling page itself, so that we can alter the presentation of the error message accordingly. We do this by inspecting the error stack (which, again, requires that the error reporting level be set to Full):

var('errorInline') = ($msg >> 'at: include with params:');

If the error stack references [include], we know that the error occurred within an included file and that our error.lasso page will be displayed within the context of the calling page. Now we can easily enclose portions of our error management code in conditional statements for more precise control:

if($errorInline);
    // only do this for inline errors
/if;

if(!$errorInline);
    // only do this for full-page errors
/if;

Display a Friendly Message to Users

Visitors should be presented with a simple message explaining that the operators of the site have been notified of a potential problem. There’s no need for them to see any technical details about the error itself. An example message might look something like this:

An error has occurred. The error has been logged and the system administrator has been notified. You may go back and try again now, or, if the error persists, try again later. We apologize for the inconvenience.

Return An Appropriate HTTP Status Code

In addition to the message you provide for the human user, you should also deliver the correct message to the user agent (usually a web browser, but also search engine spiders and other programs) in the form of an HTTP status code. By default, Lasso will return status code 500 for most errors, and status code 401 for some permission-based errors. These are perfectly reasonable defaults, but you may wish to be more specific. A few potential matches are listed below:

Lasso Errors:
-9963 Invalid password. The password supplied is not valid.
-9964 Invalid user name. The user name supplied is not valid.

HTTP Status Code:
401 Unauthorized
The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in “HTTP Authentication: Basic and Digest Access Authentication”.
Lasso Error:
-9961 No permission. The current user is not allowed to perform the specified action. This could mean that a file suffix is not allowed by Lasso security. Edit user security permissions as configured within Lasso security.

HTTP Status Code:
403 Forbidden
The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.
Lasso Errors:
-9967 Resource not found.
-9984 Unauthorized file suffix or file not found. The error -9984 can be seen if you specify a Lasso page with a file suffix which is not included in the Lasso Security settings. Also returned by file management tags.

HTTP Status Code:
404 Not Found
The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.

A full list of HTTP 1.1 status codes and their definitions can be found here:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

To set the HTTP status code according to the list above, you could use code like this within your custom error.lasso page:

if(!$errorInline);                                    // only for full-page
    var('statuses') = map(
              0    =    '200 Okay',                            // just in case
        -9963    =    '401 Unauthorized',
        -9964    =    '401 Unauthorized',
        -9961    =    '403 Forbidden',
        -9967    =    '404 Not Found',
        -9984    =    '404 Not Found'
    );

    var('status') = $statuses->find($code);
    !$status ? $status = '500 Internal Server Error';    // default to 500

    $__http_header__ = string_replaceregexp(
        $__http_header__,
        -find='(^HTTP\\S+)\\s+.*?\r\n',
        -replace='\\1 ' + $status + '\r\n'
    );
/if;

Log As Much Information About The Error As Possible

Once an error has been identified, the application should log as much detail as possible about the event to aid in subsequent debugging efforts. The simplest way to accomplish this is via the [log_critical] tag, which will insert whatever message you provide into Lasso’s built-in error database, timestamped and flagged as critical. (BTW, the default error.lasso page will log the error code and message to the error database at the “detail” level if the error reporting level is less than Full.)

While extremely convenient, there are some downsides to relying on the built-in error database. For one, longer error messages will be truncated due to the column size used in the errors table, although I assume it’s possible to alter the column size using the database browser utility from within SiteAdmin. Also, the UI for viewing logged messages doesn’t provide any way to sort or filter entries beyond the static list of error levels: critical, warning, detail, and deprecated. As a result, finding a particular series of errors, especially when more than one virtual host is handled by the same Lasso Site, can be a very tedious process.

Nevertheless, we get this functionality “for free”, and it’s a good idea to make use of it. I prefer to prefix my log entries with the complete URL the user submitted when the error occurred. This is a succinct way of separating errors between different virtual hosts, and often pinpoints the exact conditions required (i.e., a particular set of GET arguments in the query string) to reproduce the error. So, my log entry looks like this:

// log error to error database
var('desc') = ('[' + client_url + '] ' + error_code + ': ' + error_msg);
log_critical($desc);

However, for the reasons outlined above, I also log additional information to my own custom audit tables, where I can include much more information that I can search and filter as needed. Whether you log to a database or a file, some of the things you should consider logging include:

Information about the user.
If your application requires a login, and the user is authenticated, log the user’s ID. You may also want to log another unique identifier that is easier to use when filtering messages, such as a username or email address. If you use Lasso’s built-in session manager, you may also want to log the user’s session ID (using the [session_id] tag), in case there’s a clue in the sessions table.

Information about the request.
Log the complete request header sent by the user agent using [client_headers]. I also prefer to break out the client’s IP using [client_address], and, as mentioned above, the complete URL requested using [client_url]. You could also log the user agent separately (Bil Corry’s [lp_client_browser] tag is especially useful for this), but the raw form of that information is already included in [client_headers], and since we’re logging server-side errors, it’s usually of little value in debugging.

Send A Notification

In addition to logging, the application should immediately notify the developer when a problem occurs. The simplest solution is to use [email_send] to fire off an email containing the same information you chose to log. Other possibilities include gateways to other realtime messaging systems like SMS, IM, or even Twitter — anything you tend to check on a regular basis. It may seem like a nuisance if a given site starts to generate a lot of errors, but remember that it’s also a nuisance to your clients, customers, and end users. Of course, you’ll want to disable notifications when in a development environment.

Further Reading

LassoSoft has published several Tip of the Week articles on the subject of error management:

The pageblocks framework includes integrated error management:

Styling Form Controls: Next Gen Browsers Let Us Decide.

Web forms present unique challenges for designers and developers alike. HTML provides a limited and increasingly outmoded set of standard controls, all rendered with subtle (and sometimes not-so-subtle) differences by various browsers and platforms. And even though, technically, CSS can be applied to these controls, each browser is unique in selectively ignoring some or all of the rules, instead often deferring to the operating system to provide platform-native widgets. As a result, attempting to create a consistent cross-browser UI has thus far been an exercise in futility. Over at 456 Berea Street, Roger Johansson has done some rather exhaustive testing and concluded it’s a lost cause. UX guru Eric Meyers agrees.

Even the notion of whether form controls should be style-able at all is the subject of much controversy, with two opposing camps staunchly divided. In one camp are those who view web forms as an extension of the browser, which is itself an extension of the operating system. This camp maintains that web forms should be governed by the same rules as the rest of the local system UI. Therefore, widgets should appear in OS-native form, their appearance never altered beyond the quietest adjustments to width or alignment. Their chief argument is that this consistency of form helps inexperienced users recognize and comprehend the function of input elements because they are used to seeing them in other applications on the system.

The opposing camp views the web itself as the platform, and the myriad different browsers mere portals to it. In this view, consistency is still important, but the local system is replaced by the web application itself. People now list web applications by name on their resum√©s. They skim Yahoo! News headlines on their Blackberries and Twitter from their phones. You can even buy a video camera with a YouTube setting. And these people aren’t just the technorati anymore. These are your friends, your family, your coworkers. Given the various ways a user might reach your application, it becomes a basic customer service imperative to present the most consistent UI possible across all user agents and platforms.

It’s a tall order, but the good news is, progress is being made in the latest generation of browsers. And interestingly enough, that progress comes in the form of a compromise that should make you happy regardless of which side of the fence you’re on. The latest versions of Firefox, Opera, and Safari all render form elements in essentially the same way. If no styling is applied, they are presented in OS-native form. However, applying CSS rules causes them to be displayed using a standard box model. The result isn’t perfect, but it brings us much, much closer to a truly consistent cross-browser UI than we’ve ever been before. Check out the screenshots below to see for yourself.

Firefox 3 - unstyled
Firefox 3, unstyled.

Firefox 3
Firefox 3, styled.

Opera 9.5 - unstyled
Opera 9.5, unstyled.

Opera 9.5
Opera 9.5, styled.

Safari 3 - unstyled
Safari 3, unstyled.

Safari 3
Safari 3, styled.

Picking apart the differences, we see:

  • File inputs remain an awkward mess to deal with. Opera at least attempts to retain consistency with the state of other elements by replacing the native button widget, but overall, it’s still the most obvious barrier to cross-platform visual consistency. Except for the width property, Firefox completely ignores CSS rules for file inputs.
  • Safari provides a hybrid select menu which retains the general shape and configuration of the Mac-native widget, but adopts the border, font, and color preferences indicated in the stylesheet. It’s interesting to note that Apple chose to use Mac-native form widgets for the default display in the Windows version of Safari as well as the Mac version. Apparently they decided it was more important for Safari to always look like Safari than for Safari to provide OS-native widgets on Windows.
  • Firefox and Safari ignore CSS rules on checkboxes and radio buttons, retaining the default appearance. Opera replaces them with fairly nondescript custom widgets that are likely a little easier to integrate with heavily themed form layouts.
  • Firefox continues to provide the greatest control over option elements. You can even float option elements within a select (this is not new in version 3, btw). As a test, I used this to mimic the control in Apple’s iCal where you select which days of the month an event should repeat, visually conveying a calendar grid while still using the most semantically correct markup for the task at hand. If only other browsers offered this degree of flexibility.

While not perfect, this seems like a big step in the right direction. Let’s see how things look in the same browsers on the Windows platform.

Firefox 3 Win, unstyled.
Firefox 3 (Win) unstyled.

Firefox 3 Win, styled.
Firefox 3 (Win), styled.

Opera 9.5 Win, unstyled.
Opera 9.5 (Win), unstyled.

Opera 9.5 Win, styled.
Opera 9.5 (Win), styled.

Safari 3 Win, unstyled.
Safari 3 (Win), unstyled.

Safari 3 Win, styled.
Safari 3 (Win), styled.

Not bad, huh? Apart from some very minor differences in alignment, padding, and antialiasing, the displays are very consistent. My Ubuntu virtual machine is busted, so I can’t provide Linux comparisons. I can only hope the results are similar in the browsers available for that platform. And so, to wrap up…

What was that? What about Internet Explorer, you say, still the most widely used browser on the ‘net? I was hoping you wouldn’t ask. I certainly don’t consider it a “next generation” web browser, so even though I think it’s fair to exclude it from this discussion, here goes:

Explorer 7, unstyled.
Internet Explorer 7, unstyled.

Explorer 7, styled.
Internet Explorer 7, half-assed.

No big surprise that it’s the least like any of the other browsers tested. Heck, it doesn’t even apply the same rules consistently to its own elements. I think I see about seven different border styles in that last screenshot. And look at how the background color on the fieldset element extends outside the border. What a disaster. I can’t believe Microsoft even bothered to ship this pathetic excuse for a browser. The only positive thing I can say about IE7 is that it’s marginally better than IE6. It’s like they’re using their desktop monopoly to halt the progress of web standards dead in its tracks. Sigh.

p.s. — If you want to play around with the page I used to create these screenshots, you can grab the HTML and CSS here.