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}"/>
</ui: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 otherui: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);
Related
I have a page where I want to include a part of the page (footer in this instance) dependant on values given from a view parameter.
I have my ViewScoped backing bean initializing on preRenderView
<f:metadata>
<f:viewParam name="racecode" value="#{displayResults.racecode}" />
<f:event type="preRenderView" listener="#{displayResults.init}" />
</f:metadata>
This queries the database to get the name of the footer to be included. This then, is used in this fashion :
<h:panelGroup id="customFooter" display="block">
<ui:include src="#{displayResults.customFooter}" />
</h:panelGroup>
This always gives me a missing page. But if I enter the page name manually it works. Same if I replace the ui:include with an h:outputText.
I understand that it has something to do with the phases of JSF and that at the time the ui:include is done, the value is not set yet. (reading up and better understanding the phases is something on my TODO list).
The question remains. How can I get something of the sort done. Have a bean use the viewParam, query the database and use that value in a ui:include?
#wemu has already explained the cause. The <ui:include src> is evaluated before init() method is called. His proposed <f:phaseListener> solution is however clumsy.
Just use #ManagedProperty/#PostConstruct on a #RequestScoped bean.
#ManagedProperty("#{param.racecode}")
private String racecode;
#PostConstruct
public void init() {
// ...
}
PreRenderView listeners are called within the RenderResponsePhase, before components are rendered BUT AFTER the TagHandlers are called. This means that TagHandlers will NOT see data initialized within a PreRenderView event.
If you are using a <ui:include value="#{myBean.myViewId}" /> to dynamically switch an include you can't use a PreRenderView event listener to set the myViewId property of myBean.
If you need to do that use a <f:phaseListener>.
Unfortunately, I made a mistake of choosing JSF for an internet facing, high traffic application, now I am wondering as to how to improve the scalability of this JSF webapp.
I have a JSF page that displays a large no of items each of which may be commented upon.
Inorder to reduce the state & improve performance I am trying to reduce the no of forms /commandButtons on the page.
1. Through what ways can I reduce the component tree/ statefulness of JSF ? Do the plain html elements(that are mixed in between the jsf tags) also form part of component tree ? I dont know how component state saving has been helpful to my app since I have been following plain request/response model while designing my app, (may be it is helpful for just JSF's internal requirements)!?
2. I was thinking of an approach where instead of creating a separate <h:form> (each with a separate commandButton) for every item like below,
(Usual Approach)
<h:form> <!-- for each item a separately -->
<h:outputText value="Add comment"/>
<h:inputTextarea value="#{itemController.comment}" required="true"/>
<p:commandButton actionListener="#{itemController.addUserComment(123)}" value="Add" />
</h:form>
(Alternate Approach)
I am trying to make the above better by just putting a single remoteCommand for all the items & pass the required parameters to this remoteCommand.
<form>
<input id="item1_comment"/>
<button onclick="addComment(123, 'item1_comment');"/>
</form>
<script type="text/javascript">
function addComment(itemId, id) {
$('#comment_in').attr('value', $('#'+id).attr('value'));
$('#forItem_in').attr('value', itemId);
addComment_RC(); // call remoteCommand to show the content in dialog
}
</script>
<h:form prependId="false" > <!-- for all items, just single remoteCOmmand -->
<h:inputHidden id="comment_in" value="#{itemController.comment}"/>
<h:inputHidden id="forItem_in" value="#{itemController.forItem}"/>
<p:remoteCommand name="addComment_RC" process="#form" actionListener="#{itemController.addComment()}" />
</h:form>
Is it better to do it this way (or are there any issues with this approach)?
Performance issues in the situation you describe are often caused by the large number of EL expressions, That burdens the server.
One approach to tackle this issue is to compute the comments on the client side, and pass them all at once to the server. Thus reducing the number of comment EL expression to one or none, and use only one button.
Place all the elements in one form. The comments fields are not binded.
<h:form>
// first element
<h:outputText value=#{first element}
// first comment
<h:inputTextarea id="comment1"/> <-- notice there is no EL expression
But we use a unique id for each comment
// second element
<h:outputText value=#{second element}
// second comment
<h:inputTextarea id="comment2"/>
.
.
.
</h:form>
From here you could either
1. after each blur event in any of the comment fields, ajax the server and pass as parameters the comment and the id of the comment from which the ajax call was made. on the server update your model accordingly
Or You can also gather all the comments on the client side and send them to the server at one time.
2. When the user press the submit button call a js function to aggregate all the comments in a structure that you will be able to parse easily on the server side
(ie. "{c1,comment a};{c2,comment b};{c5=,comment e}...").
pass that string to the server, parse it and update your model accordingly.
3. after each blur event in any of the comment fields, call a js function that updates an hidden field.
<h:inputHidden value="{myClass.allComments}" />
when the user submits the form parse allComments and update your model accordingly.
EDIT:
To address the general performance issue I've added recommendations from an article that I found helpful speed up part 1 Speed up part 2.
Hope this helps
btw, I would recommend the first approach rather than the last two.
JFS1.2 + Richfaces 3.3
Situation is as follows:
JSP page renders conditionally one or another panelGroup.
Within each panelGroup there are couple setters and one command button.
Each of two panelGroups uses own bean for setting and performing action.
On the top of a page there's selectOneRadio with (obvious) two items - coresponding tow options of conditional rendering.
Page renders properly, switcher causes to render appropriate panel.
Case is, commands buttons doesn't call an action.
I know what's going on - when I click a button to call action dom is regenerated, but the value that hold my decision to display particular panel doesn't exist anymore. The button is not recreated, action is not fired.
Technically:
<h:selectOneRadio value="#{reportType}">
<f:selectItem itemLabel="x" itemValue="x"/>
<f:selectItem itemLabel="y" itemValue="y"/>
<a4j:support event="onclick" reRender="xPanel, yPanel/>
</h:selectOneRadio>
<h:panelGrid id="xPanel "columns="2" rendered="#{reportType eq 'x'}">
<...some setters>
<... commandbutton>
</h:panelGrid>
<h:panelGrid id="yPanel "columns="2" rendered="#{reportType eq 'y'}">
<...some setters>
<... commandbutton>
</h:panelGrid>
Question is, how to design the page to obtain proper rendering and actions?
For now, I created additional session bean that holds switching value (x|y), but that desing smells bad for me...
RichFaces 3.3 offers the <a4j:keepAlive> tag which does basically the same as Tomahawk's <t:saveState> and JSF2 #ViewScoped. Add the following line somewhere in your view:
<a4j:keepAlive beanName="#{bean}" />
This will keep the bean alive as long as you're returning null or void from action(listener) methods.
See also:
JSF 1.2: How to keep request scoped managed bean alive across postbacks on same view?
I have a problem with finding component in JSF tree. Suppose I have the following template:
<a4j:form id="someForm">
<a4j:outputPanel id="somePanel">
<a4j:repeat id="people" value="#{bean.people}" rowKeyVar="_row" var="_data" stateVar="_state">
<s:decorate id="personPanel" template="edit.xhtml">
<h:outputLabel for="personAge" value="Choose your age:" />
<h:selectOneMenu id="personAge" value="#{_data.age}">
<s:selectItems var="_item" value="#{ageValues}" label="#{_item.description}" />
</h:selectOneMenu>
</s:decorate>
</a4j:repeat>
</a4j:outputPanel>
</a4j:form>
Namespaces are defined as:
xmlns:a4j="http://richfaces.org/a4j"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:s="http://jboss.com/products/seam/taglib"
As you can see, there is a a4j:repeat tag, so there can be n rendered select inputs on the page. How can I find n-th component in JSF tree at the server side? At the client side, components are rendered like: someForm:somePanel:0:personPanel:personAge. I'm trying to find component this way:
UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
UIInput ageInput = (UIInput) root.findComponent("someForm:somePanel:0:personPanel:personAge");
But it couldn't be find. I've checked tree, and it seems like component with that id doesn't exist.
So how can I obtain this component? Is there any way to achieve that?
EDIT:
I've found some workaround. Actually, I didn't need components, but their values. Values can be retrieved from request by their names. Following code:
FacesContext facesContext = FacesContext.getCurrentInstance();
String ageValue = facesContext.getExternalContext().getRequestParameterMap().get("someForm:somePanel:0:personPanel:personAge");
did the work.
a4j:repeat isn't a tag handler that would create dedicated components for each iteration. Rather, it causes its child components to be visited repeatedly during each phase of the JSF lifecycle. That is, there isn't a dedicated component for each row.
For more information on the difference between tag handlers and components, see:
https://rogerkeays.com/jsf-c-foreach-vs-ui-repeat
It can usually be avoided to lookup components by name on the Java side. If you told us why you're trying to do this, we might suggest alternatives.
Edit: Validation in JSF is usually done by a Validator or (for complex cases) in the action method by working directly on the data in the backing bean, putting FacesMessage into the FacesContext manually. I don't see why you'd need the component for validation?
In my composite component, I have a ui:repeat that has, among other static things, a command button, like this:
<ui:repeat var="article" value="#{cc.attrs.articleList}">
<strong>Aricle: #{article}</strong>
<h:commandButton id="addToFavs" value="Subscribe" binding="..." type="..." >
<f:setPropertyActionListener value="#{article}" target="..." />
</h:commandButton >
</ui:repeat>
Now, I'd like to expose this event in the composite interface, so that in my page, I may attach event listeners and tie in f:ajax.
Had it been outside of ui:repeat (i.e., there existed only one such button), that would have been quite easy, like this:
<composite:interface>
<composite:actionSource name="addToFavs" targets="#{cc.clientId}:addToFavs" />
<composite:clientBehavior name="ajax" default="true"
event="action" targets="#{cc.clientId}:addToFavs"/>
</composite:interface>
But that fails in this case, because there's no one component by that ID (addToFavs), but rather a bunch of them. Do you know how should I expose these buttons in the interface?
Regards,
Pradyumna
Updating this post with my best guess, just in case someone wants to know if I could achieve it:
No I couldn't do it. Looks like we can't do it. We can only expose components whose IDs relative to the composite component are known to the component author apriori.