[OmniFaces utilities] The
findComponentRelatively()
returns the UI component matching the given client ID search expression relative to the point in the component tree of the given component. For this search both parents and children are consulted, increasingly moving further away from the given component. Parents are consulted first, then children.[OmniFaces utilities] The
findComponentInParents()
returns the UI component matching the given client ID search expression relative to the point in the component tree of the given component, searching only in its parents.[OmniFaces utilities] The
findComponentInChildren()
returns the UI component matching the given client ID search expression relative to the point in the component tree of the given component, searching only in its children.
Besides the goodies revealed in the Showcase, OmniFaces has
a lot of "hidden pearls" that can help us during development of JSF
projects. A few days ago I have "discovered" the OmniFaces utilities, which
are a collection of utility methods for the JSF API. More precisely, in the org.omnifaces.util.Components
I found the findComponentRelatively()
method:
public
static <C extends UIComponent> C findComponentRelatively
(UIComponent
component, String clientId) {
if (isEmpty(clientId)) {
return null;
}
// Search first in the naming container
parents of the given component
UIComponent result = findComponentInParents(component,
clientId);
if (result == null) {
// If not in the parents, search from the
root
result =
findComponentInChildren(getViewRoot(), clientId);
}
return (C) result;
}
Well, as the OmniFaces description says, this method "returns the UI component matching the given
client ID search expression relative to the point in the component tree of the
given component. For this search both parents and children are consulted,
increasingly moving further away from the given component. Parents are consulted
first, then children."
But, in order to use this method correctly, you must be
aware of several aspects:
·
when
searching in parents, the search will stop when the component with clientId is found or at the UIViewRoot
·
the
search in children is not relatively to the "point in the component tree of the given component", is rather
relative to the UIViewRoot
·
actually,
the findComponentRelatively()
searches the component with clientId
ONLY in instances of NamingContainer
(which are: HtmlDataTable, HtmlForm, UIData,
UIForm, NamingContainer)
·
for
accomplish its job, this method uses the findComponentInParents()
and findComponentInChildren() methods
·
These
methods (findComponentInParents() and
findComponentInChildren()) can
be called individually, they are not related to findComponentRelatively()
method
The findComponentInParents() is listed below:
public
static <C extends UIComponent> C findComponentInParents
(UIComponent
component, String clientId) {
if
(isEmpty(clientId)) {
return null;
}
for
(UIComponent parent = component; parent != null; parent = parent.getParent()) {
UIComponent result = null;
if (parent instanceof NamingContainer) {
try
{
result = parent.findComponent(clientId);
} catch (IllegalArgumentException e) {
continue;
}
}
if (result != null) {
return (C) result;
}
}
return null;
}
As documentation says, this method "returns the UI component matching the given
client ID search expression relative to the point in the component tree of the
given component, searching only in its parents.".
Well, again there are a few aspects to notice here:
·
the first
inspected parent is the " given
component"
·
the
component matching the given clientId
is searched ONLY in NamingContainer
instances
·
this is
NOT recursive search! Is just a parent-traversal algorithm from the " given component" to the UIViewRoot
When findComponentInParents() return null,
the findComponentInChildren()
is called. The code is listed below:
public
static <C extends UIComponent> C findComponentInChildren
(UIComponent
component, String clientId) {
if (isEmpty(clientId)) {
return null;
}
for
(UIComponent child : component.getChildren()) {
UIComponent result = null;
if
(child instanceof NamingContainer) {
try {
result
= child.findComponent(clientId);
}
catch (IllegalArgumentException e) {
continue;
}
}
if (result == null) {
result
= findComponentInChildren(child, clientId);
}
if
(result != null) {
return (C) result;
}
}
return null;
}
As documentation says, this method "returns the UI component matching the given
client ID search expression relative to the point in the component tree of the given component,
searching only in its children."
The important things to notice here are:
·
the
inspection doesn't start from the " given
component", as in the case of findComponentInParents().
It starts from the first child.
·
the
component matching the given clientId
is searched ONLY in NamingContainer
instances
·
this is a
recursive method - the recursive process performs a clean traversal of the
component's tree (or subtree) by visiting each node in a hierarchical approach.
·
the findComponentRelatively() uses this
method to traverse the component tree starting from the UIViewRoot
Each of these three methods uses the JSF, UIComponent.findComponent()
which acts as below:
For the sake of completeness, the source code of the findComponent()
method from UIComponentBase
(extracted from Mojarra implementation) is listed below - is pretty
straightforward:
public
UIComponent findComponent(String expr) {
if (expr == null) {
throw new NullPointerException();
}
FacesContext ctx =
FacesContext.getCurrentInstance();
final char sepChar =
UINamingContainer.getSeparatorChar(ctx);
final String SEPARATOR_STRING =
String.valueOf(sepChar);
if (expr.length() == 0) {
// if an empty value is provided, fail
fast.
throw new
IllegalArgumentException("\"\"");
}
// Identify the base component from which we
will perform our search
UIComponent base = this;
if (expr.charAt(0) == sepChar) {
//
Absolute searches start at the root of the tree
while (base.getParent() != null) {
base = base.getParent();
}
//
Treat remainder of the expression as relative
expr
= expr.substring(1);
} else if (!(base instanceof NamingContainer))
{
//
Relative expressions start at the closest NamingContainer or root
while (base.getParent() != null) {
if (base instanceof NamingContainer) {
break;
}
base = base.getParent();
}
}
// Evaluate the search expression (now
guaranteed to be relative)
UIComponent result = null;
String[] segments =
expr.split(SEPARATOR_STRING);
for (int i = 0, length = (segments.length -
1);
i < segments.length;
i++, length--) {
result = findComponent(base, segments[i], (i
== 0));
// the first element of the expression may
match base.id
// (vs. a child if of base)
if (i == 0 && result == null
&&
segments[i].equals(base.getId())) {
result = base;
}
if (result != null && (!(result
instanceof NamingContainer)) && length > 0) {
throw new
IllegalArgumentException(segments[i]);
}
if (result == null) {
break;
}
base
= result;
}
// Return the final result of our search
return (result);
}
OmniFaces, internally uses the findComponentRelatively() method.
Per example, in the OutputLabel component, OmniFaces uses this method to find
the component indicated by the for attribute. Let's check this example:
<?xml
version='1.0' encoding='UTF-8' ?>
<!DOCTYPE
html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
id="htmlId"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:o="http://omnifaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head id="headId">
<title>Register
Form</title>
</h:head>
<h:body id="bodyId">
<h:form
id="registerFormId">
<h:panelGrid id="panelId"
columns="3">
<o:outputLabel
id="labelNameId" for="nameId"
value="First Name" />
<h:inputText id="nameId"
required="true" value="#{dataBean.name}" />
<h:message id="msgNameId"
for="nameId"/>
<o:outputLabel id="labelSurnameId" for="surnameId"
value="Last Name" />
<h:inputText
id="surnameId" required="true"
value="#{dataBean.surname}" />
<h:message id="msgSurnameId"
for="surnameId"/>
<h:commandButton
value="Register">
<f:ajax execute="@form"
render="@form" />
</h:commandButton>
</h:panelGrid>
</h:form>
</h:body>
</html>
So, OmniFaces passes the instance of the OutputLabel
("given component ") and
the nameId
("given client ID "), which
is the value of the for attribute, but, is also the clientId of the indicated component (the input text with this id).
The parent component that contains the " given client ID " is the form with
id, registerFormId,
NOT the output label with id labelNameId, or the panel grid with id, panelId. This is happening because, the output label and
the panel grid are not NamingContainers, so they don't pass this check!
In the figure below, you can see this use case - since the
searched component is found by traversing the parents of the "given component ", there is no call
of the findComponentInChildren()
method:
A more comprehensive case can be like this (is just a re-write of the above example meant to force the call of the findComponentInChildren() method):
<?xml
version='1.0' encoding='UTF-8' ?>
<!DOCTYPE
html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
id="htmlId"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:o="http://omnifaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head id="headId">
<title>Register Form</title>
</h:head>
<h:body id="bodyId">
<h:panelGrid id="panelId"
columns="2">
<o:outputLabel id="labelNameId" for=":registerFormNameId:nameId"
value="First Name" />
<o:outputLabel
id="labelSurnameId" for=":registerFormSurnameId:surnameId"
value="Last Name" />
<h:form id="registerFormNameId">
<h:inputText id="nameId"
required="true" value="#{dataBean.name}" />
<h:message id="msgNameId"
for="nameId"/>
<h:commandButton
value="Register Name">
<f:ajax execute="@form"
render="@form" />
</h:commandButton>
</h:form>
<h:form id="registerFormSurnameId">
<h:inputText id="surnameId" required="true"
value="#{dataBean.surname}" />
<h:message id="msgSurnameId"
for="surnameId"/>
<h:commandButton value="Register Surname">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
</h:form>
</h:panelGrid>
</h:body>
</html>
So, OmniFaces passes the instance of the OutputLabel
("given component ") and
the :registerFormNameId:nameId
("given client ID "), which
is the value of the for attribute, but, is also the clientId of the indicated component (the input text with this id).
This time the findComponentInParents()
reaches the UIViewRoot and will return true. This means that the findComponentInChildren()
method enter in action. It will traverse, starting with the UIViewRoot,
the component tree and will find the NamingContainer with id, registerFormNameId,
as the component that contains the "
given client ID". As you can see in the below figure, the findComponentInChildren()
method traverse the component tree, including the nodes that already have been
traversed by the findComponentInParents() - the checks, 8, 9 and 10. Obviously,
calling the findComponentInParents()
first is the best approach, since this methods jumps from parent to
parent as a " kangaroo", while the findComponentInChildren()
method involves a recursive process, which is by its nature less efficient.
You may find this dissertation useless, but many JSF developers uses the findXxx() methods, without understanding the process behind the scene. Once you understand how this process works, you can write your own findXxx() methods, optimize/adjust the existing ones to obtain more power, flexibility and performance in particular cases. There is no rational reason to ignore the time spent by the application to traverse/find components in component tree.
I hope that this dissertation revealed to you the OmniFaces,
findComponentRelatively(),findComponentInParents() and findComponentInChildren()
methods, and that you will find them useful in your projects. Moreover,
they can inspire you to contribute with your own findXxx() methods.
Niciun comentariu :
Trimiteți un comentariu