Every web developer has experienced this before: the client wants to show "almost everything" on the same page, but is very unhappy when he opens the site and has to look at a blank page for more than a couple of seconds.
Nowadays, developers have the means to optimize a lot (and fix everything that broke in the process), but we still cannot cast a magic “load superfast” spell and get rid of all loading time. Can we do more?
Originally posted at Webdev community
Every web developer has experienced this before: the client wants to show almost EVERYTHING1 on the same page, but is very unhappy when he opens the site and has to look at a blank page for more than a couple of seconds.
Nowadays, developers have the means to optimize a lot (and fix everything that broke in the process afterwards), but we still cannot cast a magic “load superfast” spell and get rid of all loading time. Can we do more?
This page takes forever to load, because we need to do very heavy work on the backend-side. Until that work has been finished, the page is not rendered, not even partially!
If we would build the application with pure JavaScript, we could initially load our template only, and then use Ajax to load the heavy components when the page is already visible.
The application is not faster, but seems faster, because
But too bad: we do not use pure JavaScript; we use JSF and Primefaces. We need to execute all our Java methods before the page can be loaded, due to the JSF lifecycle. Or do we?
There is a way! Somewhere hidden in the depts of the Primefaces Showcase, you find “Remote Command”. This is a great but relatively unknown feature of Primefaces, which allows us to execute backing bean methods by calling them with JavaScript. Yes, you read that right: we can call Java methods with JavaScript. And it is really easy.
How do we start? We do not want to render the heavy parts while opening the page. So, let us start with that.
First, we add a property shouldRender
to our backing bean, which evaluates to false initially.
Do not forget to add a getter and setter!
Note that this bean should be viewscoped (or have a longer life), because we will update this property with Ajax later on.
@ManagedBean
@ViewScoped
public class ExampleWithRemoteCommandController {
private boolean shouldRender = false;
public boolean isShouldRender() {
return shouldRender;
}
public void setShouldRender(boolean shouldRender) {
this.shouldRender = shouldRender;
}
public String getHeavyLifting1() throws InterruptedException {
Thread.sleep(2000);
return "Pjoew, I need to work 2 seconds to calculate this part!";
}
public String getHeavyLifting2() throws InterruptedException {
Thread.sleep(5000);
return "Pjoew, I need to work 5 seconds to calculate this part!";
}
}
In the xhtml-page, we add an attribute rendered
to the tag surrounding the heavy component. The value is a reference to our shouldRender
property. This means that the part will not be rendered at page load time, because shouldRender
evaluates to false
.
<h:outputText rendered="#{exampleWithRemoteCommandController.shouldRender}"
value="#{exampleWithRemoteCommandController.heavyLifting1}" />
When we try this out, the page loads a lot faster than before. Ofcourse, we ripped out all the heavy lifting! But let’s make a deal: the initial page load time should not increase from now on, ok?
When the page is fully loaded, we want to change our rendered attribute to true
. But the rendered attribute is part of JSF, not of HTML. So, when the page is loaded, we are already past the JSF-rendering phase.
We cannot use Java either. We can only use Javascript, so… we should call Java methods with JavaScript? JavaScript can only, naturally, call other JavaScript methods. How could we possibly call a Java method in JavaScript?
With the tag p:remoteCommand, you can expose serverside logic to a JavaScript function. You define inside the <p:remoteCommand>
tag which Java code you want to execute, and Primefaces will generate a JavaScript method and the necessary Java web services which can communicate with each other!
Notice the following:
name="xyz"
, you can call the Java code inside the tag with JavaScript-function xyz()
.shouldRender
attribute to true
, so that when we update the page part, the heavy module is rendered.rendered
attribute in a new component. This is the component we will update. It is not possible to update the component with the rendered
attribute itself: at the time we try to update the component (=after the page loaded), it is not rendered (shouldRender
is false
)! You cannot update something that is not rendered!<p:remoteCommand name="doHeavyLifting1"
update="heavyLifting1LazyLoadingContainer"
onstart="showLoadingSpinner('heavyLifting1LazyLoadingContainer')"
oncomplete="hideLoadingSpinner('heavyLifting1LazyLoadingContainer')">
<f:setPropertyActionListener target="#{exampleWithRemoteCommandController.shouldRender}"
value="#{true}" />
</p:remoteCommand>
<h:panelGroup id="heavyLifting1LazyLoadingContainer">
<h:outputText rendered="#{exampleWithRemoteCommandController.shouldRender}"
value="#{exampleWithRemoteCommandController.heavyLifting1}" />
</h:panelGroup>
We can execute the code in the p:remoteCommand
tag by calling the name of the remote command as a function. This can be done anytime we like: on document ready, on clicking on an element, when a fancy introduction movie has been played, when you have reached the bottom of the page when you scoll down in a list2,…
$('document').ready(function() {
doHeavyLifting1();
doHeavyLifting2();
});
var showLoadingSpinner = function(id) {
$('#' + id).addClass('loading');
};
var hideLoadingSpinner = function(id) {
$(id).removeClass('loading');
};
When you want to do your ajax calls on the document ready event (like in this example), you don’t need to code this in JavaScript. You can add attribute autoRun="true"
to the remote command tag. Primefaces will generate the JavaScript for you.
<p:remoteCommand name="doHeavyLifting1"
update="heavyLifting1LazyLoadingContainer"
onstart="showLoadingSpinner('heavyLifting1LazyLoadingContainer')"
oncomplete="hideLoadingSpinner('heavyLifting1LazyLoadingContainer')"
autoRun="true">
I paste the url in the address bar and press enter.
I have encountered only one big limit using the Remote Command component: JSF does not handle parallel Ajax requests well. The serial order model is part of the JSF specification.3. As you see in the example gif, the left part needs to be loaded before the request for the right part has even been made. The silver lining: Primefaces handles this automatically.
<p:remoteCommand>
tag should be included in the form. If you put it outside a form, it is not possible to process or anything inside that form.<p:remoteCommand>
tag in the container that you will update. Doing that could cause an infinite loop.rendered
attribute in a new component. This is the component we will update. It is not possible to update the component with the rendered
attribute itself: at the time we try to update the component (=after the page loaded), it is not rendered (shouldRender
is false
)! You cannot update something that is not rendered!@PostConstruct
annotation. This delays the page load!If you want to block some parts of your page until a component has loaded, you can use Primefaces BlockUI.
You can pass parameters to your Java methods. This feature is out of scope for this article. Find more about this feature at the Primefaces documentation.
The way you do this has changed since PrimeFaces 3.3!
1 I like to exaggerate, especially when it comes to frustrations.
2 Primefaces 5.0 introduces a new component for this: DataScroller.
3 As described in this thread.