JSF f:event preRenderView in template f:metadata, listener not being called on every page access

Webel IT Australia - upvoter picture Webel IT Australia - upvoter · Jun 17, 2012 · Viewed 12.9k times · Source

Mojarra-2.1.3 as per Glassfish3.1.1 (distributed with Netbeans7.1)

I have a @SessionScoped backing bean Tracker with a listener void reset().

The following works fine in the f:metadata of all XHTML pages that use a template.xhtml, for example /block/view.xhtml, which also takes a query parameter id:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{blockManager.id}"/>
        <f:event type="preRenderView" listener="#{tracker.reset}"/>            
    </f:metadata>
</f:view>

<ui:composition template="/template.xhtml">

As expected, whenever I load (GET) or reload a page, and no matter what the id query parameter, the #{tracker.reset} listener is invoked (as revealed by debug logging).

However, it is tedious to have to include that f:event in every XHTML page (of which I have hundreds), I tried first instead to have it in the f:metadata of my template.xhtml. But when I did something strange happened. It only called #{tracker.reset} once, the first time /block/view.xhtml was loaded (no matter what the id query parameter was), and thereafter it was not called again until I loaded another page with a different viewId, such as /actor/view.xhtml, or /block/list.xhtml, or /index.html.

I examined the viewId using #{facesContext.viewRoot.viewId} in the template.xhtml. It is clear from the point of view of the viewId that the query parameter id plays no role in distinguishing between different block/view.xhtml?id=[id] pages called with different id query parameters, the viewId is always just '/block/view.xhtml'.

During the writing of this stackoverflow posting I discovered the solution to my problem: just place the f:event outside the f:metadata of the template.xhtml (I was using the f:metadata in the template.xhtml for grouping of f:events). This works in template.xhtml:

<f:metadata>
..
</f:metadata>
<f:event type="preRenderView" listener="#{tracker.reset}"/>
<h:head>

But I still have the following question:

Q: Why does it make a difference whether or not I place an f:event inside the f:metadata in a template ?

The reason I ask is that there are lots of examples here on Stackoverflow both of use of an f:metadata in a template.xhtml, and of an f:event inside an f:metadata in a template.

BalusC states at When to use f:viewAction / preRenderView versus PostConstruct?:

The preRenderView event is invoked on every HTTP request.

This only seems to be true (to work as expected) if I have the preRenderView f:event in the f:metadata of the final XHTML page, or outside the f:metadata of a template, but not inside the f:metadata of a template.

There seems to be some debate about whether one should have f:event in the f:metadata of a template, or use f:metadata in a template at all.

JSF2 Complete Reference (Burns and Schalk) p.540 states:

The f:metadata tag encapsulates the set of elements used to specify the metadata for a Facelets view, and therefore must be the child of the f:view tag and may not appear in a template. As of JSF2.0, the only purpose of this tag is to encapsulate f:viewParam tags.

But there are lots of examples on Stackoverflow of f:metadata being used inside templates, and there are lots of examples of f:event being used inside f:metadata. It is also discussed here:

Does it matter whether place f:event inside f:metadata or not?

Where BalusC helpfully explained:

.. the <f:event> is not strictly required to be placed inside <f:metadata>. It can be attached to any component. .. It's indeed for pure self-documentary purposes placed inside the <f:metadata> whenever you have a bunch of <f:viewParam>s and would like to hook a <f:event> to invoke an action after all those view parameters have been set. ..

But my experience above indicates that placing and f:event inside an f:metadata of a template gives slightly different (strange) behaviour. Why ?

Answer

BalusC picture BalusC · Jun 17, 2012

The <f:metadata> itself can only be used in the template client, not in the master template. I.e. it has to be declared in the page which you're actually requesting by URL, not inside the page behind <ui:composition template>, it will otherwise just be ignored. This particular restriction is unrelated to the <f:event>.

See also: