I was using GWT's HorizontalSplitPanel. The thing is that it was a quite good solution but not everything I wanted. GWT standard widget–set is known for being on the Spartan side. I have no complaint about this, it is just a fact. The added behaviour I was after is the ability to double-click the splitter in order to toggle a fully collapsed or restored state. I'm not the only one, it seems.
This was not as straight–forward to achieve as I thought it would be. This is an account of the dead-end strategies I followed in trying to achieve this and the solution I settled on.
Inheritance strategy
My first (possibly lazy) approach was to use an inheritance strategy to extend HorizontalSplitPanel. Anyone whose familiar with the GWT standard widget set will know that this approach is a non–starter, since HorizontalSplitPanel is final so it can't be extended and — one step further up the class hierarchy — SplitPanel has package-local access so that can‘t be extended either (not in a class outside the same package in any event).
So of course, I put my best–practice hat back on to try and solve the problem properly.
Composition
So I used a SimplePanel as the base class for my widget as follows.
private String presetPosition;
private HorizontalSplitPanel splitPanel;
public ElegantHorizSplitPanel() {
splitPanel = new HorizontalSplitPanel();
this.add(splitPanel);
setSplitPosition("30%");
}
public void setLeftWidget(Widget widget) {
splitPanel.setLeftWidget(widget);
}
public void setRightWidget(Widget widget) {
splitPanel.setRightWidget(widget);
}
public void setSplitPosition(String position) {
presetPosition = position;
splitPanel.setSplitPosition(position);
}
}
This implementation does not support all the functionality provided by HorizontalSplitPanel — in fact it does nothing more than delegate what little functionality it does support directly to HorizontalSplitPanel. However, it provides enough detail to demonstrate the technique. The methods providing all the other functionality could be added fairly easily.
So now on to adding double-lick behaviour. To do this I needed to get a reference to the the splitter DOM element. Unfortunately — but probably for good reasons — the getter for this element is protected in HorizontalSplitPanel . So, this means we have to cheat a little. GWT exposes some of the DOM traversal JavaScript methods in the Widget API, so provided I could get hold of an element in splitter‘s ancestry somewhere there was hope.
I used Firebug to find out what HTML was generated by my new widget and found the following.

So in order to get a reference to the splitter element I need the element returned by:
But that – as we all know – is a what @unclebobmartin (and others) describes as a train wreck, so I added the following methods to ElegantHorizSplitPanel
// ...
private Element getSplitterElement() {
return getLeftElement().getNextSiblingElement();
}
private Element getLeftElement() {
return getSplitPanelInnerElement().getFirstChildElement();
}
private Element getSplitPanelInnerElement() {
return getSplitPanelOuterElement().getFirstChildElement();
}
private Element getSplitPanelOuterElement() {
return getElement().getFirstChildElement();
}
// ...
}
Caveat: I checked IE HTML as well and it uses more or less the same HTML, though I suppose it's possible that compiling for different user agents could result in different HTML. I could probably add some test coverage for this using Matt Raible's guide to testing GWT libraries with Selenium, something to look into in future.
Adding double-click behaviour
I‘m more familiar with JavaScript and the DOM than I am GWT so my first approach to adding the behaviour was add native JavaScript code to attach the behaviour to the splitter element.
// ...
private void toggleLeftPanel() {
int leftWidth = getLeftElement().getClientWidth();
if(leftWidth == 0) {
splitPanel.setSplitPosition(presetPosition);
} else {
splitPanel.setSplitPosition("0px");
}
}
private native void addBehaviour(Element splitter)/*-{
var panel = this;
splitter.ondblclick = function() {
panel.@com.malethan.gwt.ui.widgets.client.ElegantHorizSplitPanel::toggleLeftPanel()()
};
}-*/;
// ...
}
This actually works just fine in IE but is patchy in Firefox for some reason. But more importantly it introduces a reference cycle and is an almost guaranteed way to introduce a memory leak in most browsers.
Reference cycles & memory leaks.
Well, Google had already thought of that. They provide a standard, non–native way to add event handlers to DOM elements. So the actual code we use to add the double–click behaviour to the splitter element is the following:
// ...
private void addBehaviour(Element splitter) {
Event.setEventListener(splitter, new EventListener() {
public void onBrowserEvent(Event event) {
if(event.getTypeInt() == Event.ONDBLCLICK) {
toggleLeftPanel();
}
}
});
Event.sinkEvents(splitter, Event.ONDBLCLICK);
}
// ...
}
N.B. When I started this post I had only tested the above technique in IE. It works great. Unfortunately it became quickly apparent that when I tried this in Firefox (running on Ubuntu Jaunty) it worked sporadically if at all. If anyone knows of a way to get this working in Firefox, please let me know and I'll post a correction.
First published on Nov 30, 2009. Last updated on: Dec 29, 2009.
