Introduction
One of the unusual (and powerful) features of the GWT
environment is the ability to load segments of code on demand
only. While the implementation is obviously very sophisticated for the most
part, there is one area (namely the leftover segment) that has
fairly severe restrictions. So long as you can understand this, then there are
definite possibilities for loading large chunks of your application
asynchronously, and the end user experience will be that much better.
How effective are split points
Split points can have a dramatic effect on your initial
application download load times. Here is the current split report from my
application. This includes all major control implementations, up to and
including the date box and the editable grid control. As mentioned elsewhere,
you get the compile report by specifying the –compileReport argument.

Below is the number of bytes that is saved by loading the
DateBox asynchronously.


Split points
The first thing to understand is that the entire project is
divided into segments called ‘split points’ and they have one of three types.
|
Split Point Type
|
Description
|
|
Initial load
|
Usually one split point that is loaded when the application loads.
You goal should be to try and make this as small as possible in size. However
you should not attempt to make it zero as some things must be loaded at the
start so there is no benefit in trying to load them later on e.g. if your
application always has a button on the start screen, then there is no point
trying to load the button controls asynchronously as they are going to need
to be loaded immediately. Needlessly loading things asynchronously will
actually slow your overall application down!
|
|
Exclusive
|
The exclusive split points are completely self-contained and can be loaded
at any time. You want to have as many of these split points as possible, but
at the same time you do not want them too tiny (e.g. a minimum
size of 1000 bytes) and you do not want them too large (e.g. a maximum
size of 50000 bytes). Also be careful of what happens to the leftover split
point (see below) as you make more exclusive split points. You may see this
growing very large, very quickly
|
|
Leftover
|
The leftover split point is interesting. It is not loaded at start-up
but is completely loaded when the first exclusive split point is loaded. The
left-over split point contains synchronous code that is referenced by two or
more exclusive split points. This is the principle reason why I say the split
point loading is not very sophisticated in this particular area. Suppose you
have 20 exclusive split points and two of them make use of a large chunk of
common code. This means it will be in the leftovers split point so it will be
loaded as soon as any exclusive split point is loaded (even if that split
point is not using the common code).
You definitely want to make the leftover split point as small as
possible but sometimes it is impossible to make it any smaller. So long as
you understand why this is then you will not waste your time trying to reduce
it. Hopefully the DateBox wrapper example will help you to understand what is
going on.
|
Put everything in one call
The key thing to understand is that every interaction with
the exclusive split point must be completely self-contained in one function
call. In general you can feed in input data (during the object creation) but it
is difficult to get output data unless you are prepared to work with
asynchronous concepts. In general these concepts are either events or call
back functions. In this example, I show you how to wrap the DateBox
control, so that it behaves like a synchronise control but is actually
asynchronous in its internal implementation. We use events in this
example. The code pattern we want to support is the following
|
void createDateBox(){
MyDateBox dateControl = new NavWinAsynchDateBox();
HorizontalPanel panelControl = new HorizontalPanel();
panelControl.add(dateControl);
dateControl.setValue(new Date());
}
void someEventHandler(){
Window.alert(“” + dateControl.getValue());
}
|
Declare the class
Normally, if you extend a control you use the extends
keyword e.g.
|
public class MyDateBox extends DateBox
|
For asynchronous loading you must not do this as you
will be indirectly creating the control synchronously, whenever you create an
instance of MyDateBox. The DateBox creation must occur in the asynchronous code
later on. The correct declaration pattern to use is this.
|
public class NavWinAsynchDateBox extends HorizontalPanel
{
private DateBox _dateBox = null;
private Date _currentDate = null;
}
|
In the above example, we assume that the HorizontalPanel
will be loaded in the initial load in any case, as it is so fundamental to the
GWT GUI. You are therefore free to use it anywhere in the code.
Declare the synchronous functions
Now we want to declare two functions that allow us to set
the control date and get the date back. Note these are synchronous functions
and therefore they cannot directly access any part of the DateBox control interface,
otherwise the entire DateBox will be forced into the left-overs section or
initial load.
|
public class NavWinAsynchDateBox extends HorizontalPanel
{
...
private Date _currentDate = null;
public void setValue(Date date)
{
_currentDate = date;
}
public Date getValue()
{
return _currentDate;
}
}
|
Create the DateBox asynchronously
We are now ready to create the actual DateBox control. We
can either use a dedicated synchronous method, or for simplicity, we will reuse
the setValue() method as this is the only access function we intend to use
which actually accesses the underlying DateBox.
|
public void setValue(Date date, String width)
{
_currentDate = date;
final NavWinAsynchDateBox finalThis = this;
final String finalWidth = width;
GWT.runAsync(new RunAsyncCallback()
{
public void onFailure(Throwable caught) {
NavWinClient.Client.DisplayError("Download
code - DateBox");
}
public void onSuccess() {
if (_dateBox == null)
{
finalThis.CreateDateBoxWithEventHandlers();
}
_dateBox.setValue(_currentDate);
if (!finalWidth.equals(NavWinMessage.EMPTY_STRING))
{
_dateBox.setWidth(finalWidth);
}
}
});
}
|
The event handlers are set up in the function
CreateDateBoxWithEventHandlers() below. Notice it is the fact that the change
event handler sets a class level variable (_currentDate) to the current
contents of the DateBox that allows the getValue() to work later on without
accessing the DateBox itself. Also note we override the default display of the
date box in order to remove the time element. If you want to use the default
date and time display used by the date box then you can delete the whole block
_dateBox.setFormat() {…}
|
private void CreateDateBoxWithEventHandlers()
{
_dateBox = new DateBox();
this.add(_dateBox);
_dateBox.addValueChangeHandler(new
ValueChangeHandler<Date>(){
@Override
public void onValueChange(ValueChangeEvent<Date>
event) {
// TODO Auto-generated method stub
_currentDate = event.getValue();
}
});
_dateBox.setFormat(new DateBox.Format() {
@Override
public String format(DateBox dateBox, Date
date) {
return FormatDate(date);
}
@Override
public Date parse(DateBox dateBox, String
text, boolean reportError)
{
return ParseDate(text, new Date());
}
@Override
public void reset(DateBox
dateBox, boolean abandon) {
// TODO Auto-generated method stub
}
});
}
|
The left-over code
Although the above implementation makes a very big saving,
it is not perfect because the DateBox makes use of other controls such as the
PopupPanel. Because I use the PopupPanel in other parts of my application then
this forces the PopupPanel to be in the left-over code. There is nothing I can
do about this because the DateBox implicitly uses the PopupPanel and I cannot
change this, without writing my own DateBox. It also using things like the
CustomButton control which also appears in the left-over segment.
However at the end of the day it is all about balancing
trade-offs of effort versus payback. For sure, my application,
the way it is right now, loads very fast on a good broadband connection.
Therefore I do not spend much time worrying about saving any more bytes in
either section right now.