Opening Pandora's box of widget craziness
In this blog a personal story by Gabor, the lead developer of H5mag, about developing the most user friendly widgets in the H5mag Live Editor. Or as he preferably describes it: "opening Pandora's box of widget craziness".
While I personally prefer bathing in the fluorescent green light of a terminal window, our customers tend to find that experience less enjoyable. That's why we have widgets in our editor: knobs, levers, handles and gauges to get stuff done. The problem with these visually appealing gadgets is that they all behave differently. That's why we propose a uniform widget interface to iron out these idiosyncrasies.
Even native HTML elements have wildly different interfaces. For instance, you can't determine whether a checkbox has been checked by reading out the 'value' property. Likewise, there is no way to set a 'placeholder' in a select. This causes headaches when you need to extract information from these widgets.
To address these issues we have decided to introduce UWI - the Universal Widget Interface. UWI is actually a sort of contract. A UWI compliant widget promises to behave in a predictable way. As such UWI might be an interesting tool for any development team trying te maintain a degree of sanity when confronted with custom user interface elements.
Our UWI 1.0 specification strives to be the simplest solution to build a uniformly behaving widget collection. UWI consists of two parts: rules and methods. A UWI compliant component has to abide by the UWI rules and has to implement all the required UWI interface methods.
To describe the UWI rules, we define two roles: user and system.
- System hides an input field of type 'text'
- System only changes the value of the hidden input field
- System never changes the value of the widget
- System only changes appearance of the widget
- System never changes appearance of the input field (apart from 1)
- User only changes the value of the widget
- User never changes the value of the hidden input field
UWI compliant widgets are always based on HTML input fields of type 'text' because this is the most versatile native HTML form field. Text fields offer a rich feature set implemented by the browser: from maxlength to pattern matching, yet they are easy to debug (simply decloak them to see their value for instance).
A UWI compliant widget hides this field and treats it like a data source. Using observers, a component can monitor the state of the input field and update the appearance of the widget accordingly. This mechanism allows for some interesting dynamics, consider the following scenario:
- User acts on widget
- Widget changes state of input field
- Widget gets notified about changed state of input field
- Widget updates appearance to reflect new state
In this case the widget uses its own monitoring system to update itself following a user interaction. In practice this means very little code. To initialize itself, the widget can rely on the same monitoring functionality as well, reducing the amount of code even further.
Because UWI relies on functionality already provided by the browser there is no need to wrap all possible functionality in custom methods. The result is a very slim interface. A UWI component has to implement the following methods:
the following methods are optional:
The first three methods are pretty straight-forward. The init method is required to hook up the widget to a component initialization infrastructure, like we have in the H5Mag editor. The purpose of the init method is to hide the text field used as data source, build the widget user interface and add listeners to the hidden input field. The recording methods offer some control over the observation process.
Because we use a text field, some values will be harder to represent. For instance if you use an image picker, you can represent all possible images using an URL (i.e. /photos/autumn.png) but you cannot represent something like 'use a random photo'. In this case you need a special value. The optional methods above deal with special values. The getSpecialValues() method returns a map associating special values with their keys. The hasSpecialValue(key) method determines if the value in the hidden input is the special value identified by 'key'. The setSpecialValue(key) method sets the special value identified by 'key'.
Not everyone needs special values, however there is one special value that's always present: the clear value. Therefore, the UWI defines three methods to deal with that ubiquitous value. The clear() method must clear the value of the data source by setting the designated special value. This might be simply an empty string but it might also be a textual value like 'NONE' or 'EMPTY'. The getClear() method returns the string used as clear value. The isClear() method determines whether the current value of the data source equals the clear value.
The special values allow components to 'understand' each other. 'Clear' might be the same as nothing or an empty string but it might also mean something completely different. For instance when building a color picker, 'clear' might mean 'transparent' while 'empty' means 'inherited color'. Similarly, to a date picker, 'clear' might mean 'never' while 'empty' means 'unknown'. This why special values should have keys, keys convey the meaning of the values.
We are currently implementing the UWI specification for a number of controls. We have yet to evaluate its impact on maintainability and complexity of our code. However we hope UWI will prove a useful tool to tackle our somewhat overgrown widget garden.
Want to know more? Take a look at h5mag.com for more information or contact us at firstname.lastname@example.org!