I’ve designed a number of software widgets, components and gadgets throughout my career. In the early days of Mac programming I created small desktop accessories and my own set of components to enhance my application development (e.g., geographical map component, 3D graphics library).
Later in the days of Motif I wrote numerous components (spreadsheets, charts, directed graphs, server-side grids). I also made a trek through the TCL community creating components for the incr Widgets set.
I spent several years working in the world of Java Swing building components (in fact I built at least three completely different component sets for three different companies—thank God that we now see more things getting open sourced!)
I finally ended up in the land of web development taking trysts through HTC components, JSP/Struts components (tag libraries) and finally pure JavaScript components. There are a lot of lessons I have learned over the years in how to create useful components. There are three that stand out in my thinking:
- Make the component specific in purpose, yet flexible in use.
Avoid the do-everything component. Instead make it do the main thing well. But make it pragmatic by keeping it flexible.
- Separate the concerns of interface, interaction, and content.
Avoid hard-coding visual style, give flexibility in the interaction model and provide flexible ways to manage data content. Keep these three areas independent to allow them to be customized separately.
- Document the interface and use the component before releasing it.
You know the experience. You think you have a great idea. Then when you go to explain it to another person you immediately see the holes in your logic. Documentation provides a way to explain your interface; writing demos allow you to exercise the component to test its ease of use and flexibility.
Make it Specific yet FlexibleMake the component specific in purpose, yet flexible in use.The
agile community expresses the idea of simplicity over over-generalizing. One of the traps that people fall into is trying to build a component that does everything. Not unlike feature creep in application development, a component can collapse under the weight of too many features. One of the sure signs of this is when certain parts of the API start competing with other parts of the API.
As an example, let's look at the
carousel. It is just a simple list view manager. You can actually make the carousel look like a
tabset or a
slideshow. However, I chose not to provide additional tabset or slideshow functionality. Why? I knew from experience that the interface would become unwieldy. Lots of new configuration parameters and methods would get added that only made sense if you were using the carousel strictly in slideshow mode. You can imagine that with enough features, some of the API would only apply to these tangential behaviors. The result would be a carousel component that ends up with portions of API that don’t even apply when the carousel is behaving “just” as a carousel.
A nice way to keep a component flexible is by providing a simple configuration mechanism. The Yahoo! UI library provides a Configuration object for just this purpose. (The first time I saw this mechanism in popular use was with the
Prototype library.) Instead of having to pass lots of parameters to the carousel’s constructor (new Carousel()), you pass in a configuration object. The configuration object is really just an object that contains parameters and values encoded in JavaScript object notation. Here is an example from the wrap example:
var carousel = new YAHOO.extension.Carousel("dhtml-carousel",
{
numVisible: 4,
animationSpeed: .8,
animationMethod: YAHOO.util.Easing.easeBoth,
scrollInc: 3,
navMargin: 40,
size: 17,
loadInitHandler: loadInitialItems,
prevElementID: "prev-arrow",
nextElementID: "next-arrow",
loadNextHandler: loadNextItems,
loadPrevHandler: loadPrevItems,
prevButtonStateHandler: handlePrevButtonState,
nextButtonStateHandler: handleNextButtonState,
wrap: true
}
);
Each configuration parameter has a default value. If the user provides a configuration parameter it is treated as an override and is applied to the internal set of configuration values. The carousel then uses these values in its operation.
Keep Concerns Separate Separate the concerns of interface, interaction, and content.
If you have read other articles on my blog or heard me speak you will notice that I often fall back on this idea of separation of concerns along the lines of interface, interaction and content (or data). Sometimes I express it as feedback, interaction and information. And often I just state in the way I first learned it from the boys at Xerox as simply
Model-View-Controller.
When I wrote the carousel I started with the basic understanding that I was creating a list view manager. The underlying HTML structure I chose to control was an “unordered list” (UL & LI HTML tags). Keeping my thinking general in this matter allowed me to separate several areas of concern. Here is how this concept of separation played out in the carousel.
1. Flexibility of Visual StyleFirst I wanted to provide flexibility in the component’s presentation. One of the ways I did this was by putting all style choices into a stylesheet (CSS). It’s really easy to hard-code style choices into the component logic. By placing style considerations externally you can help fulfill the first point above – keep it flexible. The other way the presentation was made flexible was by providing configuration parameters that select the way items are displayed. Control over the number of visible items, the animation speed and method, the size of a page of information, and the orientation are all ways that the carousel is flexible enough to adapt to most environments.
2. Flexibility of InteractionSecond, I decided that the interaction should be completely flexible. I did not want to constrain it to my choice of navigation controls or method of user interaction. In most of the current examples, previous and next arrow buttons control the carousel. While this is the normal case, it is easy enough to allow mouse over events drive the interface. The carousel exposes the same interfaces it uses internally. You can simply wire your events to the same internal mechanisms used by the carousel internally.
Microsoft Altas (now called Microsoft Ajax Framework) does a nice job of separating behavior from its components. For example, they isolated the behavior of auto complete from the components. This means that if you add this behavior to say a text field you would have an auto complete text field. It also means that you can write your own components and by obeying an interface contract, have your component support this behavior. In essense the interaction is packaged as a separate entity and just attached to the component needing the behavior.
3. Flexibility in ContentAnd third, I felt that there had to be flexibility in the way content gets displayed in the carousel. The simplest mechanism for this was to allow developers a way to shove their own HTML into any item in the list. The innerHTML technique is very useful in this regard. It is very friendly to PHP, JSP, ASP and other page generation techniques. It’s also the native way to express an interface on the web. What this means is that each “item” in the list could be a complete page of content, a single photo from a photo stream, a set of form elements—in short anything you want a “list item” to be it can be.
Another dimension of content is in how the data actually gets loaded. By externalizing the loading mechanism through a set of function callbacks (functions that you write that let you decide how to load the initial set of data and fetch the next and previous set of data) the component is able to support static data, client side generated data or server-side fetched data (e.g. via Ajax). Recently someone wanted to wire the carousel up to RSS feeds. Even though I had not considered RSS feeds, since the data loading was externalized, it turned out to be a fairly trivial exercise.
Test by Documentation and UsageDocument the interface and use the component before releasing it.
Alan Cooper advocates that designers design in pairs. One takes the role of Interaction Designer (ID), the other the role of Document Communicator (DC). In design sessions the ID takes the lead role in pitching ideas. The DC tries to capture the ideas and wherever there is difficulty in capturing or explaining the idea it is forced to go through some iterations to make it easier to communicate. The idea is that by having to explain your ideas. This doesn’t just work in designing user interfaces, but it also works in designing programmer interfaces.
As I am writing this blog post I am sitting next to
Luke Wroblewski on a flight back from Lisbon. He saw this point in my article and reminded me of a post he wrote a while back on
Nine Lessons from Nine Years of Interface Design. One of the lessons he notes is that “writing it down forces you to think it through.” On this point he summarize three good reasons for writing it down:
- Solidifies the approach. Guards against localized decisions.
- Articulates the rationale. Puts the solution in context of its use.
- Ensures clarity. If you can’t explain it, then nobody else will be able to either.
I find the exercise of both documenting and writing demos for components to be refreshing. It gives you a chance to come above the code cloud and both talk to the user and be the user of the component you are writing.
I finished the first version of the carousel in just 2 days. I spent the better part of the next 2 days writing documentation and creating demos for it. All during that phase I was constantly going back and rewriting parts of the code. Documentation can seen as a kind of test-first strategy for writing APIs. Writing demos is a good way to create tutorials as well as experience what it is like to really use your component.
Of course the biggest caution I would have about trusting too much in the demo applications you write is to realize that they are only demo applications. It really takes being exercised in several real world examples to fine tune an interface.
This lesson follows something that we try to employ at Yahoo! We call it “eat your own dog food.”
The phenomona of
Yahoo! Hack Day has had a profound impact on the web services and APIs throughout Yahoo! (I would go so far as arguing that this has been the biggest impact of Hack Day—causing our services and APIs to become more robust.) When the Hack Team attempted to put together a combination of our APIs (events, maps, search, etc.) they discovered just how many things were missing. The creation of just that one demo pushed the discussion of what needed to be added or corrected—-and the result is a much richer API available to everyone (public included.)