Search code examples
jsfprimefacesviewparams

Binding f:viewParam on component binding


I want to create <f:viewParam> which sets active tab of <p:tabView>. In my page I define a <f:viewParam>:

<f:viewParam name="tab" value="#{tabView.activeIndex}" />

and <p:tabView> with binding:

<p:tabView binding="#{tabView}">
    <p:tab title="tab1" />
    <p:tab title="tab2" />
    <p:tab title="tab3" />
</p:tabView>

But it's not working. If I open my page with parameter ?tab=1 I see this exception:

Caused by: javax.el.PropertyNotFoundException: Target Unreachable, identifier 'tabView' resolved to null

Why is this happening?

P.S.: in addition to @BalusC answer, you can use binding component property on the viewMap like this:

<f:viewParam name="tab" value="#{viewScope['activeTabIndex']}" />

<p:tabView activeIndex="#{viewScope['activeTabIndex']}">
    <p:ajax event="tabChange" />
    <p:tab title="tab1" />
    <p:tab title="tab2" />
    <p:tab title="tab3" />
</p:tabView>

in this case you must provoke ajax-request to put value in the viewMap through <p:ajax event="tabChange" /> or dynamic="true" cache="false"


Solution

  • That's because the view is not yet built at that moment and thus all binding attributes are not yet evaluated. This is indeed somewhat unexpected, but on a GET request the view is by default only built during render response phase. It works fine when the request is a postback as the view is by definition built during restore view phase already.

    I can think of two workarounds:

    1. Move the <f:viewParam value> into <f:event type="preRenderView" listener>. On a GET request, the view is at that point guaranteed built.

      <f:metadata>
          <f:viewParam name="tab" />
          <f:event type="preRenderView" listener="#{tabView.setActiveIndex(tab)}"/>
      </f:metadata>
      

      This works, because <f:viewParam> without a value will by default put the converted and validated request parameter value as a request attribute under the very same name #{tab}. You only need to make sure that you don't have request scoped managed beans on the same name, or it may clash.

    2. If you don't need conversion and validation, then just pass #{param.tab} directly.

      <f:metadata>
          <f:event type="preRenderView" listener="#{tabView.setActiveIndex(param.tab)}"/>
      </f:metadata>
      

    Nice approach, by the way.

    See also: