I have, what I would assume, is a pretty common use case. We're rendering a simple "Comments" page using JSF on Wildfly 10.0. Each comment may have a parent comment, and child comments underneath it. Since there's no way to know ahead of time what the structure is, we'd like to create a JSF fragment and <ui:include /> it recursively to render the contents. It would look something like this...
Main page:
<ul class="comments>
<ui:repeat value="#{myObj.comments}" var="comment">
<ui:include src="/WEB-INF/fragments/comment.xhtml">
<ui:param name="comment" value="#{comment}" />
</ui:include>
</ui:repeat>
</ul>
Comment Fragment:
<li><h:outputText value="#{comment.text}">
<ui:fragment rendered="#{not empty comment.childComments}">
<ul class="comments">
<ui:repeat value="#{comment.childComments}" var="comment">
<ui:include src="/WEB-INF/fragments/comment.xhtml">
<ui:param name="comment" value="#{comment}" />
</ui:include>
</ui:repeat>
</ul>
</ui:fragment>
</li>
However, when I run this code, the recursion seems to cause java.lang.StackOverflowError, regardless of how many items there are. Additionally, we see a javax.servlet.ServletException saying, "Could not Resolve Variable [Overflow]"
Is there a reason why this recursive call results in this Exception? Is there a better way to accomplish this? I've tried using <c:forEach /> to iterate over the comments, however when I do this it does not appear to work in JSF. I've tried both the http://xmlns.jcp.org/jsp/jstl/core and http://java.sun.com/jsp/jstl/core namespaces for the taglib, but the <c:forEach /> tag doesn't seem to iterate over my objects. (That is, nothing is being rendered to the page)
Any help you can give would be GREATLY appreciated.
Related
I just inherited a project implemented in JSF. I have the following code which looks fine in Chrome, but Firefox renders the borders on the "empty" list items:
<ul>
<li><a href="/home">Home</li>
<li>
<s:link view="/signup.xhtml" rendered="#{someCondition}">Sign Up</s:link>
</li>
<!-- etc... -->
</ul>
Which ends up looking like:
Is there a JSF tag to conditionally render the <li> ?
If you do it as in #CoolBeans example, you will get a <span> around your <li>. In some cases it might disrupt your layout, besides you don't really want an extra tag under <ul>. To get rid of it, use <ui:fragment rendered="#{condition}" /> around your item instead of <h:panelGroup>.
Also you can use style attribute to hide an item:
<li style="display: #{condition ? 'list-item' : 'none'};" />
No li is vanilla html, not a jsf component.
You can hack it by putting a <h:panelGroup /> around the li element and setting the rendered property on it.
ie.
<h:panelGroup rendered="#{someCondition}">
<li>
<s:link view="/signup.xhtml">Sign Up</s:link>
</li>
</h:panelGroup>
Another option is to use <f:verbatim rendered="#{someCondition}" >, but keep in mind that it has been deprecated in JSF 2.0.
You could also use the core JSTL library:
<c:if test="#{someCondition}">
<li>
<s:link view="/signup.xhtml">Sign Up</s:link>
</li>
</c:if>
I think using this core tag makes the code easier to understand than using the panelGroup tag.
I know that with JSF 2, facelets is the preferred view declaration language.
Is JSP to jsf deprecated?
Anyway, I need to create a special layout so I cannot use Datatable. Instead, I have 6 divs that I use as columns in which I drop a collection of Articles.
My problem is that I have a JSF composite component, that is injected with a Collection
A:
List<Article>
object.
The component then needs to divide the size of the collection into equal pieces for each column. Then set the appropiate offset and size for each
<ui:repeat></ui:repeat>
so i end up with this
<!-- INTERFACE -->
<cc:interface>
<cc:attribute name="featuredArticles" required="true" type="java.util.List;" />
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<div class="col">
<ui:repeat value="#{cc.attrs.featuredArticles}" var="art" offset="??" size="??">
<mycomps:article art="#{art}" />
</ui:repeat>
</div>
<div class="col">
<ui:repeat value="#{cc.attrs.featuredArticles}" var="art" offset="??" size="??">
<mycomps:article art="#{art}" />
</ui:repeat>
</div>
<div class="col">
<ui:repeat value="#{cc.attrs.featuredArticles}" var="art" offset="??" size="??">
<mycomps:article art="#{art}" />
</ui:repeat>
</div>
<div class="col">
...same here...
</div>
<div class="col">
...same here...
</div>
</cc:implementation>
So how do I calculate those offsets and sizes so that each columns iterates over a portion of the collection? Or maybe there's a better way?
You can get collection's size with fn:length and there are basic arithmetic operators in EL.
<ui:composition xmlns:fn="http://java.sun.com/jsp/jstl/core">
...
<ui:param name="size" value="#{fn:length(featuredArticles) / 6}" />
...
<ui:repeat size="#{size}">
...
</ui:composition>
Update: as to the rounding, that get tricky. In old JSP you could use JSTL <fmt:formatNumber> for this which can export to a var attribute instead of displaying it straight in the view.
<fmt:formatNumber var="size" value="${fn:length(featuredArticles) / 6}" pattern="0" />
But the JSTL fmt is not available in Facelets.
A hacky way would be to split the fractions using fn:substringBefore.
<ui:param name="size" value="#{fn:substringBefore(fn:length(featuredArticles) / 6, '.')}" />
But this always rounds down.
The best way would be to create a custom EL function. You can find an example in this answer. For JSF 2.0 you only need to replace the deprecated <param-name>facelets.LIBRARIES</param-name> by <param-name>javax.faces.FACELETS_LIBRARIES</param-name>. Finally you'll end up like as:
<ui:param name="size" value="#{x:roundUp(fn:length(featuredArticles) / 6)}" />
As a completely different alternative, you could also do this job in the constructor, init or getter of a managed bean.
I think the title is clear enough so I only add an example of typical situation.
First block of code:
<div id="mailpanel">
<h:panelGroup id="sendmailpane" styleClass="sendmailpane" layout="block"
rendered="#{userReports.reportRendered}">
<o:inputTextarea promptText="#{msg['mail.listrules']}" promptTextStyle="color: #333"
value="#{userReports.mailingList}" styleClass="maillist"/>
<br/>
<h:commandLink id="sendlink" value="#{msg['mail.sendLink']}"
action="#{userReports.sendMail}"/>
</h:panelGroup>
</div>
Second (copied) block of code:
<div id="mailpanel">
<h:panelGroup id="sendmailpane" styleClass="sendmailpane" layout="block"
rendered="#{projectReports.reportRendered}">
<o:inputTextarea promptText="#{msg['mail.listrules']}" promptTextStyle="color: #333"
value="#{projectReports.mailingList}" styleClass="maillist"/>
<br/>
<h:commandLink id="sendlink" value="#{msg['mail.sendLink']}"
action="#{projectReports.sendMail}"/>
</h:panelGroup>
</div>
As you can see both blocks of code are almost similar but each of them uses different backing bean (but even beans have a superclass and all used in this example methods are actually methods of that superclass).
<ui:include src="commonFile.jsp">
<ui:param name="reportsBean" value="#{projectReports}" />
</ui:include>
and in the commonFile.jsp you have:
<h:commandLink id="sendlink" value="#{msg['mail.sendLink']}"
action="#{reportsBean.sendMail}" />
You cannot, alas, specify what params exactly are to be included. That's why I'm using the following practice: whenever you add a parameter, you put a comment ontop of the commonFile.jsp stating the name, the type and the required/optional. For example:
<!-- param: reportsBean, required -->
<!-- param: showLegend, optional, default: false, type: boolean -->
In JSP, you can use custom tags for this. In Facelets, you can use templating or the JSF 2.0 composite components for this.
do you know a way to select a different facelets component at runtime?
I've got some of code similar to this:
<s:fragment rendered="#{r== 'case1'}">
<div>
<ui:include src="case1.xhtml" />
</div>
</s:fragment>
<s:fragment rendered="#{r== 'case2'}">
<div>
<ui:include src="case2.xhtml" />
</div>
</s:fragment>
I'd like to write
<ui:include src="#{r}.xhtml" />
Thanks.
Your solution should work OK - the src attribute can be a literal or an EL expression. You might want to make the expression use a managed bean property or resolve it through a function. That way, you can ensure that it is never null (you could return a reference to an empty page if it was). You'll probably get a 404 error if #{r} resolves to null.
<ui:include src="#{myfn:resolveNotNull(r, 'pageIfRIsNull')}.xhtml" />
Not sure. An alternative though would be to use a template with a ui:insert and then direct to case1 or case2 which use ui:define programatically.
It is possible to have selective use of ui:include with other JSF components. Example:
<h:panelGroup rendered="#{!menuMBean.passwordResetRequired}">
<ui:include src="homeNormal.xhtml" />
</h:panelGroup>
Inside a nested foreach, accessing the same variable is returning different values. This happens when the page is reloaded, not on first load.
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
(...)
xmlns:c="http://java.sun.com/jstl/core"
xmlns:h="http://java.sun.com/jsf/html">
(...)
<c:forEach items="#{controller.availableTransitions}" var="transition">
<c:forEach items="#{transition.availableTransitions}" var="transitionItem">
<h:outputText value="1_#{transitionItem.name} 2_#{transitionItem.name}" />
3_#{transitionItem.name} 4_#{transitionItem.name}
</c:forEach>
</c:forEach>
</ui:composition>
After page reload, transitionItem.Name returns the correct value for 3 and 4, and different values for 1 and 2. Maybe a JSF-JSTL integration problem?
I see that you are using Facelets.
Maybe you can try to replace your <c:forEach> by <ui:repeat>...
The code will then become:
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
(...)
xmlns:c="http://java.sun.com/jstl/core"
xmlns:h="http://java.sun.com/jsf/html">
(...)
<ui:repeat value="#{controller.availableTransitions}" var="transition">
<ui:repeat value="#{transition.availableTransitions}" var="transitionItem">
<h:outputText value="1_#{transitionItem.name} 2_#{transitionItem.name}" />
3_#{transitionItem.name} 4_#{transitionItem.name}
</ui:repeat>
</ui:repeat>
</ui:composition>
In general, I try to use ui:repeat most of the time. When I was having c:set issues, I found this blog, which was very helpful and may apply in your case also.
https://rogerkeays.com/jsf-c-foreach-vs-ui-repeat
Found a workaround, by getting rid of the inner forEach loop, thus returning a linear list from the controller.