Web Toolkit Blog
The official source of information about GWT.
Widget Best Practices / Widget Building
Friday, May 01, 2009
Developers often asked what's the best way to go about building custom widgets. There are of course a number of best practices to consider when answering this question. In this post I'll highlight a few I like to start off with.
Prefer composition to
inheritance
. Unnecessarily exposing implementation details is generally a bad idea, and it's no different when you are building custom widgets. In GWT terms, this means that your custom widgets should typically extend
Composite
.
//
Here the HorizontalPanel implementation is exposed
//
Others may come to depend on the presence of one or more inherited methods
public class OverExtendedWidget extends HorizontalPanel {
public OverExtendedWidget() {
//...
}
}
//
The use of HorizontalPanel is a hidden implementation detail
//
We are free to change the implementation without affecting others
public class ConservativeWidget extends Composite {
private final HorizontalPanel panel;
public ConservativeWidget() {
panel = new HorizontalPanel();
initWidget(panel);
// ...
}
}
Conservative API
. Extending
Composite
also ensures that you do not inadvertently expose methods inherited from the parent classes into your new widget's API. Doing so can quickly lead to other classes depending on not only your class' implementation, but also the implementation of any parent classes. Remember, you can always add to your API. It's generally impossible to take anything away without introducing a breaking change.
public class LabeledTextBox extends Composite {
//
Be wary of multiple constructors as they can quickly get out of hand
public LabeledTextBox(String labelText) {
// ...
}
public LabeledTextBox(String labelText, boolean hideUserEnteredText) {
// ...
}
public LabeledTextBox(String labelText, String textBoxText, boolean hideUserEnteredText) {
// ...
}
//
Only expose getters and setters if you have a use case for them
public void setLabelText(String labelText) {
// ...
}
public String getLabelText() {
// ...
}
}
Recycle and reuse
. When your widgets repeat themselves try to avoid mirroring that repetition in your implementation. Often it is better to compose your new widget out of smaller, reusable widgets. This can both reduce widget complexity and allows improved testability. In a future post we'll explore widget design trade offs and lighter weight alternatives to widget composition to ensure the best possible user experience, which is GWT's ultimate mission (see GWT's
mission statement
for details).
public class LoginPanel extends Composite {
private final VerticalPanel container;
//
We've decomposed the Label/TextBox pair into a separate LabeledTextBox widget class
private final LabeledTextBox usernameTextBox;
private final LabeledTextBox passwordTextBox;
private final Button loginButton;
public LoginPanel() {
container = new VerticalPanel();
initWidget(container);
usernameTextBox = new LabeledTextBox("Username");
container.add(usernameTextBox);
passwordTextBox = new LabeledTextBox("Password", true);
container.add(passwordTextBox);
loginButton = new Button("Login");
container.add(loginButton);
}
@Override
protected void onLoad() {
super.onLoad();
usernameTextBox.setFocus(true);
}
}
Design for real use cases
. Instead of just imagining how your API might be used, you should write actual use cases that you want to support. You can start with a few lines of code which construct and use your new widget. Once you get the hang of it, you may want to consider
test-driven development
, a software development method whereby you write failing tests before you implement the features and functionality to make those same tests pass. While it may feel odd to write code that uses non-existent classes and/or interfaces and doesn't even compile, it can really help to flush out design problems early on. This should also help you with your 'Conservative API' efforts since the compiler will only complain about methods you actually try to use. As a bonus you can use your IDE to help you stub out your code. Eclipse, for example, provides many different kinds of
Quick Fixes
.
Eclipse Quick Fix offers to create a new Java class for you.
Eclipse Quick Fix can add missing methods or make other code changes for you.
Use Style
. Your widgets should have a default look and feel. Your widgets should also allow for easy styling so that they will fit in with the rest of the page they live on. Providing CSS class names to key DOM elements in your widget is a good start. Setting a (primary) style name in the constructor is generally a good practice as well. This allows a designer to easily use different class names for different instances of your widgets on the same page. Browse the source for some of the out of the box GWT widgets to get an idea on how to do this.
public class RadioButton extends CheckBox {
public RadioButton(String name) {
// ...
setStyleName("gwt-RadioButton");
}
//
...
}
Clean up after yourself
. If you do extend Widget directly, use
onLoad()
/
onUnload()
rather than
onAttach()
/
onDetach()
to perform any DOM attachment setup or detachment cleanup. Any JavaScript references, such as event listeners, should be cleaned up in
onUnload()
to avoid memory leaks. Those same references should be setup in
onLoad()
rather than in the widget constructor. See for example the
Checkbox
source code. Recall that the widget life-cycle consists of three stages (1) construction, (2) attach to DOM, (3) detach from DOM. The most common life cycle for a widget is simply 1
→
2. Since GWT is all about building AJAX applications, many of your widgets will at least see a 1
→
2
→
3 life cycle. Widgets that move around the DOM as they are manipulated on the page will likely see a 1
→
2
→
3, 2
→
3, 2
→
3, ... life cycle. A common way to be tripped up by life cycles is when you, say, move your initialization code from widget construction to the
onLoad()
method without realizing that
onLoad()
is called each time your widget is (re)attached to the DOM.
//
This method is called when a widget is attached to the browser's document
@Override
protected void onLoad() {
setEventListener(inputElem, this);
// ...
}
//
This method is called when a widget is detached from the browser's document
@Override
protected void onUnload() {
//
Clear out the inputElem's event listener (breaking the circular
//
reference between it and the widget); avoid memory leaks
setEventListener(inputElem, null);
// ...
}
I hope you enjoyed reading a few high level best practices for widget design. In a follow-up post we'll continue to look at widget design through the eyes of the GWT compiler.
Finally, I hope you join us at
Google I/O
where we will have a number of
sessions
dedicated to GWT and plenty fellow developers to talk to, both on the conference room floor and in the
developer sandbox
.
Labels
accessibility
1
crash
1
dev mode
2
English
172
googlenew
1
gwt
1
IE
1
internet explorer
1
Web Toolkit Blog
172
Archive
2016
Oct
2014
Dec
Nov
Sep
May
Jan
2013
Nov
Oct
Jul
Mar
Feb
2012
Dec
Oct
Sep
Jul
Jun
Jan
2011
Nov
Sep
Aug
Jul
Jun
May
Apr
Mar
Feb
Jan
2010
Dec
Oct
Sep
Aug
Jul
Jun
May
Apr
Mar
Feb
Jan
2009
Dec
Nov
Oct
Sep
Aug
Jul
Jun
May
Apr
Mar
Feb
Jan
2008
Dec
Nov
Oct
Sep
Aug
Jul
Jun
May
Apr
Feb
Jan
2007
Dec
Nov
Oct
Sep
Aug
Jul
Jun
May
Apr
Mar
Feb
Jan
2006
Dec
Nov
Oct
Sep
Aug
Jul
Jun
May
Feed