© Bauke Scholtz, Arjan Tijms 2018

Bauke Scholtz and Arjan Tijms, The Definitive Guide to JSF in Java EE 8, https://doi.org/10.1007/978-1-4842-3387-0_12

12. Search Expressions

Bauke Scholtz and Arjan Tijms2

(1)Willemstad, Curaçao

(2)Amsterdam, Noord-Holland, The Netherlands

As stated in the sections “Ajax Life Cycle” in Chapter 3 and “Ajaxifying Components” in Chapter 4, the execute and render attributes of <f:ajax> tag take a space-separated collection of so-called component search expressions .

The search expressions have always been part of JSF (JavaServer Faces) since the beginning as this is used in the for attribute of <h:outputLabel>, <h:message>, and <h:messages>, but they have only become essential knowledge for the JSF developer since the introduction of <f:ajax> in JSF 2.0. Namely, labels and messages are in almost any case already within the very same naming container parent as the target input component, so simply specifying the ID of the target input component in the for attribute already suffices, but this is not necessarily true for the execute and render attributes of <f:ajax> as the target component may sit in a different naming container context or even in a physically different Facelets file.

To overcome these difficulties, JSF 2.0 introduced a few more abstract search expressions: "@this", "@form", "@all", and "@none". Something like "@form" is particularly easy to use, as it just means target whatever the current form is. If that current form is defined two parent templates up from the page where it’s referenced, this really makes referencing it much easier.

Although they made things much easier, these keywords were quite limited. Not only are there just four of them, but they're also not extensible and the default JSF component set only uses them internally in the <f:ajax> tag. Using them in other components, even in JSF’s own <h:outputLabel>, <h:message>, and <h:messages>, as well as using them programmatically, was left out. Therefore, in JSF 2.3 a "Component Search Expression Framework" was at the last moment introduced that greatly expands upon those four keywords. It was largely based on a proven API (application programming interface ) of PrimeFaces.1

Relative Local IDs

This is the simplest form of a component search expression. The most common use cases are found in the for attribute of <h:outputLabel>, <h:message>, and <h:messages> components. It simply references the sole ID of the target UIInput component.

<h:outputLabel for="email" value="Email address" />
<h:inputText id="email" value="#{login.email}" required="true" />
<h:message for="email" />

This only prerequires that the target component is also sitting within the very same naming container parent. A naming container parent is a component that implements the NamingContainer interface.2 In standard JSF, only <h:form>, <h:dataTable>, <ui:repeat>, and <f:subview> are an instance of NamingContainer. All composite components are also an instance of NamingContainer, but tag files are not.

In case you need to reference a specific UIInput component within a naming container from <h:outputLabel> on, then you need to append the so-called naming container separator character to the ID of the naming container component and then the ID of the target UIInput component. The default naming container separator character is a colon “:”. The currently configured separator character is programmatically available by UINamingContainer#getSeparatorCharacter().3

char separatorCharacter = UINamingContainer
    .getSeparatorCharacter(FacesContext.getCurrentInstance);

This is configurable via the javax.faces.SEPARATOR_CHAR context parameter in web.xml.

<context-param>
    <param-name>javax.faces.SEPARATOR_CHAR</param-name>
    <param-value>-</param-value>
</context-param>
Caution

Changing this to something else like a hyphen “-” or even an underscore “_” is not recommended.4

In the long term, it is confusing and brittle as those characters are also allowed in the ID attribute itself. JSF does not validate the component ID against the currently configured naming container separator character and thus it may easily slip through and cause trouble while referencing such a component in a search expression.

Coming back to referencing a specific UIInput component within a naming container from <h:outputLabel> on, in the example composite component <t:inputLocalTime> as demonstrated in the section “Composite Components” in Chapter 7, the hour drop-down component has an ID of “hour”. Thus, for <h:outputLabel>, when using the default naming container separator character, the relative local ID of the hour dropdown inside the composite component is “time:hour”.

<h:outputLabel id="l_time" for="time:hour" value="Time" />
<t:inputLocalTime id="time" value="#{schedule.time}" required="true" />
<h:message id="m_time" for="time" />

Note that this is not necessary for <h:message> as faces messages are under the hood already added to the faces context with the client ID of the composite component itself.

Using relative local IDs also works within the context of <h:column> of <h:dataTable>. It’s then interpreted in the context of the currently iterated row, even when the target component is sitting in another column. The following example demonstrates that:

<h:dataTable id="users" value="#{admin.users}" var="user">
    ...
    <h:column>
        <f:facet name="header">Country</f:facet>
        <h:selectOneMenu id="country" value="#{user.address.country}">
            <f:selectItems value="#{data.countries}" />
            <f:ajax render="city" />
        </h:selectOneMenu>
    </h:column>
    <h:column>
        <f:facet name="header">City</f:facet>
        <h:selectOneMenu id="city" value="#{user.address.city}">
            <f:selectItems value="#{user.address.country.cities}" />
        </h:selectOneMenu>
    </h:column>
    ...
</h:dataTable>

Under the hood, relative local IDs are resolved using the algorithm as described in the UIComponent#findComponent() API.5 This means that you can also resolve them programmatically. You only need to ensure that the findComponent() method is invoked on the correct base component , not on, for example, UIViewRoot.

Absolute Hierarchical IDs

In case the target component is not within the same naming container parent as the current component, then you need an absolute hierarchical ID instead of a local relative ID. The key difference is that an absolute hierarchical ID starts with the naming container separator character. It will then search for the target component from the UIViewRoot on. Such construct is often used in the render attribute of <f:ajax> when it needs to reference a component that is not located inside the same form.

<h:form id="search">
    ...
    <h:commandButton id="submit" ...>
        <f:ajax execute="@form" render=":results" />
    </h:commandButton>
</h:form>
<h:panelGroup id="results" layout="block">
    ...
</h:panelGroup>

A less common use case where an absolute hierarchical ID is needed is when you need to reference a component that is in turn nested in another naming container—for example, when you want to update <h:message> associated with a composite component during a <cc:clientBehavior> event inside the composite component .

<h:form id="form">
    <h:outputLabel id="l_time" for="time:hour" value="Time" />
    <t:inputLocalTime id="time" value="#{schedule.time}" required="true">
        <f:ajax render=":form:m_time" />
    <h:message id="m_time" for="time" />
</h:form>

You could argue that this is a bug or an oversight in the JSF specification. This is very true and should be worked on for a next version of JSF. Another yet less common use case is when you need to update a specific iteration round of an iteration component, such as <h:dataTable> and <ui:repeat>.

<h:form id="form">
    <h:dataTable id="table" value="#{bean.items}" var="item">
        <h:column>
            <h:panelGroup id="column1" layout="block">
                ...
            </h:panelGroup>
        </h:column>
        <h:column>
            <h:panelGroup id="column2" layout="block">
                ...
            </h:panelGroup>
        </h:column>
    </h:dataTable>
    <h:commandButton value="Update second row">
        <f:ajax render=":form:table:1:column1
                        :form:table:1:column2">
        </f:ajax>
    </h:commandButton>
</h:form>

Note that the iteration index is zero-based as with normal Java collections and arrays. Also note that you basically need to wrap the cell’s content in another component in order to properly reference the cell’s content, and that you need to explicitly specify every column in order to update the entire row, as demonstrated above. Updating the entire column is also possible, but less convenient because you basically need to specify the search expression for every single row. Fortunately, the render attribute can take an EL (Expression Language) expression and the EL stream API can be used to concatenate a bunch of strings in the :form:table:[i]:column format depending on the amount of items in the table.

<h:commandButton value="Update second column">
    <f:ajax render="#{bean.items.stream()
        .map(i -> ' :form:list:' += bean.items.indexOf(i) += ':column2')
        .reduce((l, r) -> (l += r)).get()}">
    </f:ajax>
</h:commandButton>

Admittedly, this is not the most elegant approach. You’d better delegate to a custom function in an application-scoped bean. It could look something like the following:

<h:commandButton value="Update second column">
    <f:ajax
        render="#{ajax.columnIds(bean.items, ':form:table::column2')}"
    </f:ajax>
</h:commandButton>

Whereby the #{ajax} application-scoped bean looks something like the following:

@Named @ApplicationScoped
public class Ajax {


    public String columnIds(List<?> list, String idTemplate) {
        return IntStream.range(0, list.size()).boxed()
            .map(i -> idTemplate.replace("::", ":" + i + ":"))
            .collect(Collectors.joining(" "));
    }
}

That’s already something better, but still boilerplate-ish. If necessary, you can also programmatically add Ajax render IDs from a backing bean on. You can use the PartialViewContext#getRenderIds() 6 for this. The returned collection is, namely, mutable and only consulted during the render response phase (sixth phase). You also need to specify an absolute hierarchical ID here, but with only one important difference: it cannot start with the naming container separator character. In other words, “:form:table:0:column2” isn’t going to work; you need to specify “form:table:0:column2” instead. It’s always resolved relative to UIViewRoot.

FacesContext context = FacesContext.getCurrentInstance();
PartialViewContext ajaxContext = context.getPartialViewContext();
ajaxContext.getRenderIds().add("form:table:0:column2");

As a tip, in case you’re having a hard time figuring out the absolute hierarchical ID and/or memorizing which components exactly are naming containers, then you can always look in the generated HTML output. Open the JSF page in your favorite web browser, do a View Page Source, locate the HTML element representation of the JSF component of interest, take the value of its ID attribute, and finally prefix it with the naming container separator character.

Also, in case you encounter an autogenerated ID prefixed with j_id, then you absolutely need to give the associated JSF component a fixed ID; otherwise its value would be off when the component’s position in the component tree is subject to be changed because of, for example, a conditionally included component somewhere before the position of the component of interest. (See also the section “Text-Based Input Components” in Chapter 4.)

Like relative local IDs , absolute hierarchical IDs can be programmatically resolved using the algorithm as described in the UIComponent#findComponent() API.7 The following example demonstrates how to get hold of the UIData component representing <h:form id="form"><h:dataTable id="table">.

UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
UIData table = (UIData) root.findComponent("form:table");

Standard Search Keywords

JSF provides a set of more abstract search expressions, known as “search keywords .” They all start with the “@” character. They can be used to substitute a fixed component ID in the search expression. Table 12-1 provides an overview of them.

Table 12-1 Standard Search Keywords Provided by JSF

Keyword

Resolves to

Since

@this

UIComponent#getCurrentComponent()

2.0

@form

UIComponent#getNamingContainer() until an UIForm is encountered

2.0

@all

Everything

2.0

@none

Nothing

2.0

@parent

UIComponent#getParent()

2.3

@child(index)

UIComponent#getChildren() at given index

2.3

@next

UIComponent#getParent() and then UIComponent#getChildren() at next index

2.3

@previous

UIComponent#getParent() and then UIComponent#getChildren() at previous index

2.3

@namingcontainer

UIComponent#getNamingContainer()

2.3

@composite

UIComponent#getCompositeComponentParent()

2.3

@id(id)

UIComponent#findComponent() with given ID .

2.3

@root

FacesContext#getViewRoot()

2.3

In a standard JSF component set, all search keywords, including custom ones, can be used in the following component attributes :

  • <f:ajax execute>—Specifies components which must be processed during the apply request values, process validations, update model values, and invoke application phases (second, third, fourth, and fifth phases) of the Ajax postback request. Defaults to @this.

  • <f:ajax render>—Specifies components which must be processed during the render response phase (sixth phase) of the Ajax postback request. Defaults to @none.

  • <h:outputLabel for>—Specifies the target component of the generated HTML <label> element. Defaults to @none.

  • <h:message for>—Specifies the target component for which the first faces message must be rendered. Defaults to @none.

  • <h:messages for>—Specifies the target component for which all faces messages must be rendered. Defaults to @none.

Note that using search keywords is, for the for attribute of <h:outputLabel>, <h:message>, and <h:messages>, has only been possible since JSF 2.3. In older versions, only relative and absolute IDs were supported.

The most commonly used search keyword is undoubtedly @form. You have basically no other choice when using <f:ajax> in a UICommand component which is supposed to process the entire form.

<h:form>              
    ...
    <h:commandButton value="Submit" ...>
        <f:ajax execute="@form" />
    </h:commandButton>
</h:form>

Also new since JSF 2.3 is that search keywords can be chained with regular component IDs. Following is an example which expands on the example given before in the section “Absolute Hierarchical IDs.”

<h:form>
    <h:outputLabel id="l_time" for="time:hour" value="Time" />
    <t:inputLocalTime id="time" value="#{schedule.time}" required="true">
        <f:ajax render="@form:m_time" />
    <h:message id="m_time" for="time" />
</h:form>

And following is an example that updates the entire table when a row is deleted.

<h:form>
    <h:dataTable value="#{products.list}" var="product">
        ...
        <h:column>
            <h:commandButton id="delete" value="Delete"
                action="#{products.delete(product)}">
                <f:ajax render="@namingcontainer" />
            </h:commandButton>
        </h:column>
    </h:dataTable>
</h:form>

Note particularly that it thus references the closest component implementing the NamingContainer interface, which, in this context, is <h:dataTable> and thus not <h:form>.

As to programmatic resolution, search expressions with keywords cannot be programmatically resolved using UIComponent#findComponent(). For that, you need SearchExpressionHandler#resolveComponent() or resolveComponents() 8 instead. SearchExpressionHandler is in turn available by Application#getSearchExpressionHandler(). You also need to create SearchExpressionContext 9 beforehand which wraps the component to start searching from.

FacesContext context = FacesContext.getCurrentInstance();
UIComponent base = context.getViewRoot(); // Can be any component.
String expression = "@namingcontainer";


SearchExpressionContext searchContext = SearchExpressionContext
    .createSearchExpressionContext(context, base);
SearchExpressionHandler searchHandler = context.getApplication()
    .getSearchExpressionHandler();
handler.resolveComponent(searchContext, expression, (ctx, found) -> {
    System.out.println(found);
});

Frankly, it’s quite verbose, but it’s JSF API’s own. Fortunately, utility libraries such as OmniFaces exist.

Custom Search Keywords

The Component Search Expression Framework introduced in JSF 2.3 also comes with an API which allows us to create custom search keywords. Imagine that you have a form with multiple <h:message> components, and that you’d like to re-render them all when submitting the form. Then you’d be tempted also to use @form in the render attribute of <f:ajax>.

<h:form>                
    <h:outputLabel for="input1" ... />
    <h:inputText id="input1" ... />
    <h:message for="input1" />


    <h:outputLabel for="input2" ... />
    <h:inputText id="input2" ... />
    <h:message for="input2" />


    <h:outputLabel for="input3" ... />
    <h:inputText id="input3" ... />
    <h:message for="input3" />


    <h:commandButton value="Submit" ...>
        <f:ajax execute="@form" render="@form" />
    </h:commandButton>
</h:form>

But this is not terribly efficient. In fact, it also unnecessarily re-renders all label and input components and any other static content inside the very same form which doesn’t at all change during the Ajax postback request. This is a waste of resources. Ideally, we should have a search keyword like “@messages” which basically references all message components within the same form .

<h:form>
    <h:outputLabel for="input1" ... />
    <h:inputText id="input1" ... />
    <h:message id="m_input1" for="input1" />


    <h:outputLabel for="input2" ... />
    <h:inputText id="input2" ... />
    <h:message id="m_input2" for="input2" />


    <h:outputLabel for="input3" ... />
    <h:inputText id="input3" ... />
    <h:message id="m_input3" for="input3" />


    <h:commandButton value="Submit" ...>
        <f:ajax execute="@form" render="@messages" />
    </h:commandButton>
</h:form>

Note that each <h:message> component has an explicit ID set, because without an explicit ID, they will by default render nothing to the HTML output, and then the JSF Ajax API JavaScript wouldn’t be able to find them in order to update its content based on the Ajax response .

In order to get JSF to recognize the new search keyword @messages, first extend the javax.faces.component.search.SearchKeywordResolver 10 as follows:

public class MessagesKeywordResolver extends SearchKeywordResolver {

    @Override
    public boolean isResolverForKeyword
        (SearchExpressionContext context, String keyword)
    {
        return "messages".equals(keyword);
    }


    @Override
    public void resolve
        (SearchKeywordContext context, UIComponent base, String keyword)
    {
        UIComponent form = base.getNamingContainer();
        while (!(form instanceof UIForm) && form != null) {
            form = form.getNamingContainer();
        }


        if (form != null) {
            Set<String> messageClientIds = new HashSet<>();
            VisitContext visitContext = VisitContext.createVisitContext
                (context.getSearchExpressionContext().getFacesContext());


            form.visitTree(visitContext, (visit, child) -> {
                if (child instanceof UIMessage) {
                    messageClientIds.add(child.getClientId());
                }
                return VisitResult.ACCEPT;
            });


            if (!messageClientIds.isEmpty()) {
                context.invokeContextCallback(new UIMessage() {
                    @Override
                    public String getClientId(FacesContext context) {
                        return String.join(" ", messageClientIds);
                    }
                });
            }
        }


        context.setKeywordResolved(true);
    }
}

It should be noted that this approach is already slightly hacky. Namely, the intent of the SearchKeywordResolver is to resolve a keyword to exactly one component whose client ID will then be used to substitute the keyword . This component will then be passed to SearchKeywordContext#invokeContextCallback(). In the above approach, we instead collect the client IDs of all UIMessage components found within the parent UIForm and then supply a fake UIMessage component to invokeContextCallback() who will in turn call the getClientId() of the fake UIMessage component which actually returns the desired collection of client IDs.

It should also be noted that UIComponent#visitTree() 11 is being used instead of recursing over UIComponent#getChildren() in order to collect the client IDs of any UIMessage component. Namely, when plain iterating over children, you may sooner or later come across an iterator component such as <h:dataTable> or <ui:repeat>, and if it happens to have only one UIMessage component nested, then you’ll effectively end up with only one client ID, namely, the one without the iteration index. UIComponent#visitTree() doesn’t do that; any iterator component in the path will actually iterate over its model value and visually give back multiple UIMessage components, each with the correct client ID with the iteration index included.

In the end, SearchKeywordContext#setKeywordResolved() must be called with true in order to inform the search context that the keyword has successfully been resolved, even if it actually resolved to nothing. It doesn’t actually do any harm if you forgot this, but if you don’t mark the search keyword resolved this way, then the search context will continue consulting all other search keyword resolvers, which might end up being less efficient .

Finally, in order to get the new MessagesKeywordResolver to run, register it in faces-config.xml as follows:

<application>
    <search-keyword-resolver>
        com.example.project.MessagesKeywordResolver
    </search-keyword-resolver>
</application>

Or, programmatically in a @WebListener as follows:

@WebListener
public class ApplicationConfig implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent event) {
        FacesContext.getCurrentInstance().getApplication()
            .addSearchKeywordResolver(new MessagesKeywordResolver());
    }
}

Note that the FacesContext must be available at this point and hence a ServletContainerInitializer won't necessarily work, and that the FacesServlet may not have serviced a request yet and hence registration in some managed bean won't necessarily work .

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.179.239