I have a set of jsf components that are statically generated from a set of excel files (they are updated by business people). Each generated file represents a business object that has slightly different data, and all of them belong to a same class.
In order to render this dynamically, the only solution I found was to set up a bunch of ui:fragment
and dispatch to the right component at runtime:
<!-- IMPLEMENTATION -->
<composite:implementation>
<ui:fragment rendered="#{cc.attrs.type eq 'cartcred'}">
<limites:limites-cartcred limite="#{cc.attrs.limite}"/>
</ui:fragment>
<ui:fragment rendered="#{cc.attrs.type eq 'cdcp'}">
<limites:limites-cdcp limite="#{cc.attrs.limite}"/>
</ui:fragment>
<ui:fragment rendered="#{cc.attrs.type eq 'cheqpredatado'}">
<limites:limites-cheqpredatado limite="#{cc.attrs.limite}"/>
</u开发者_如何学编程i:fragment>
<ui:fragment rendered="#{cc.attrs.type eq 'confirming'}">
<limites:limites-confirming limite="#{cc.attrs.limite}"/>
</ui:fragment>
<!-- many more lines -->
<!-- many more lines -->
<!-- many more lines -->
<ui:fragment rendered="#{cc.attrs.type eq 'contacorr'}">
<limites:limites-contacorr limite="#{cc.attrs.limite}"/>
</ui:fragment>
But I found out that the perfomance of this is terrible. I tought that JSF would only render a single component, but it seems that it is rendering all of them and "hiding" the others at runtime.
Is there a more efficient way of achieving my goal? I want to render a single component based on runtime information about a business class (much like an if-then-else), but I can only determine what is the component to render at runtime.
Clarification:
what happens is that each component referenced by limites:limites*
is a huge complex page with lots of other components. At runtime, the parameter named type' will decide what component to render. But my tests show that if I only render one component, but leave the other
ui:fragments` (even knowing that they will not be rendered), it will render much slower than if I remove the components.
So if my page is exactly like this:
<composite:interface>
<composite:attribute name="type" required="true" />
<composite:attribute name="limite" required="true" />
</composite:interface>
<composite:implementation>
<ui:fragment rendered="#{cc.attrs.type eq 'cartcred'}">
<limites:limites-cartcred limite="#{cc.attrs.limite}"/>
</ui:fragment>
</composite:implementation>
it will render much (around 10x) faster than the initial version, even though the parameters are the same. I suspect that JSF will create the entire component tree and only at runtime it will decide (depending on the supplied parameter) if it will render each other or not.
Edit
Almost there. I just need to include my composite component dynamically. I tried evaluating an ELExpression but that didn't work. What I need is a way of accessing the current scope within the component creation, and using that to generate the proper file name:
//obviously, ELExpressions don't work here
Resource resource = application.getResourceHandler().createResource("file-#{varStatus.loop}.xhtml", "components/dynamicfaces");
Yes, the rendered
attribute evaluates during render time, not during build time. Yes, it is relatively terrible. Imagine that one such a condition costs 1ms, evaluating ten of them would take in total 10 times longer, 10ms. If you in turn have ten of those components in a paginated table, the webapp loading time would take 0,1 second longer. About one eyeblink longer. But if you don't paginate and/or are using MSIE as reference browser, then it would take much longer. Are you paginating the data and testing in proper browsers?
Best what you could do is to replace the <ui:fragment>
by JSTL tags like <c:if>
/<c:choose>
so that it evaluates during build time, not during render time. Or, alternatively, build the component tree in the backing bean constructor instead of in the view.
One possibility might be to use the binding
attribute to access a container
component from inside your managed bean and build the component tree from the
java side. That way you could include only the needed components, unneeded
components won't be evaluated at all.
JSP:
<h:panelGroup binding="#{managedBean.panel}"/>
Managed Bean:
private UIPanel panel;
// getter and setter
// Action method, might also work in a @PostConstruct
public String showComponent() {
if (showComponent1) {
UIOutput component1 = new HtmlOutputText();
component1.setValue("Hello world!");
getPanel().getChildren().add(component1);
}
return "viewId";
}
I haven't used this together with composite components yet, this question seems to have some more details and an example application regarding using this with composite components.
Edit: Regarding your edit, you can also evaluate EL expressions in your managed bean like this:
FacesContext facesContext = FacesContext.getCurrentInstance();
ELContext elContext = facesContext.getELContext();
ExpressionFactory exprFactory = facesContext.getApplication().getExpressionFactory();
ValueExpression expr = exprFactory.createValueExpression(elContext, "#{expr}", String.class);
String value = (String) expr.getValue(elContext);
精彩评论