TabSet Caching TabPanes

Table of Contents

TabSet Caching TabPanes

  Name Size Creator (Last Modifier) Creation Date Last Mod Date Comment  
File tabSet-caching-tutorial.zip 90 kB Mark Collette Apr 24, 2012 Apr 24, 2012  

The ACE TabSet component uses TabPane child components to represent each tab. When the TabSet is in client-side mode, every TabPane's contents are cached in the browser, and no server submitting is required for changing tabs. If some component does do a submit, then the TabSet, every TabPane, and all of the contents will be rendered. With ICEfaces Direct-To-DOM technology, rendered updates are automatically pruned down so that only actual differences need be transmitted to the browser. Therefore, as long as nothing changes, or the changes are structured to minimise cascading updates, then content can remain unaffected in the browser. This is important for iframes, and Javascript objects that are bound to html markup in the browser DOM. There is a performance cost to rendering and DOM differencing a complete TabSet with all of its TabPanes' contents, particularly if they contain large dataTables, or otherwise access large database data sets.

With a server-side TabSet, by default it will only render the currently active TabPane, and so incur less rendering, DOM differencing and updating overhead. The problem with this is that as the user changes tabs, and the active TabPane changes, the previously active TabPane is removed from the browser, and so will lose any state that was not explicitly saved before the server submit happened. It can be onerously difficult to save iframe and Javascript object state, especially in a cross-browser manner. What is much simpler is for there to exist a caching mechanism whereby as tabs are changed, the contents remain in the browser, and only differences are transmitted, while unchanged contents remain intact.

TabPane now incorporates several different caching options, which balance trade-offs of: automatically and granularly providing updates, while leaving unchanged content intact, with the need to optmise the limitation of rendering and updating of expensive and/or static content. To facilitate optimising updates, and reducing update cascading to unwanted sections of the DOM, certain steps should be taken.


Best Practices for the Facelet Page


Each portion of code given below is relevant and accomplishes a necessary part of a whole, which allows for optimal updates.

main.xhtml
<h:form id="exampleForm">
    <h:panelGroup id="tabSetPanel">
        <ace:tabSet id="tbset"
                    clientSide="false">
            <ace:tabPane label="#{msgs['content.tab.dynamic.label']}"
                         id="tab_dynamic"
                         cache="dynamic">
                <f:subview id="sub_dynamic">
                    <h:panelGroup id="pnl_dynamic">
                        <iframe id="iframe_dynamicA"
                                src="./changing-content/dynamicA/#{caching.alwaysIncrement}"/>
                            <h:commandButton value="#{msgs['content.button.submit']}"/>
                            <h:commandButton value="#{msgs['content.button.update']}"
                                             actionListener="#{caching.increment}"/>
                        <iframe id="iframe_dynamicB"
                                src="./changing-content/dynamicB/#{caching.conditionallyIncrement}"/>
                    </h:panelGroup>
                </f:subview>
            </ace:tabPane>
        </ace:tabSet>
    </h:panelGroup>
</h:form>

Going through the code, let's examine the relevance of each portion, one component and tag at a time.

<h:form id="exampleForm">

ace:tabSet does not need to be wrapped in a form, as it can instead use an ace:tabSetProxy with a form elsewhere in the Facelet page, and then be able to have form components within it, for each individual tab. In this tutorial we just happen to use the simplest method, of wrapping the ace:tabSet in an h:form, but it's not necessary for cached tabs.

<h:panelGroup id="tabSetPanel">

Again, wrapping the ace:tabSet in an h:panelGroup is not necessary for cached tabs, but what this illustrates, which does generally help optimise DOM updates, is that it's best to avoid placing content directly within a form, and instead place it within some container component that has an id. That way when components use the rendered property, and switch between being rendered and not, or the component tree has components dynamically added or removed, the DOM update is limited to the h:panelGroup, and does not cascade up to the form.

<ace:tabSet id="tbset"
            clientSide="false">

Giving the tabSet an explicit id, instead of a generated id is usually a good idea. It makes things more clear and obvious when debugging.

<ace:tabPane label="#{msgs['content.tab.dynamic.label']}"
             id="tab_dynamic"
             cache="dynamic">
    <f:subview id="sub_dynamic">

Both the ace:tabPane and the f:subview should have an explicit id. The ace:tabPane's cache property defaults to none, and can be: none, dynamic, static, staticAuto, revertDynamicStaticAuto.

f:subview acts as a NamingContainer, so that the clientIds of everything within it will be different from tab to tab, which is necessary because neither tabPane nor tabSet are NamingContainers themselves. If the tabs used the same content, say from using ui:include on the same included facelet file, there could be duplicate ids, without f:subview.

<h:panelGroup id="pnlGrp">

Just like how a container with an id was recommended to contain updates from cascading up to the form, it's best to put one right inside the f:subview, since the f:subview acts as a NamingContainer in a logical sense, but doesn't actually render anything itself, so this h:panelGroup will be the point at which the unique clientId from the f:subview actually gets rendered into the DOM.

<iframe id="iframe_dynamicA"
        src="./changing-content/dynamicA/#{caching.alwaysIncrement}"/>
    ...

At this point, no further special considerations need be taken, for caching to function. The general advice of using explicit ids to limit updates still applies, especially with iframes and any other components whose updates are not desired to affect the iframes.

This tutorial uses a special Servlet for the iframes, to easily depict content being updated or not. Essentially, each iframe uses a cookie to remember what its current content is, and the alwaysIncrement and conditionallyIncrement parts of the URI control whether the content would change in the absence of caching. Caching then affects whether the TabPane and its iframe child gets rendered, and so has a chance to update or not.


Java Tutorial Source


There is no special support required for cached tabs, in the Java code, for most of the cache settings, the exception being revertDynamicStaticAuto, which needs some code to manage the switching between staticAuto and revertDynamicStaticAuto. This will explored in that portion of the tutorial.


Cache Examples


Cache: none

No caching occurs in the browser and the tab contents are completely rendered and updated in the browser when the tab is active, and become un-rendered when the tab is no longer active.

<iframe id="iframe_none"
        src="./changing-content/none/#{caching.alwaysIncrement}"/>
<h:commandButton value="#{msgs['content.button.update']}"/>
public String getAlwaysIncrement() {
    return getNewValue();
}
...
protected String getNewValue() {
    return Long.toHexString(System.currentTimeMillis() + counter);
}

Every button press inside or outside this TabPane, including the Update button inside it, as well as switches between tabs, will cause a server submit. When the TabSet is rendered, since the TabPane has cache="none", it will always render its contents, including the iframe, which is set to increment its contents every time it is rendered. So, every interaction will cause the iframe contents to increment and update.

Cache: dynamic

The tab contents are lazily loaded when the tab first becomes active, and then remain in the browser. On subsequent lifecycles, the previously lazily loaded tab contents will continue to be rendered whether the tab is still active or not, and any changes will be detected and granularly updated. Essentially, what can be cached will be cached, and what has changed will be updated, all automatically.

<iframe id="iframe_dynamicA"
        src="./changing-content/dynamicA/#{caching.alwaysIncrement}"/>
<h:commandButton value="#{msgs['content.button.submit']}"/>
<h:commandButton value="#{msgs['content.button.update']}"
                 actionListener="#{caching.increment}"/>
<iframe id="iframe_dynamicB"
        src="./changing-content/dynamicB/#{caching.conditionallyIncrement}"/>
public String getAlwaysIncrement() {
    return getNewValue();
}
public String getConditionallyIncrement() {
    // Lazy init
    if (conditionallyIncrement == null) {
        conditionallyIncrement = getNewValue();
    }
    return conditionallyIncrement;
}
public void increment(ActionEvent event) {
    conditionallyIncrement = getNewValue();
}
protected String getNewValue() {
    return Long.toHexString(System.currentTimeMillis() + counter);
}

The dynamicA iframe is set to always increment its contents, and dynamicB iframe is set to maintain the same contents unless specifically the Update button is pressed. Pressing the Submit button will cause the dynamicA iframe to update and not the dynamicB iframe, illustrating that updates are granular.

Cache: static

The tab contents are lazily loaded when the tab first becomes active, and then remain in the browser. The tab will not be rendered or updated after the first time. This is an optimisation to reduce rendering resource usage. This is intended for displaying large unchanging data sets that the user views but does not interact with.

<iframe id="iframe_static"
        src="./changing-content/static/#{caching.alwaysIncrement}"/>
<h:commandButton value="#{msgs['content.button.tryUpdateStaticCached']}"/>

The iframe is set to always increments its contents, but will never update, since the static caching will keep it from updating. Pressing the Try Update but Static Cached button would have updated this type of iframe, with other cache settings, but not with static caching.

Cache: staticAuto

Behaves like static caching, until the moment when the user interacts with an input or command component within the TabPane, that causes a submit to the server, at which point, for that one lifecycle, the contents of the TabPane will be rendered and updated. The updates may be granular or comprehensive, depending on previous user interactions, as this caching strategy favours eliminating rendering and updating when possible, which can later cause larger updates. For TabPanes with user interactions that affect the server, which are more than sporadic, then dynamic caching is likely more appropriate.

<iframe id="iframe_staticAuto"
        src="./changing-content/staticAuto/#{caching.alwaysIncrement}"/>
<h:inputText value="#{caching.staticAutoInput}">
    <f:ajax execute="@this" render="@all"/>
</h:inputText>
<h:commandButton value="#{msgs['content.button.interact']}"/>

The iframe is set to always increments its contents, but will not update from changing between tabs, nor from interactions occurring outside of this TabPane, but changing the input field value or pressing the Interact button will update the content automatically.

Cache: dynamicRevertStaticAuto

Works in conjunction with staticAuto caching. The idea being that just like how staticAuto knows to render and update the TabPane when an input or command component within the TabPane causes a server submit, there may be components outside the TabPane that might perform some operation which should affect the TabPane contents, and only in that lifecycle a render and update of the TabPane should occur. This is accomplished by using a ValueExpression for the cache property on the TabPane, bound to a settable bean property, which is initialised to staticAuto. In the action, actionListener, valueChangeListener, or whichever application code that executes before the render phase, the application would set the bean property to dynamicRevertStaticAuto, which will then force the behaviour of rendering and updating the TabPane for the one lifecycle. Then the TabPane cache property will automatically revert to being staticAuto going forward, which is why the bean property needs to have a setter method.

<h:panelGroup ...>
    <ace:tabSet ...>
        <ace:tabPane ...>
            ...
            <iframe id="iframe_dynamicRevertStaticAuto"
                    src="./changing-content/dynamicRevertStaticAuto/#{caching.alwaysIncrement}"/>
            <h:inputText value="#{caching.dynamicRevertStaticAutoInput}">
                <f:ajax execute="@this" render="@all"/>
            </h:inputText>
            <h:commandButton value="#{msgs['content.button.interact']}"/>
            ...
        </ace:tabPane>
    </ace:tabSet>
</h:panelGroup>
<h:commandButton value="#{msgs['content.button.submit']}"/>
<h:commandButton value="#{msgs['content.button.dynamicRevertStaticAuto']}"
                 actionListener="#{caching.dynamicRevertStaticAutoActionListener}"/>
private String cache = "staticAuto";
public String getCache() {
    return cache;
}
public void setCache(String cache) {
    this.cache = cache;
}
public void dynamicRevertStaticAutoActionListener(ActionEvent event) {
    cache = TabPaneCache.DYNAMIC_REVERT_STATIC_AUTO.getNamed();
}

This will behave exactly like staticAuto, since it is actually set to staticAuto. The Submit button below the TabSet will not effect the contents, as it would not for staticAuto, but the Dynamic Revert Static Auto button below will update the contents, as it has an actionListener that switches the cache property to dynamicRevertStaticAuto.

That concludes this overview of using cached tabs with ACE TabSet. If you're interested in further details of how this example works, take a look at the complete source code below; or sign up for ICEfaces training for a complete guide to this example and every feature of ICEfaces!

  Name Size Creator (Last Modifier) Creation Date Last Mod Date Comment  
File tabSet-caching-tutorial.zip 90 kB Mark Collette Apr 24, 2012 Apr 24, 2012  

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

© Copyright 2018 ICEsoft Technologies Canada Corp.