Thursday, September 29, 2011

Disabling jQuery UI Dialogs

jQuery UI dialogs aid in gathering input from the user.  Dialogs jump out from page, usually in response to some user-triggered event such as a button click.  When a dialog is displayed, the page layout — that is, the way the page looked before the dialog was displayed — remains unchanged.  It's almost like a secondary browser window, harmonized with the rest of the user interface thanks to the theme framework.

The dialog widget isn't a perfect fit for everyone — some might prefer the approach of displaying forms in-line on the page as opposed to having the form jump out at them.  But aside from individual user preferences, there are a few implementation issues with displaying data in dialog widgets.  Some major API redesign is lined up for the 1.9 version of the dialog widget, aimed at solving some of these imperfections.

One issue I'm interested in seeing worked out is proper disabling of the dialog widget.  Subtle cases, where not having the dialog entirely disabled, cause some weird side-effects.  And then there are not so subtle breakages where we need to fetch remote data.

Forms and remote data
Forms wouldn't be vary useful in the context of web applications if they didn't somehow interact with the server.  For example, submitting a forms often means dispatching a POST HTTP request from the browser to the application server, resulting in a new resource.  Sending data to the server is one avenue to interacting with remote data — but the user who submitted the form will need feedback — how else will they know if the action succeeded or not?

The response generated from the form submission should be presented to the user.  More often than not, the user will have made an error in one or more fields, so the user interface will need to convey this.  The error message — or error messages — typically go beside the errant field.

Dialog widgets that display forms to users have another potential use for remote application data and that is the form itself.  Imagine a user clicks a registration button, located on the homepage, which then displays the form in a dialog widget.  Now, imagine that this website has several other public informational pages that do not require a login, but nonetheless, we still to display the registration button on these pages.  The registration form is the same on all pages.

So where does the dialog widget get the actual markup for this form?  The jQuery UI dialog widget uses a div element as it's template — whatever is inside the div is displayed when the dialog is opened.  This means that we'll have to insert the form markup on every page where the registration button is present.  A solution to avoid duplicate registration forms is to make an Ajax call when the button is clicked.  Instead of just opening the dialog, we'll populate the div with remote data first — this way we're only duplicating the URI pointing to the form on each page instead of the form itself.

Three methods in which the form displayed inside a jQuery UI dialog can communicate with the application server — through submitting data, through receiving responses, and through loading the initial form.  On their own, the methods aren't difficult to comprehend and implement, but latency and usability are always a concern.  An Ajax-based form can take on several states when presented inside a dialog, some of which require the form be disabled.

Disabling form elements
There comes a point, during the communication from the form to the application server, where it'll need to be disabled.  Disabling means giving the user a visual cue that the element is temporarily paralyzed.  Also, disabled widgets shouldn't respond to user events.

For example, let's say the user has just opened up a registration form inside a dialog widget.  Before it can be displayed, we need to retrieve the actual form HTML and insert it into the dialog.  In the mean time, while the HTTP request is happening in the background, there isn't anything for the user to see — there is nothing to display in the dialog yet.  If we defer displaying the dialog until the form is ready, it appears to the user as though nothing has happened yet — sluggish responses are discouraging.

Display the dialog in a disabled state while the form loads.

What about when the user has actually submitted form data and is awaiting the result?  We could hide the dialog entirely while the Ajax response churns.  But this is clumsy as there is high probability of having to display the form again for the user to correct errors.  It seems the logical solution is to keep the dialog visible, but disabled, just while processing happens.  This is a friendly way, a responsive way, to indicate that stuff is happening.

Simple solution to a difficult problem
There are scenarios when using jQuery UI dialog widgets where it makes sense to disable it.  Especially when dealing with forms as mentioned above.  Sending and receiving remote data while the dialog is open means users can interact with it — unless it's properly disabled.  However, while data is being transferred — from the browser to the server — or vice versa — there is a short latency duration.

Ajax-style applications cannot avoid latency during HTTP requests that take place in the background.  Or, at least they cannot work under the assumption that the network response time will be faster than user actions.  Preventing the user from performing certain actions is easy — it becomes difficult when we take into account the perceived responsiveness of the user interface.  There is a delicate balance between ensuring correct behavior while remaining intuitive.

The solution to this problem, as we'll see in the 1.9 release of the jQuery UI dialog widget is to place a div element over top of the dialog, preventing the user from making changes during background processing.  The best part about this enhancement, however, is that there is now a simple API for the dialog widget that'll make it disabled.  This greatly simplifies many usage scenarios where we're waiting on the application server — we can simply disable the dialog and re-enable it instead of stringing together a boisterous solution.