Sunday, October 6, 2013

Removing Markup from UI Widgets to Build Reusable and Flexible Components

I've been quite impressed with the simplicity and flexibility of the Twitter Bootstrap CSS framework. Its becoming the basis of many of the projects I'm starting. Since it does 90% or more of what I need anyways, why start from scratch? That said, the accompanying widget library is not always as robust as I need and I generally have to step outside of the Bootstrap world to find something that provides the required functionality. However, most of those widgets tend to have their own style definitions that don't always play well with Bootstrap. I started moving away from widget libraries like jQuery UI which come with their own CSS framework and have been turning instead to widgets that offer hooks to define the content and styling that is rendered by the widget. This approach makes a lot of sense if you're accustom to a MVC framework where the logic and presentation are generally decoupled to enable easy restructuring and styling to promote reuse and modularization.

The fact that this approach may have been feasible was further enforced when I found the kylestetz/CLNDR calendar widget which attempts to separate the actual content of the calendar from the underlying logic that organizes the data to be rendered in the calendar. Considering the number of calendar widgets out there and the various attempts to provide styling hooks, this is an interesting solution to the problem. I've been looking for a search-ahead multiple item list widget to use for various use cases including entering tags, choosing contacts, or just about anything where a user can search for an item and build a list of them to form a group. The jQuery TokenInput widget looked the most promising and provided a lot of flexibility for overriding styles. However, it still had too much of an opinion about the structure of the main widget content that changing it to enable using a framework like Bootstrap would require a little more coding than I thought made sense.

At this point, I decided to challenge myself to build a widget based on the jQuery UI widget framework but not rely on specific markup structure or styling. The widget should define a way to call out points in the mark up that are related to a specific role the widget control logic will provide but where that point is relative to other content should not matter. That statement really makes no sense without an example - I usually will start with what I want the widget to do and then figure out how to make it do it. In the case of this MultiSearch widget, there's the main widget container, an input box, a container for the picker drop down, a container for the actual list of items in the picker, and a container for the list of selected items. An example of what you might build with the widget looks like this:

And the most simple HTML to represent these "roles" might look like this:

And then initialize the widget with the provided content:

   $("#myMultiSearch").multisearch({ ... });

With this layout, there's no assumption about where the picker is relative to the input. The input doesn't have to be contained by the selected-list. The only assumptions made are:
  1. All the content is contained inside the widget's containing element. This makes sense because the element you target when initializing the widget should hold all the content relevant to that widget. It will only inspect the content inside its bounds.
  2. The actual items that will be added to the selected-list and picker-list elements are expected to be direct children of those elements. Each item can have any amount of HTML defined, but ultimately, the item will be added as a child of the containing element. Since the widget will handle adding the items, it will enforce this rule by attaching the role to the items as they are added to the DOM.

Both of those assumptions seem fairly reasonable. Since the widget will be providing most of the manipulation control inside the widget, some basic ground rules need to be established to keep things reasonably doable. The only thing left to define is the repeating content for the picker and selected item list. The easiest approach to defining this content was to use the Underscore micro template function. With that in mind, the item markup might look like this:

And would be passed as an option to the widget by compiling it with _.template():


      formatPickerItem: _.template( $('#picker-item').html() ),
      formatSelectedItem: _.template( $('#selected-item').html() ),


That's almost all the information the widget needs to render the content. Internally, its not generating any of it - just using what was defined when it was initialized or by calling those format functions when building the repeating item content. There are two content/style issues that need to be addressed which affect the visual appearance:

  1. Mouse interactions like hovering or selecting should allow changing the styles to provide visual cues to the user. In Bootstrap, virtually all anchor elements have :hover and :active pseudo selector styles defined. However, those can't be triggered from code. Since there are places in the widget where the keyboard can be used to navigate both the picker list and the currently selected item list, classes need to be set to trigger similar styles. I decided to trigger events for all of these actions and allow external handling. If its not handled, the widget will fall back to a default class.
  2. Search term highlighting presented another challenge since it conveniently encapsulated a regex to find the search term and wrap it with <strong> tags. Right now the widget doesn't use a callback or event to override this behavior. Since the widget instance is available in, it is possible to change - even if its a bit of a hack.

So far, I'm happy with the direction of the widget. It took several rounds of refactoring to design out all of the structure assumptions and provide enough hooks to enable controlling key points in the various control life-cycles the widget defines. More information and demos are available on the main project page for the widget.