How QuickUI controls use code to specialize the handling of their content (in ways that might not be supported by Web Components
April 27, 2012
As indicated in the earlier overview comparing QuickUI and Web Components, one significant difference between the two frameworks is that QuickUI allows code to run when a control’s content() property is set, while the Web Components spec does not currently allow for this. This post will attempt to begin making the case for the need for this feature, starting with an analysis of how that feature is used in QuickUI controls today.
The largest public body of QuickUI controls is QuickUI Catalog, which as of this writing includes 76 open source controls that handle a variety of common user interface tasks or serve as demonstrations of how to achieve common behaviors in controls. Of the 76 published controls:
- 32 controls include code that runs when their content() property is set. Since the base Control class already provides a default content() property, these controls are overriding that base implementation. (In some cases, like PopupSource, the class’ content() property override is itself overridden by a subclass like ComboBox.)
- Of the above 32 controls, 23 use their content() property override to delegate content to a sub-element. This is the standard approach in QuickUI for a control to incorporate content from its host. (For a working example, see this jsFiddle, in which a UserTile control delegates its content to a span inside the control. This topic is also covered in the second portion of the QuickUI JavaScript tutorial.) This is roughly analogous to what Web Components spec accomplishes with the proposed <content> element.
- 12 controls (of the 76 in the catalog) are text box variations that delegate their content() property to a text box: either an <input> element of type “text” or a <textarea>. For example, the content() of a ListComboBox will be placed inside an <input>. Historically, HTML input fields have insisted on handling the field’s value through a string “value” property, whereas an element’s content is a DOM subtree. Despite the difference in data type, in many cases the distinction between “value” and “content” feels somewhat arbitrary. The convenience of a content property is just as interesting to a control that wants to render that content in a text box. For example, if a combo box is going to hold a list of animal names, it’s nice to be able to set the default content of that combo box in markup as:<ListComboBox>Dog</ListComboBox>. Naturally, this translation is lossy: if one passes a DOM subtree into such a control’s content() property, it’s to be expected that it will only preserve the subtree’s text. Nevertheless, it is highly useful to be able to define controls that render their primary content in text boxes.
- 20 of the controls override their content() property to perform work whenever the content changes. The following table summarizes these 20 cases:
Control | When content() property is set, the control… | |
AutoSizeTextBox | Recalculates its own height to match that of the content. | |
ColorSwatchButton | Transforms a string color name/value into a color. | |
ColorSwatchTextBox | Transforms a string color name/value into a color. | |
HighlightEffects | Recalculates its height/width. | |
HintTextBox | Shows hint text if the content is empty. | |
Menu | Recalculates the width of a subelement (a “shield” element that must be exactly as wide as the content to achieve a particular visual effect). | |
PackedColumns | Recalculates its layout. | |
PanelWithOverflow | Recalculates its layout. | |
PersistentPanel | Checks to see whether the panel should be docked or not. | |
PopupButton | Adjusts its layout if the content is not empty. | |
Repeater | Copies the content into the array of repeated sub-controls. | |
SearchBox | Enables its search icon if the content is not empty. | |
SlidingPages | Recalculates its height/width. | |
SlidingPagesWithDots | Updates the number of page buttons to match the number of pages (i.e., child nodes) in the content. | |
Switch | Determines which child should be visible. | |
Tab | Lets the parent tab set know the tab’s size may have changed. | |
TabSet | Creates a tab button for each tab page. | |
TextBox | Generates a programmatic “change” event. | |
TextCondenser | Determines whether the text should appear condensed to help it fit. | |
ValidatingTextBox | Validates the contents. |
To summarize, these controls are doing the following types of work when their content changes:
- Adjust its dimensions or the dimensions of some subelements (e.g., AutoSizeTextBox, Menu).
- Layout contents to achieve results not directly supported in HTML and CSS (e.g., PackedColumns, PanelWithOverflow).
- Transform or manipulate the content before rendering it (e.g., Repeater, ColorSwatch).
- Update its own subelements based on the content (e.g., TabSet, SlidingPagesWithDots).
- Validating content (e.g., ValidatingTextBox, and its subclasses like DateTextBox).
Such controls represent a significant percentage of the QuickUI Catalog — approximately 25% — and it’s very likely that similar results would be found in other QuickUI-based projects. And in addition to the scenarios listed above, other scenarios likely exist in which a control wants to perform work when its content changes.
Overall, this pass through the QuickUI Catalog suggests that many interesting user interface components have a need to perform work when their content is set — to do something more than passively hold the content they’re passed. At this point, it’s not exactly whether the aforementioned QuickUI controls could be successfully ported to Web Components as the spec currently stands, which would be unfortunate. (As stated in the previous post, a long-term vision for the QuickUI framework is that controls created in QuickUI can be transitioned to a Web Components foundation in the future.)
It’s possible that a component could use forthcoming support for DOM mutation observers could be used to track changes to its own content, but whether this would work, or work well, is not yet known. A control could also force its host to invoke some method on the control whenever the host changes the control’s content, but that would be unfortunate; it would place extra work on the developer, and a host’s failure to properly notify the control that its contents have changed could lead to subtle bugs.