Search code examples
jsfjsf-2viewparamsviewaction

What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?


Can anyone clarify how we can use in general, or a in real world example, this snippet?

<f:metadata>
    <f:viewParam id="id" value="#{bean.id}" />
    <f:viewAction action="#{bean.init}" />
</f:metadata>

Solution

  • Process GET parameters

    The <f:viewParam> manages the setting, conversion and validation of GET parameters. It's like the <h:inputText>, but then for GET parameters.

    The following example

    <f:metadata>
        <f:viewParam name="id" value="#{bean.id}" />
    </f:metadata>
    

    does basically the following:

    • Get the request parameter value by name id.
    • Convert and validate it if necessary (you can use required, validator and converter attributes and nest a <f:converter> and <f:validator> in it like as with <h:inputText>)
    • If conversion and validation succeeds, then set it as a bean property represented by #{bean.id} value, or if the value attribute is absent, then set it as request attribtue on name id so that it's available by #{id} in the view.

    So when you open the page as foo.xhtml?id=10 then the parameter value 10 get set in the bean this way, right before the view is rendered.

    As to validation, the following example sets the param to required="true" and allows only values between 10 and 20. Any validation failure will result in a message being displayed.

    <f:metadata>
        <f:viewParam id="id" name="id" value="#{bean.id}" required="true">
            <f:validateLongRange minimum="10" maximum="20" />
        </f:viewParam>
    </f:metadata>
    <h:message for="id" />
    

    Performing business action on GET parameters

    You can use the <f:viewAction> for this.

    <f:metadata>
        <f:viewParam id="id" name="id" value="#{bean.id}" required="true">
            <f:validateLongRange minimum="10" maximum="20" />
        </f:viewParam>
        <f:viewAction action="#{bean.onload}" />
    </f:metadata>
    <h:message for="id" />
    

    with

    public void onload() {
        // ...
    }
    

    The <f:viewAction> is however new since JSF 2.2 (the <f:viewParam> already exists since JSF 2.0). If you can't upgrade, then your best bet is using <f:event> instead.

    <f:event type="preRenderView" listener="#{bean.onload}" />
    

    This is however invoked on every request. You need to explicitly check if the request isn't a postback:

    public void onload() {
        if (!FacesContext.getCurrentInstance().isPostback()) {
            // ...
        }
    }
    

    When you would like to skip "Conversion/Validation failed" cases as well, then do as follows:

    public void onload() {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (!facesContext.isPostback() && !facesContext.isValidationFailed()) {
            // ...
        }
    }
    

    Using <f:event> this way is in essence a workaround/hack, that's exactly why the <f:viewAction> was introduced in JSF 2.2.


    Pass view parameters to next view

    You can "pass-through" the view parameters in navigation links by setting includeViewParams attribute to true or by adding includeViewParams=true request parameter.

    <h:link outcome="next" includeViewParams="true">
    <!-- Or -->
    <h:link outcome="next?includeViewParams=true">
    

    which generates with the above <f:metadata> example basically the following link

    <a href="next.xhtml?id=10">
    

    with the original parameter value.

    This approach only requires that next.xhtml has also a <f:viewParam> on the very same parameter, otherwise it won't be passed through.


    Use GET forms in JSF

    The <f:viewParam> can also be used in combination with "plain HTML" GET forms.

    <f:metadata>
        <f:viewParam id="query" name="query" value="#{bean.query}" />
        <f:viewAction action="#{bean.search}" />
    </f:metadata>
    ...
    <form>
        <label for="query">Query</label>
        <input type="text" name="query" value="#{empty bean.query ? param.query : bean.query}" />
        <input type="submit" value="Search" />
        <h:message for="query" />
    </form>
    ...
    <h:dataTable value="#{bean.results}" var="result" rendered="#{not empty bean.results}">
         ...
    </h:dataTable>
    

    With basically this @RequestScoped bean:

    private String query;
    private List<Result> results;
    
    public void search() {
        results = service.search(query);
    }
    

    Note that the <h:message> is for the <f:viewParam>, not the plain HTML <input type="text">! Also note that the input value displays #{param.query} when #{bean.query} is empty, because the submitted value would otherwise not show up at all when there's a validation or conversion error. Please note that this construct is invalid for JSF input components (it is doing that "under the covers" already).


    See also: