Tuesday, December 23, 2008

Composite Widgets and Blur Events

It is a common need in web applications to create a single logical widget (aka UI component) that internally is composed of multiple native html elements. A few examples:
  • A DateWidget composed of 3 html SELECT elements (month, day and year)
  • A custom combobox composed of a textbox, a button (placed to the right of the textbox) and a popup (or more accurately a drop-down) div that contains a pick list and appears when the button is clicked or a letter is typed.
  • A DatePicker (same as custom combobox mentioned above, except the drop down is a calendar instead of a pick list)
Below is a problem with this kind of widget that i have not been able to easily solve:
Problem Definition
It is often desirable to have a single blur event that applies to the composite as a whole. Using the blur event for the internal html elements may be the wrong granularity for validation or change notifications. For example, suppose that for the above mentioned combobox, we chose to use the textbox's blur event (because the combobox as a whole has no blur event) for validation. Then imagine this scenario:
  1. The user types 1 letter into the textbox
  2. The popup appears
  3. User clicks on the popup - perhaps to scroll the pick list or (in the case of the DatePicker) to scroll the month
  4. Boom! The TextBox's change and blur events fire.
  5. Our app, in response to the blur event, attempts to validate
  6. Our validation function reads the value in the textbox and, detecting that it is only 1 character long, displays an error message.
This is clearly not the desired behavior.

Desired Behavior 

Test #1:
Does the composite widget have its own blur event as distinct from those of its child widgets? And (more to the point) is there an obvious way to add a blur listener to the composite (again, as distinct from its child elements)?

Test #2: 
Assuming we passed test #1, does the composite's blur event fire in a way that is logical for the composite as a whole:

Test 2a (comboBox with down-arrow trigger button):
  1. Click into the textbox portion of the combobox
  2. Type a few letters
  3. Click the down-arrow-button (to reveal the drop down list). This should not fire the composite's blur event.
Test 2b (date picker with no trigger button):
  1. The user types 1 letter into the textbox
  2. This causes the popup calendar to appear
  3. The popup contains a "Scroll Next Month" button. Click on that. This should not fire the composite's blur event.
Workarounds
This isn't a deal breaker issue for most applications. We can just hold off on validation until later, when the entire form is submitted. But none-the-less, many customers prefer field-level errors to appear sooner rather than later. This is, after all, one of reasons we have blur and change events.

Other Widget Libraries
I did a quick survey of composite widgets from various web widget libraries, to see if they handled blur events as desired (see "desired behavior" above). Here is what I found:

Library Widget Composite blur/change events Demo Link
DOJO dijit.form.FilteringSelect Yes TODO
jQuery ui.datepicker No TODO
Spry SuggestText ?? No TODO
GWT 1.5 SuggestBox No TODO
GWT 1.6 SuggestBox Maybe TODO
GWT Incubator DateBox No TODO

Possible Solutions
  • Timer solutions. TODO: elaborate
  • Global focus tracker. TODO: elaborate
  • Try to figure out what DOJO did (their solution was not easy for me to follow)
Event Bubbling
Note that what we are describing here is not the same as bubbling. Event bubbling refers to the fact that if a child element does not handle an event (or more specifically cancel the event) then that event bubbles up to the parent element. But a div containing 3 select elements (as in the date selector described above) would still fire 3 change events in either case:

Case #1: three select#changeListeners
Case #2: one div#changeListener.

This is not to say, that bubbling might not be used in the solution. But bubbling, in and of itself, is not the solution.

Objective
Come up with a solution that:
  1. Provides the desired behavior described above
  2. Is general enough to componentize (for example, in GWT, a generic parent class called BlurableComposite). Note: GWT has a class called FocusPanel, but I can't figure what it's purpose is and if it is designed to address the problem I am describing here.
  3. If the solution is not general enough to express as a reusable class, then a solution general enough to express in the form of a design pattern/solution
  4. Perhaps tie in the solution with GWT's new HasValue/HasValueChangeHandler (in 1.6 trunk)


No comments: