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.
Going through the code, let's examine the relevance of each portion, one component and tag at a time.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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!