[OmniFaces utilities] The
forEachComponent()
methods allows us to invokes an operation on every component in the component tree. This is a simplified version of regular component visiting that uses the builder pattern to provide the various optional parameters. Includes supports for only visiting components of a certain class type and two simplified functional interfaces / lambdas.
Do you need
to accomplish a task as the ones listed below (or related to these) ?
·
Invoke an operation on every component in the
component tree
·
Invoke an operation on every component in a sub-tree of the component tree
·
Invoke an operation only on components with
specific IDs
·
Invoke an operation only on components with specific
type (e.g. UIInput,
UIForm,
etc)
·
Invoke an operation only on components with
specific VisitHint
·
Invoke an operation on components that meet a
combination of above restrictions
Well, if your
answer is yes, that this post can be
very useful for you. Most probably, until now, you solved such tasks directly via JSF
VisitTree API (you can see an example of using this API in post "Use JSF VisitTree API to check a componentin the component tree"). OmniFaces combines this API with its collection
of callback interfaces (Callback) to obtain simplified version of
regular component visiting.
If you are
not familiar with OmniFaces Callback, then you can find more details and
an example in post "Subscribe With OmniFaces 2.1 (serializable) /2.0 (un-serializable) Callback-listeners to JSF System Events".
For this post, you have to be familiar with these two callbacks:
·
callback which takes an argument
public
static class ForEach {
private FacesContext facesContext; //
the FacesContext instance
private UIComponent root; // the component where tree visiting
starts
private Collection<String> ids; //
IDs of the components that are visited
private Set<VisitHint> hints; // VisitHints IDs of the components that are
visited
private Class<?>[] types; // types of the components that are
visited
public ForEach() {
facesContext = Faces.getContext();
}
public ForEach(FacesContext facesContext) {
this.facesContext = facesContext;
}
...
}
Now, behind
each of these private
fields, we have a at least a public method for initializing them, as
follows:
·
optional, we indicate the root component where
tree visiting starts (when it is omitted OmniFaces uses the default value, UIViewRoot)
via fromRoot():
public
ForEach fromRoot(UIComponent root) {
this.root = root;
return this;
}
·
optional, we indicate the IDs of the components
that are visited via havingIds() methods:
public
ForEach havingIds(Collection<String> ids) {
this.ids = ids;
return this;
}
public
ForEach havingIds(String... ids) {
this.ids = asList(ids);
return this;
}
·
optional, we indicate the VisitHints
that are used for the visit via withHints() methods:
public
ForEach withHints(Set<VisitHint> hints) {
this.hints = hints;
return this;
}
public
ForEach withHints(VisitHint... hints) {
if (hints.length > 0) {
EnumSet<VisitHint> hintsSet =
EnumSet.noneOf(hints[0].getDeclaringClass());
for (VisitHint hint : hints) {
hintsSet.add(hint);
}
this.hints = hintsSet;
}
return this;
}
·
optional, we indicate the types of the
components that are to be visited via ofTypes() method:
@SafeVarargs
public final
ForEach ofTypes(Class<?>... types) {
this.types = types;
return this;
}
Practically,
we can call one of these methods or a logic combination of them to obtain the
desired visiting model. Finally, we have three approaches to trigger the visit
process (three invoke()
methods implementations). So, we can pass the operation to be invoked for each
visited component as:
·
a Callback.WithArgument<UIComponent>
argument
public void
invoke(final Callback.WithArgument<UIComponent> operation) {
invoke(new VisitCallback() {
@Override
public
VisitResult visit(VisitContext context, UIComponent target) {
operation.invoke(target);
return ACCEPT;
}
});
}
·
a Callback.ReturningWithArgument<VisitResult,
UIComponent> argument
public void
invoke(final Callback.ReturningWithArgument<VisitResult, UIComponent>
operation) {
invoke(new VisitCallback() {
@Override
public VisitResult visit(VisitContext
context, UIComponent target) {
return operation.invoke(target);
}
});
}
·
a VisitCallback argument
public void
invoke(final VisitCallback operation) {
VisitCallback callback = operation;
if (types != null) {
callback = new TypesVisitCallback(types,
callback);
}
getRoot().visitTree(createVisitContext(getContext(),
getIds(), getHints()), callback);
}
The TypesVisitCallback
is an OmniFaces implementation of VisitCallback which takes into account
the types of the components that are to be visited:
private
static class TypesVisitCallback implements VisitCallback {
private Class<?>[] types;
private VisitCallback next;
public TypesVisitCallback(Class<?>[]
types, VisitCallback next) {
this.types = types;
this.next = next;
}
@Override
public VisitResult visit(VisitContext context,
UIComponent target) {
if (isOneInstanceOf(target.getClass(),
types)) {
return next.visit(context, target);
}
return ACCEPT;
}
}
Now, that we have the "rough" picture of what is happening, let's have several examples.
For start we need the below JSF page snippet:
...
<h:body>
<h:panelGroup id="myDivId"
layout="block">
<h:outputText value="New ATP
Player"/>
</h:panelGroup>
<h:form
id="myFormId" binding="#{playerBean.uiForm}">
<h:panelGrid id="panelId"
columns="2">
<o:outputLabel id="nameLabelId"
for="nameId" value="Name"/>
<h:inputText id="nameId"
value="#{playerBean.name}"/>
<o:outputLabel
id="surnameLabelId" for="surnameId"
value="Surname"/>
<h:inputText id="surnameId"
value="#{playerBean.surname}"/>
<o:outputLabel id="ageLabelId"
for="surnameId" value="Age"/>
<h:inputText id="ageId"
value="#{playerBean.age}"/>
<o:outputLabel id="titleLabelId"
for="titleId" value="Title" rendered="false"/>
<h:inputText id="titleId"
value="#{playerBean.title}" rendered="false"/>
<h:commandButton id="btnId"
value="Save" action="#{playerBean.someAction(1,2,3 or 4)}"/>
</h:panelGrid>
</h:form>
</h:body>
...
So, we have
a simple snippet of a JSF page, and we want to exploit the OmniFaces implementation for executing an operation for each component in the component
tree. Pass the integer 1,2,3 or 4 to the someAction()
method to switch between the below examples:
·
1 - suppress page rendering (this is the
operation). Visit from root UIViewRoot (default)
// 1 -
suppress page rendering; visit from root
// use
Callback.WithArgument<UIComponent>()
public void
useCase_1(FacesContext fc) {
// is like using fromRoot(fc.getViewRoot())
Components.forEachComponent(fc).invoke(new
Callback.WithArgument<UIComponent>() {
@Override
public void invoke(UIComponent component) {
component.setRendered(false); // invoke any
other operation
}
});
}
·
2 - suppress rendering for the components with
IDs: surnameLabelId,
surnameId,
ageLabelId,
and ageId.
Visit from root with ID, myFormId (the above uiForm)
// 2 -
suppress surnameLabelId, surnameId ,ageLabelId, ageId rendering; visit from
form, myFormId
// use
Callback.WithArgument<UIComponent>()
public void
useCase_2(FacesContext fc) {
// use havingIds(String... ids) if you don't
like to use a collection
Collection<String> ids =
Arrays.asList("myFormId:surnameId", "myFormId:ageId",
"myFormId:surnameLabelId", "myFormId:ageLabelId");
Components.forEachComponent(fc).fromRoot(uiForm).havingIds(ids).invoke(new
Callback.WithArgument<UIComponent>() {
@Override
public
void invoke(UIComponent component) {
component.setRendered(false); // invoke any
other operation
}
});
}
·
display IDs only of rendered components (will
skip components with IDs: titleLabelId and titleId. Visit from root with ID, myFormId
(the above uiForm),
with VisitHint.SKIP_UNRENDERED
// 3 -
display IDs only of rendered components (will skip titleLabelId and titleId);
visit from form, myFormId, with VisitHint.SKIP_UNRENDERED
// use
Callback.WithArgument<UIComponent>()
public void
useCase_3(FacesContext fc) {
// use withHints(VisitHint... hints) if you
don't like to use a set
Set<VisitHint> vhs = new
HashSet<>(Arrays.asList(VisitHint.SKIP_UNRENDERED));
Components.forEachComponent(fc).fromRoot(uiForm).withHints(vhs).invoke(new
Callback.WithArgument<UIComponent>() {
@Override
public void invoke(UIComponent component) {
// you should not see listed: titleLabelId
and titleId
System.out.println(component.getClientId()); // invoke any other
operation
}
});
}
·
suppress the rendering of all components of type
UIInput
and HtmlOutputLabel.
Visit from root with ID, myFormId (the above uiForm), with VisitHint.SKIP_UNRENDERED
// 4 -
suppress the rendering of all components of type UIInput and HtmlOutputLabel;
visit from form, myFormId, with VisitHint.SKIP_UNRENDERED
// use
Callback.WithArgument<UIComponent>()
public void
useCase_4(FacesContext fc) {
// use withHints(VisitHint... hints) if you
don't like to use a set
Set<VisitHint> vhs = new HashSet<>(Arrays.asList(VisitHint.SKIP_UNRENDERED));
Components.forEachComponent(fc).fromRoot(uiForm).withHints(vhs).ofTypes(UIInput.class,
HtmlOutputLabel.class).invoke(new Callback.WithArgument<UIComponent>() {
@Override
public void invoke(UIComponent component) {
component.setRendered(false); // invoke any
other operation
}
});
}
You can find
the complete source on GitHub [ComponentsForEach]
Niciun comentariu :
Trimiteți un comentariu