Plugging source lists into sockets
Once upon a time (apparently June 20, 2006) the Coderspiel staff went to the Soho Apple Store to see that John Gruber guy. He’s probably the biggest independent weblogger on the technical internet, and one of the few that combines wide subject knowledge with reasoned arguments laid out in polished language (when he’s not throwing a fit), so we went to figure him out.
On that front we learned that he has a computer science degree from Drexel. He got interested in design, or Macs, or interfaces or something (sorry, no notes!) when he was laying out his campus newspaper in Quark. As the Editor in Chief! That was the detail we were looking for actually, one that isn’t in his short Wikipedia bio. It explains Daring Fireball more than anything else.
The other thing we learned before we getting hungry and leaving Gruber’s talk was the name source list—yet another code word of the internet design clique. Everybody found out about typeface being more correct than font (when you aren’t talking about a particular size and weight), so they had to come up with another way to distinguish themselves. Source list is the neologism for the System 6 control panel’s navigation metaphor, whereby a constantly visible list determines the content of an associated block. Simple.
Of course, no one could leave those exquisitely simple source lists alone. OS/2 turned the idea into a gigantic notebook, complete with spiral binding and pages that flipped within tabbed sections. Microsoft, not to be outdone, designed some pretty slick chiseled tabs that put the source list horizontal. The problem with that genius idea is you can only fit about four things in the list before you have to wrap around, making three or more levels of tabs common. That complicates the metaphor and is hard to skim through, but everyone liked the way it looked so we are stuck with it forever.
Even though Baroque tabs have taken over desktop interfaces and many on the internet, it turns out that the Databinder examples have always had a few basic source lists. The potential to factor out some of their logic and markup has been there all along, but having the terminology and conceptual framework was the boost that Databinder needed to get it done.
Enter SourceList
, which isn’t itself a component but a handler for them. This is how you use it:
SourceList sourceList = new SourceList();
SourceLink showLink = sourceList.new SourceLink("show", showRecipe);
SourceLink editLink = sourceList.new SourceLink("edit", editRecipe);
What, did you forget about obj.new
? Us too. But it’s perfect here, constructing the link and binding it to the list in one step. The links set their target components to visible and the previous target to hidden. Because there’s no enforced structure you can put the a SourceLink
anywhere you want. The end.
Just kidding. This kind of static source list has broad applications in Wicket, but we can cast source lists in another way that applies to practically every data-driven application: the list for selecting an item to edit or view in detail on the same page. In Wicket that means setting the model object of the target component, and now Databinder has a ModelSourceLink
for that basic purpose.
Following that, we can factor out the code to display these links in a panel fed by a list model and targeting a single component. First, with a base class SourceListPanel
that defines the markup as an unordered list with a stylesheet class source-list
. Then the concrete ModelSourceListPanel
ties that to ModelSourceLink
objects. If you wanted to update the model object of this
with objects from a list model:
new ModelSourceListPanel("recipes", this, "name", recipeListModel);
That is seriously handy! But we can put just a little bit more air in this balloon, because there is one more thing we often do with lists of items: link to bookmarkable pages. For this a PageSourceLink
class factors out the annoying code of setting a page parameter (id
by default) from a Hibernate identifier. Then we use those in a source list panel subclass, PageSourceListPanel
. And suddenly making that list of bookmarkable links is easy:
new PageSourceListPanel("recipes", RecipeBook.class, "name", recipeListModel);
Can you guess what example has been updated (and shortened) to show off these classes? It’s in the example archive for Databinder 1.1-beta2 (which, by the way, is out). Enjoy.
Sockets and plugs
Databinder’s other new earthshaking UI curiosity doesn’t even exist in code. It’s just some words we stole from the real world: socket and plug. They’re short and easy to type, and they just might help us defeat the scourge of extensible generic components.
The problem as ever with generic Panel
subclasses is that users will want to change markup and add to it at arbitrary places. Those people will never be satisfied. But we can at least provide them with multiple extension points (something that <wicket:extend>
doesn’t support) in a consistent way.
Thus we define socket in this context as one such extension point, and plug as something that occupies it. Toss them in a template like DataProfilePanel.html
:
<div wicket:id="lowFormSocket" />
And its class:
add(lowFormSocket("lowFormSocket"));
}
protected Component lowFormSocket(String id) {
return new NullPlug(id);
}
There is a tiny piece of library support here. NullPlug
is a component that doesn’t render anything unless for some reason you give it a body in the markup. If you want to put a plug in that low-form socket, as Typeturner does, you just override the method:
protected Component lowFormSocket(String id) {
return new Plug(id);
}
class Plug extends Panel { ... }
This technique works best, to be honest, when the generic panel’s users work with the panel author (or if they’re the same person!) so extension points can grow naturally. Exactly where a socket would be useful isn’t often apparent until you need it there. But at least with a consistent approach you can plunk them in without sweating the names and the mechanism.
And you never know—socket and plug might be programming clique code words some day.
Add a comment