TabSet Caching TabPanes

You are viewing an old version (v. 10) of this page.
The latest version is v. 12, last edited on Apr 25, 2012 (view differences | )
<< View previous version | view page history | view next version >>

TabSet Caching TabPanes

There are currently no attachments on this page.

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.

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!

There are currently no attachments on this page.

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

© Copyright 2017 ICEsoft Technologies Canada Corp.