I'm trying to show a 2D array as a table in a page. I know this sounds as if I was reinventing the wheel but the object I have to handle is a 2D array of a custom type that must also be rendered in a particular way (always the same).
The tricky part is that this 2D array can have null values in some of its indexes. In those cases, a particular "unavailable" cell must be rendered. The table structure (yes... <table> <tr> and <td>) was already defined by the Design Team and accepted by the client.
I've tried to use <c:forEach/> but got in trouble trying to make it work, because of the order in which JSTL and JSF tags are managed. When the JSF tags are handled there are some issues that include outdated values and missing components.
The array (attribute of a #ViewScoped bean) is always null when <c:forEach/> is invoked, even when I force the creation of the array:
public MyObject[][] getMatrix() {
if(loadedMatrix == null)
initializeMatrix();
return loadedMatrix.getTable();
}
The initializeMatrix() method obtains the corresponding data from the database and invokes the logic that creates the 2D array, making loadedMatrix reference it once it's created (all of this works, no exceptions or errors). When initializeMatrix finishes, loadedMatrix is still null.
I went for the nested <c:forEach/> option because I need to manage the indexes of the table to know what to render (if the object is null, has an availability flag set to false or if it can be rendered normally), but as for now I think the safest solution is to create a custom component.
My question is: What alternatives do I have to render the content of a 2D array as a table while being aware of the indexes I'm rendering?.
Thanks in advance
You can use ui:repeat instead of c:forEach, I tried myself with a sample and it worked for me. c:foreach is a tag-handler and please take a look at the following post of #BalusC to learn more about why you should not use tag handlers with view-scoped beans.
JSTL in JSF2 Facelets... makes sense?
<table>
<ui:repeat value="#{sampleBean.twodarray}" var="firstLevel" varStatus=#{vs}>
<tr>
<ui:repeat value="#{firstLevel}" var="secondLevel" rendered="#{!empty firstLevel}">
<td>#{vs.index} - #{secondLevel}</td>
</ui:repeat>
<h:panelGroup rendered="#{empty firstLevel}">
<td colspan="3">empty</td>
</h:panelGroup>
</tr>
</ui:repeat>
</table>
Related
I'm pretty new to JSF / Facelets and trying to understand where best placed to put logic when deciding whether to render content within a ui:repeat component.
In my backing bean I have a list of messages each one containing a date. I want to print all messages but only render the date when it changes.
Currently I have something like below where the backing bean getter method determines whether or not to render the message date based on logic and setting state within the backing bean.
<ui:repeat value="#{bean.orderedMessages}" var="message" varStatus="messageLoop">
<h:panelGroup rendered="#{bean.isDateRendered(messageLoop.index)}">
#{message.formattedDate}
</h:panelGroup>
// some other stuff for each message
</ui:repeat>
This is buggy and horrible since JSF calls the getter method many times before actually rendering which adds further complexity to the getter method, the code has nothing to do with the anything other than whether something should be rendered or not which should really be in the UI and the getter method has a whole bunch of nasty logic in it whereas I would prefer a simple property to be returned.
I believe the UI has to somehow store the state of the last rendered date and compare that against the one it is about to render but I'm not sure whether that's correct.
I don't believe this is an uncommon problem. Does anyone know if there is a more elegant solution to solving this?
From the thoughts raised by BalusC in the comments of the question, I have implemented the following solution.
My Message object from the entity/domain layer is returned to the backing bean when the instance is created #PostConstuct, I have created another model in the UI layer called UIAugmentedMessage which takes the Message in the constructor as well as a boolean property for isRendered and a String for the formatted date.
The backing bean builds the list of UIAugmentedMessage objects when the Message list is returned from the server just after the bean is constructed and this contains the correct state for the isRendered and formattedDate fields.
The UI layer is now simplified as follows
<ui:repeat value="#{bean.orderedMessages}" var="message">
<h:panelGroup rendered="#{message.rendered}">
#{message.formattedDate}
</h:panelGroup>
</ui:repeat>
Using expression language, how can I access a component that is bound and repeated in a datatable?
<h:dataTable value="#{bean.items}" var="item" id="table">
<h:column>
<h:inputText value="#{item.name}" id="name" binding="#{mybinding}"/>
</h:column>
</h:dataTable>
Should I give each binding a generated name with a concatenation of a literal and the row index, for instance ('mybinding_1', 'mybinding_2', and so forth), and if so, how?
Or instead is there a way to get a particuliar element with #{mybinding} plus some kind of brace notation ([])?
There's a misconception going here. There are definitely not physically multiple <h:inputText> components in the component tree. There's only one component whose HTML representation is generated multiple times depending on the current state of the parent table component. You can confirm this by walking through the component tree starting at FacesContext#getViewRoot(), you'll ultimately find only one <h:inputText> component.
So, binding="#{mybinding}" is perfectly fine.
If you're having problems with it, it's caused elsewhere and needs to be solved differently. Only and only if you're using a view build time tag to generate physically multiple components in a loop, such as JSTL <c:forEach>, then there would indeed be physically multiple <h:inputText> components in the component tree and you'd need to bind them to an array or map. But this is currently clearly not the case.
Is it possible to use OmniFaces <o:validateAllOrNone> (which is pretty cool ;)) within an <ui:repeat> or <h:dataTable>?
I need a table with each row having an input field column. You could either fill in none of these values or all of them.
If I put the <o:validateAllOrNone> within the <ui:repeat> or <h:dataTable> and use the id of the input field in components attribute, then the validator also gets triggered, if all fields are empty.
No, that's not possible. The components attribute must refer physically multiple components, not a single component which is rendered multiple times. It can however be used on physically multiple components which are rendered during same iteration round. The <o:validateXxx> multi field validator is not designed to reference a single component which is rendered multiple times. The only OmniFaces validator which does that is <o:validateUniqueColumn>.
If you want to use the <o:validateXxx> multi field validator on dynamic inputs based on a collection, then your best bet is to use JSTL <c:forEach>. It will build physically multiple components.
E.g.
<c:forEach items="#{bean.items}" var="item" varStatus="loop">
<h:inputText id="input_#{loop.index}" value="#{item.value}" />
</c:forEach>
Assuming that there are 3 items, this will dynamically create JSF components with IDs of input_0, input_1 and input_2. Then you can just use <o:validateXxx> as follows (put it outside the loop!)
<o:validateAllOrNone components="input_0 input_1 input_2" />
You can replace the hardcoded string in the above example by an EL expression which returns the desired space separated string of component IDs from a backing bean.
<o:validateAllOrNone components="#{bean.inputIds}" />
An alternative would be to create a <x:validateAllOrNoneColumn> yourself or to post an enhancement request at OmniFaces issue tracker. It would be not exactly trivial to alter the existing <o:validateAllOrNone> that a completely separate component is desired.
Due to the problem with #ViewScoped, I have to use ui:repeat instead of c:foreach in my facelet. However I encounter this problem and I wonder if anyone can suggest a work-around.
<ui:repeat value="#{someBean.idCodes}"
var="led">
<h:outputText id="mailbox-#{led}"
value="#{someBean.getSomeValue(led)}" />
</ui:repeat>
The first problem is that the #{led} reference is the id attribute does NOT get evaluated. All the generated id attributes are simply the value "mailbox-". I need to find specific element instances with document.getElementById() so I need generated id attributes.
The other problem which I can't see a workaround for is that if I wrap the ui:repeat element inside an h:panelGrid element, I end up with a table with one cell in it, rather than one cell for each member of the idCodes collection.
When using c:foreach neither of these problems exist, but use of that tag causes other problems. Any suggestions?
Notice in the javadocs that the type of the id attribute is String, not ValueExpression. That's why your ${led} is not evaluated. You could try to wrap each one in a div or span and set the id of that element to your ${led} based expression.
There is a way to sort in the tag in 'desc'? And is it possible to sort by two columns?
I know that there is the possibility to pass the element already sorted in the controller but would be useful to have the chance also in the gsp tag.
Not optimal, but in a pinch you can sort the collection inline:
<g:each in="${[2,3,6,1,10].sort{a,b -> a < b ? -1 : 1}}">
${it}
</g:each>
In general I agree with mfloryan. Putting logic in the view is a slippery slope, but there's a time and a place for everything.
That is correct. The <g:each> tag does not support sorting and I don't think it should. Any logic (like setting sort order) should not live in the view itself.