When you add any Wicket component library e.g. Wicket-Bootstrap to your project, usually it adds some JavaScript and CSS resources to every page or panel. But the problem is that these JS files are added in the <head> section of your page while all Internet knowledge says that JS should be placed at the bottom of the page for a better performance and faster loading times. In this post I will show how we could configure Wicket to place JS files wherever we want.
Initial project setup
Let’s start with a simple one-page-one-form Wicket application counting days from user’s birthday:
It has a dependency to mentioned earlier Wicket-Bootstrap because we need its DatePicker component:
1 2 3 4 5 6 7 8 9 10 |
<dependency> <groupId>de.agilecoders.wicket</groupId> <artifactId>wicket-bootstrap-core</artifactId> <version>0.10.11</version> </dependency> <dependency> <groupId>de.agilecoders.wicket</groupId> <artifactId>wicket-bootstrap-extensions</artifactId> <version>0.10.6</version> </dependency> |
Our web page contains a form, one text field and one date picker:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class HomePage extends WebPage { private String name; private Date birthDate; public HomePage(final PageParameters parameters) { super(parameters); add(new FeedbackPanel("feedbackPanel")); Form<HomePage> form = new Form<HomePage>("form", new CompoundPropertyModel<>(this)) { @Override protected void onSubmit() { info(format("Hello %s. It's nice to meet you! You are %d days old!", name, calculateDaysSinceBirthDay(birthDate))); } }; form.add(new TextField("name").setRequired(true).setLabel(Model.of("Name"))); form.add(new DateTextField("birthDate", new DateTextFieldConfig() .autoClose(true) .withLanguage("pl") .withFormat("dd/MM/yyyy")) .setLabel(Model.of("Date of birth")) .setRequired(true) ); add(form); } private int calculateDaysSinceBirthDay(Date birthDate) { return Days.daysBetween(LocalDate.fromDateFields(birthDate), LocalDate.now()).getDays(); } } |
Problem
When we run our application we will see that all Bootstrap related JS files are placed in the page header:
1 2 3 4 5 6 7 |
<head> <!-- ... --> <script type="text/javascript" src="./wicket/resource/de.agilecoders.wicket.extensions.markup.html.bootstrap.references.BootstrapDatepickerJsReference/js/datepicker-ver-1484131947646.js"></script> <script type="text/javascript" src="./wicket/resource/de.agilecoders.wicket.extensions.markup.html.bootstrap.references.BootstrapDatepickerLangJsReference/js/lang/bootstrap-datepicker.pl-ver-1484131947646.js"></script> <script type="text/javascript" id="bootstrap-js" src="./wicket/resource/de.agilecoders.wicket.webjars.request.resource.WebjarsJavaScriptResourceReference/webjars/bootstrap/3.3.7-1/js/bootstrap-ver-1484131946909.js"></script> <!-- ... --> </head> |
It is not what we wanted, so we need to configure our application to add JS files somewhere at the bottom.
Solution
First, let’s start with some background. Each time when a component wants to add something to page header (for example using IHeaderContributor) it creates a new instance of HeaderItem object. By default all HeaderItems are rendered in <head>, but we could modify this behaviour using IHeaderResponseDecorator and FilteringHeaderResponse to separate HeaderItems into two places to render: header and defined place in our page body.
First, we should define a place where all JS files should be rendered by adding a <wicket:container> element with id:
1 2 3 4 5 6 7 8 |
<!DOCTYPE html> <html xmlns:wicket="http://wicket.apache.org"> <body> <!-- ... --> <wicket:container wicket:id="footer-container"/> </body> </html> |
and add it to the Page class:
1 |
add(new HeaderResponseContainer("footer-container", "footer-container")); |
Next step is to configure a response decorator and response filter that will direct some items to our footer-container bucket. This should be done in init() method of our WicketApplication class:
1 2 3 4 5 6 7 8 9 10 |
@Override public void init() { super.init(); IBootstrapSettings settings = new BootstrapSettings(); Bootstrap.install(this, settings); setHeaderResponseDecorator(new JavaScriptToBucketResponseDecorator("footer-container")); } |
Our decorator should use a filter that is already in Wicket core module, a JavaScriptFilteredIntoFooterHeaderResponse that does exactly what we need:
A header response that creates two buckets. The header bucket will contain all references to CSS and markup from the <head> section from the page. The other bucket will contain all other header items, and you will need to add a HeaderResponseContainer to the footer of your page (typically just before the end body tag) to render those items.
So it will split all our HeaderItems into two buckets, one (default) that will be rendered in page header and second rendered where we placed footer-container element. Working version of our decorator looks as presented below:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class JavaScriptToBucketResponseDecorator implements IHeaderResponseDecorator { private String bucketName; public JavaScriptToBucketResponseDecorator(String bucketName) { this.bucketName = bucketName; } @Override public IHeaderResponse decorate(IHeaderResponse response) { return new JavaScriptFilteredIntoFooterHeaderResponse(response, bucketName); } } |
After that we can rebuild our application and see that all our Bootstrap related JS files are rendered at the bottom of our page, improving performance and overall user experience of our users.
Thanks Tomasz!
In case any Wicket 8 user is reading your blog entry, the usage has changed slightly:
return new ResourceAggregator(new JavaScriptFilteredIntoFooterHeaderResponse(response, bucketName));
Or – if you’re more adventurous – try out the new JavaScriptDeferHeaderResponse which defers all JavaScript while keeping it in the page’s header.