5

Consider a simple h:outputText component:

<h:outputText value="#{myBean.myValue}"/>

How can I lazy load that value after the page has been rendered, and display custom 'ajax loading' icon instead of the value while this is being done?

I am using PrimeFaces 3.5 in my project so any PF-specific implementation will be welcome.

rootkit
  • 2,165
  • 2
  • 29
  • 43

3 Answers3

6

A suggest to do this by calling remoteCommand after on page load (it is done by setting autoRun attribute to true) and update your outputText.

private String myValue;

// getter and setter

public void initMyValue() {
  // init myValue
}

On page you should have ajaxStatus component for viewing loading image, and your outputText. Also there should be p:remoteCommand component:

<p:ajaxStatus style="width:16px;height:16px;" id="ajaxStatusPanel">  
  <f:facet name="start">  
    <h:graphicImage value="ajaxloading.gif" />  
  </f:facet>
  <f:facet name="complete">  
    <h:outputText value="" />
  </f:facet>  
</p:ajaxStatus>

<h:outputText id="myText" value="#{myBean.myValue}"/>

<p:remoteCommand autoRun="true" actionListener="#{myBean.initMyValue}" update="myText"/>

EDIT: I supposed that you want to lazy load value of outputText because it contains some long running calculations, but if you want to completely deffer rendering of outputText first add boolean property in your backing bean, and set this property to true at the end of initMyValue method:

private boolean loaded;

// getter and setter

public void initMyValue() {
  // init myValue
  loaded = true;
}

on the page reorganize it as follows:

<h:panelGroup id="myPanel" layout="block">
  <h:graphicImage value="ajaxloading.gif" rendered="#{!myBean.loaded}"/>
  <h:outputText value="#{myBean.myValue}" rendered="#{myBean.loaded}"/>
</h:panelGroup>

<p:remoteCommand autoRun="true" actionListener="#{myBean.initMyValue}" update="myPanel"/>
partlov
  • 13,789
  • 6
  • 63
  • 82
  • Looks like it would work, but also mean that myText will be eagerly loaded during page rendering. How can I avoid that? I realize I can do it in the backing bean, but it would be nice just lazy load it. Also, the ajaxStatus will be global in this case - I wanted just a loading status displayed in place of the value specifically for this component. Thanks! – rootkit Mar 13 '13 at 16:28
  • I edited an answer. I added part which is maybe better for your needs. – partlov Mar 14 '13 at 07:48
  • Yes, this works after a bit of tweaking. Still, all of the above is way too much hassle for such a simple task. Isnt there a more elegant solution? I'd write a component for doing lazy rendering... – rootkit Mar 14 '13 at 16:27
  • Indeed, it is not hard to create composite component which will lazy load its children, with the logic above. – partlov Mar 14 '13 at 17:50
  • Accepting your answer. I will also provide my own answer with full implementation details. Thanks! – rootkit Mar 14 '13 at 19:04
1

You can use a BlockUI to conditionally block the component while it loads.

  1. Define a preRenderComponent event on the <h:outputText/>

       <h:outputText id="myText">
          <f:event name="preRenderComponent" id="started"/>
       </h:outputText>
    
  2. Define a <p:blockUI/> with the id of the event as the trigger

       <p:blockUI block="myText" trigger="started" />
    

You can customize the blockui to display an image or whatever.

A word of caution: I presume you require this because you're doing some heavy lifting in the getter of that component. Know that the getter will be called several times in the lifecycle of that page. So hiding the fact that the operation is taking a long time will not change the fact. A better design would be to preload and cache the value for that component in a durable scope, rather than the theatrics of a "loading" throbber.

kolossus
  • 20,559
  • 3
  • 52
  • 104
  • Tried that, but preRenderComponent event listener is called before my page is displayed to the user. This is a modal dynamic PF dialog - wonder if that has something to do with it? Also, I fully understand what you are saying about caching the value in durable scope - in this case I have API call to resolve IP address to location, and since number of API calls is limited I rather not do it unless user actually looks at the data. – rootkit Mar 14 '13 at 14:52
  • @rootkit, now with better context, I can suggest a more appropriate solution. Set `dynamic="true"` on the dialog to stop the content of the dialog from being loaded on page load. Additionally, the `rendered` attribute of the outputText can be made conditional, to ensure that the element is put in the DOM only when needed – kolossus Mar 14 '13 at 15:11
  • The dialog already is dynamic, but I display it multiple times. I want the location to be looked up up only when user opens the dialog, and since lookup takes good several seconds I dont want to delay opening the dialog... – rootkit Mar 14 '13 at 16:14
  • @rootkit then set the `rendered` attribute of the ouptutText to a condition that will be toggled on button click. If your bean is `@RequestScoped`, you can then be sure that the render condition will always be reset to `false` or whatever default, immediately after the dialog is fully rendered. Post an ssce of your view and I can give code sample – kolossus Mar 14 '13 at 18:03
  • Why button click? I dont want user to click anything for value to load. Instead p:remoteCommand with autoStart=true can call a method that toggles the 'rendered' attribute. This is pretty much what @partlov suggested. – rootkit Mar 14 '13 at 19:03
1

This is how I ended up implementing it:

<h:panelGroup id="loginLocation">
<p:graphicImage library="assets" name="small-kit-loader.gif" width="16" height="16" rendered="#{empty mybean.lastLoginLocation}"></p:graphicImage> 
<h:outputText value="#{myBean.lastLoginLocation}" rendered="#{!empty myBean.lastLoginLocation}"/>
</h:panelGroup>
<p:remoteCommand global="false" actionListener="#{actionBean.getUserLoginLocation(myBean.selectedUser)}" name="refreshLoginLocation" id="rc1" autoRun="true" update="loginLocation" process="@this"></p:remoteCommand>

Personally I am not entirely happy with this implementation:

  1. lazy loading state is stored server-side, not client-side where it should be
  2. I have to implement separate method on my backing bean (getUserLoginLocation) to retrieve the value, and explicitly store it in another property (lastLoginLocation). It would have been much cleaner just to have a single getter that is lazy-called after rendering the page in browser
  3. Not easily reusable - depends on backing bean 'loaded' flag (#{empty myBean.lastLoginLocation} in this case), and requires action listener to actually set the value. Any composite component based on this approach would also depend on specific code in backing bean.

Any recommendations on how to improve this code are welcome! :)

rootkit
  • 2,165
  • 2
  • 29
  • 43