ui:repeat in o:tree not working as expected - jsf-2

I'm using a omnifaces o:tree of "branches" where each branch has a "list of leafs" with the attribute "color" which should be editable in the tree.
- branch 0
- leaf 0 (color = "green")
- leaf 1 (color = "yellow")
- branch 1
- leaf 0 (color = "purple")
- branch 1_0
- leaf 1 (color = "red")
- leaf 2 (color = "orange")
- leaf 3 (color = "brown")
Adding/removing branches and adding leafs to any branch works fine and as expected. Also the rendering of any complex tree including all the list of leafes (displaying of the color attribute with the correct value) works like a charme.
But changing the color or removing a leaf (so whatever is within the ui:repeat) only works as expected for the last rendered branch.
The "color" input fields for the other leafes/branches don't work at all and the delete leaf link within the ui:repeat also doesn't work as for the leaf attribute in removeLeaf(...) always the corresponding leaf from the last rendered branch gets passed.
So in the above example clicking on delete for the yellow leaf from branch 0 will call removeLeaf(orange, branch 0) which will then oviously not delete anything as branch 0 has no orange leaf.
This is the code strapped down to the essential part - anything else can be provided if needed:
<h:form>
<o:tree value="#{treeBean.tree}" var="branchEntity" varNode="branchNode">
<o:treeNode>
<o:treeNodeItem>
<ui:repeat value="#{branchEntity.leafList}" var="leaf">
<h:panelGrid columns="2">
<p:inputText value="#{leaf.color}" />
<p:commandLink action="#{treeBean.removeLeaf(leaf, branchEntity)}" styleClass="ui-icon ui-icon-trash"
process="#form" update="#form" />
</h:panelGrid>
</ui:repeat>
<p:commandLink action="#{treeBean.addLeaf(branchEntity)}" styleClass="ui-icon ui-icon-plus"
process="#form" update="#form" />
<o:treeInsertChildren />
</o:treeNodeItem>
</o:treeNode>
</o:tree>
<p:commandButton id="save" value="Save" action="#{treeBean.save}" process="#form" update="#form" />
</h:form>
I know that there is some issue with nested ui:repeat but as I'm on Mojarra 2.1.19 I guess it's not the case here. And actually nesting two ui:repeats works fine if I replace o:tree by a ui:repeat which iterates over a list of branches. But then I obviously lost the tree functionality which I need. This I just tested to verify nested ui:repeats work properly.
This problem seems to be similar to another question but it's not exactly the same...
If I replace the the inner ui:repeat by a p:dataList (as suggested in some other answers to solve ui:repeat problems and also now in BalusC answer here) the removeLeaf() links will all work proper but still only color input fields of the last branch will be bound.
UPDATE: I now had a closer look when using the p:dataList. The POST content from the AJAX request which I got from my browsers developer tools looks good:
javax.faces.partial.ajax=true
javax.faces.source=hForm:save
javax.faces.partial.execute=hForm
javax.faces.partial.render=hForm
hForm:save=hForm:save
hForm=hForm
hForm:oTree:0:pDataList:0:color=green
hForm:oTree:0:pDataList:1:color=yellow
hForm:oTree:1:pDataList:0:color=purple
hForm:oTree:1_0:pDataList:0:color=red
hForm:oTree:1_0:pDataList:1:color=orange
hForm:oTree:1_0:pDataList:2:color=brown
javax.faces.ViewState=-6137230173999059936:-6718691551411872927
The treeBean.save() method is just logging the whole Tree.toString() to console which ends up like this:
[Branch [leafList=[Leaf [color=null], Leaf [color=null]]],
Branch [leafList=[Leaf [color=null]]]
[Branch [leafList=[Leaf [color=red], Leaf [color=orange], Leaf [color=brown]]]]]
If the color had some value != null before, the value remains the same - so it doesn't get overriden by null. I'm not yet deep enough into JSF to really know how to investigate where the information gets lost on it's way.
(I initially used Mojarra 2.1.19 on JBoss EAP 6.2 + Primefaces 5.2 + Omnifaces 2.1 but also experience exactly the same on Mojarra 2.2.12, TomEE, Primefaces 5.3 + Omnifaces 2.2. The complete Maven Eclipse sample project can be found here I added even one more simple String attribute (no List) directly to the Branch which works fine.)

This is indeed caused by a state management bug in <ui:repeat>. It didn't properly recognize itself being nested in another iterator and stubbornly clears out the state before being revisited during each iteration of the parent iterator. It only recognizes a parent UIRepeat or UIData, but that's it. The <o:tree> doesn't and can't extend from UIData, and the UIRepeat is unfortunately a implementation-specific class and thus can't be used publicly as long as you want to be independent from a specific JSF implementation (Mojarra, MyFaces, etc).
Your best bet is to replace <ui:repeat> by a real UIData component which has a much better state management implementation. I just tried it here and adding items works just fine for me with <h:dataTable> and <p:dataList>. Only removing items failed with <h:dataTable>, because it rendered wrong client IDs. It's not taking into account the container client ID. The <p:dataList> worked for me in both cases of adding and removing items. Still updating the server side model of any bound input fields in the list fails as discussed in detail in the comments of this answer.
Replacing the entire <ui:repeat> by a <p:dataList type="none"> (without type, it will render an <ul><li>) is the closest you can get.
<p:dataList type="none" value="#{branchEntity.leafList}" var="leaf">
...
</p:dataList>
World would have been much simpler if JSF offered a public UIRepeat-like (marker) interface which all those repeaters could just check for when they're about to manage the state.

Related

valuechangelistener getting called multiple time

I'm working on icefaces upgrade from 1.8 to 3.3 as well as jsf from 1.2 to 2.0.
I have used this link as reference for icefaces upgrade
I'm using ice:datatable in which I have column of checkbox which is grouped for single worker.Check picture which is of before upgrade.
ISSUE 1:
Now Problem faced after migration is that the valuechangelistener of ice:selectbooleancheckbox works fine when I check then uncheck the checkbox of a single worker. But when I do following steps:
Check first worker (This updates the Assigned door column ).
Then check other worker . This unchecks the previous checked worker and also unchecks currently checked worker.
To analyse if there is any issue in phase I used balus C blog to check phase of icefaces when it hits valuechangelistner . Then I found on my second check 2 or more valuechangelistner are called one for previous worker other for current worker(Sometimes twice) this all happens in same invoke application phase.
ISSUE 2:
ValueChangeListener gets called even after Clicking "OK" button or "CANCEL".
Fixed it by adding f:param in ice:commandButton and checking for the param in valuechangelistener method.
Tried:
To change all ice tags in the page to ace tags(Related to datatable i.e datable ,row and column).
Result : Same issue with ace valuechangelistener as well as distorted style. Maybe because I could not find ice:rowHeader equivalent in ace tags and also much more.
Changed only checkbox column to ace:column. Valuechangelistener worked fine but issue with "groupOn" attribute so changed it to "groupBy" and condition="group" still may be it did not work because I used ice:datatable which doesn't support it.
Then I tried to implement groupOn functionality manually. Using rowspan and rendering single checkbox for single worker . But rowspan didn't work. Also when I tried to render checkbox, its style is not exactly same as I need. Check this hattp://postimg.org/image/ih2mgoh7d/ remove 'a' from 'hattp'.
<ace:column groupBy ="#{item.workerName} + #{item.workerId}" rowspan="2"
styleClass= "alignSBChbx" >
<ice:setEventPhase events="ValueChangeEvent"
phase="INVOKE_APPLICATION">
<ice:selectBooleanCheckbox id="dwaCheckbox" value="#{item.select}"
style=" width:'#{appViewSettings.checkboxSize}';
height:'#{appViewSettings.checkboxSize}';"
valueChangeListener="#{dockWorkerAssignmentBean.doorAssignmentChange}"
partialSubmit="true" immediate="true"
disabled="#{!empty dockWorkerAssignmentBean.errorMap['dockWorkersAssignmentPopup:assignmentErrorField']}"
rendered="#{item.visible}">
<f:attribute name="workerIdSelected" value="#{item.workerId}" />
<f:attribute name="assignmentIdSelected" value="#{item.assignmentId}" />
</ice:selectBooleanCheckbox>
</ice:setEventPhase>
</ace:column>
backend
public final void doorAssignmentChange(final ValueChangeEvent event) {
System.out.println("inside door Assignment...");
final String workerIdSelected = (String) event.getComponent().getAttributes().get(
"workerIdSelected");
// unrelevant code
}
}

Ajax attached to Composite Component updates model but does not fire listener

I have a custom component location. I want that, when a change is done, the model is update so another component (an autocomplete) is to show only results related to the location value. Also, that component is rerendered to reset it.
I have a page with the following code (simplified):
<h:form id="ticketForm">
...
<loc:location id="locationId" <-- CUSTOM COMPONENT
hiddenFieldValue="#{ticketCtrl.ticket.location}"
visibleFieldValue="#{ticketCtrl.ticket.locationDescription}"
rendered="#{ticketCtrl.ticketModifiable}">
<f:ajax event="changeLocation" render=":ticketForm:gfhId" <-- AJAX CALL.
execute=":ticketForm:locationId" listener="#{ticketCtrl.locationListener}"/>
</loc:location>
...
</h:form>
When the value in the component is changed, the model is updated and :ticketForm:gfhId is rendered as needed, but the listener(which performs additional resets) is not executed.
Attaching the ajax to a simpler control results in the listener being executed; v.g.
<h:inputText id="contactId"
value="#{ticketCtrl.ticket.contactPerson}"
disabled="#{not ticketCtrl.ticketModifiable}">
<f:ajax event="change" render=":ticketForm:gfhId"
execute=":ticketForm:locationId" listener="#{ticketCtrl.locationListener}"/>
</h:inputText>
works perfectly.
I do not know if it may be related as how the changeLocation event is fired; inside my component I define it as
<composite:interface>
...
<composite:clientBehavior name="changeLocation" event="change" targets="visibleId"/>
</composite:interface>
with visibleId being a readonly text; when it is changed by javascript I fire the change event on it with JS.
function launchEvent(fieldName) {
if ("fireEvent" in fieldName) {
fieldName.fireEvent("onchange");
} else {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
fieldName.dispatchEvent(evt);
}
}
The info I find in other questions is about making ajax an inner part of the composite component, here I want ajax detached because I probably won't need it in other uses of the component.
I am using JBoss 6.1 wih Mojarra 2.1.9 and Richfaces 4.
Thanks in advance.
UPDATE:
Even after finding jarek.jpa's answer right, if someone wants to check the code it is here:
The composite component
http://pastebin.com/9wqMVfR5
The main form
http://pastebin.com/i39ys2D9
This may have to do with the "readonly" attribute set to true. Though you manage to send the change-event by hand (JS), the server-side processing of the listener may be dropped due to the readonly state. See e.g. jsf (richfaces) readonly input text validation.

Value not retained for action parameter when inside ui:repeat

Below is my Code:
<ui:repeat var="status" value="#{showUpdatedAction.statusUpdates}">
<h:panelGroup>
#{status.content}
<h:form>
<h:commandLink value="Like" action="#{statusAction.likeStatus(status.id,1)}" />
</h:form>
</h:panelGroup>
<ui:repeat>
#{status.content} shows correct values. When I print id of status using #{status.id}, it also gives correct value. But when I click the command link, value passed is always 0 for status.id.
Can someone tell me why this happens and how can I avoid this?
Thank you.
Edit 1
Interestingly, when instead of passing the parameter in function, I pass it using <f:param>, it works perfectly. Can anyone comment on that?
I think you should try using <c:forEach> instead of <ui:repeat>.
I can't tell you exactly, why status.id is 0 in your case but you can directly pass the whole status object in your EL expression. Like so:
<h:commandAction value="Like" action="#{statusAction.likeStatus(status)}" />
Then in your likeStatus you simply do a int statusId = status.getId() or similar and you have what you want.
As an addition: Using <c:forEach> should actually be just a fallback, because people say you shouldn't mix JSTL with JSF for whatsoever reasons.
Your code in the JSF page is just fine, just checked it... (generated the beans at my side too : showUpdatedAction, statusAction , and a simple class Status)
public void likeStatus(String id,long someVal){
System.out.println(id+"___"+someVal);
}
which prints the ids just fine
id1___1
id4___1
Maybe its something to do with the type of the id or something with your beans?

How to group Radio Buttons in h:datatable jsf2.0

I have a column of radio buttons in h:datatable in JSF2 but I can not find a way to group them. Meaning all of them can be selected at the same time where as whole point of radio button is so that only one can be selected at any given time. I am sure there will be standard way of doing it.
I am using myfaces. Can use richfaces if really needed to.
Can any one help with this.
When using standard JSF <h:selectOneRadio> component inside a <h:dataTable> you'll need to bring a shot of JavaScript into the game which unchecks all other radio buttons in the same column when one is checked.
<h:column>
<h:selectOneRadio onclick="uncheckOthers(this);">
</h:column>
with
function uncheckOthers(radio) {
var name = radio.name.substring(radio.name.lastIndexOf(':'));
var elements = radio.form.elements;
for (var i = 0; i < elements.length; i++) {
if (elements[i].name.substring(elements[i].name.lastIndexOf(':')) == name) {
elements[i].checked = false;
}
}
radio.checked = true;
}
First, it's ludicrous that something this simple in HTML or JSP is so hard to do in JSF. These added layers should be making life easier, not harder. But it seems to me they just make life harder. I used to like them. Now I am a Java Luddite, advocating now in favor of simpler approaches that include less in the way of "new cool-sounding 'technologies'" and more in the way of "stuff that just plain works without having to struggle all week to do something that used to take just 5 minutes". Anyway, I have tried the past 3 working days to get the simple functionality in JSF to work that causes other radio buttons to deselect and leave the user-selected one unchanged. I tried several approaches including a custom tag library ("overkill", defined) and a number of mixing and matching of stuff that only produced a lot of stacktraces. I finally fell back on Javascript and accepted that creating a reliably generalized solution was not likely. I have read similar complaints elsewhere. OK, my gripe session is over. Here's what I did to slay my particular JSF-radio-button dragon:
On the .xhtml page:
<ui:composition template="../template.xhtml"
...
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:a4j="https://ajax4jsf.dev.java.net/ajax"
...
>
<a4j:loadScript src="/resources/scripts/custom.js" />
...
<ui:param name="pageBean" value="#{recordsBean}" />
...
<h:dataTable id="tblMine"
value="#{pageBean.records}"
var="item"
border="1" celpadding="0" cellspacing="0"
style="font-size=10px;text-align: center;"
>
<h:column>
<f:facet name="header">
<h:panelGroup><h:panelGrid cellspacing="0" cellpadding="0" border="0"
id="rowSelect" columns="1" width="100%"><h:outputText
value="Select" /></h:panelGrid></h:panelGroup>
</f:facet>
<h:selectOneRadio id="userselectedrecord"
onclick="javascript:clearOtherRadioButtons(event);" >
<f:selectItem itemValue="#{item.recordid}"
itemLabel="" /> <!-- I set itemLabel to "" because in my case,
I don't want any text next to the radio btns -->
</h:selectOneRadio>
</h:column>
...
... and the rest of the page ...
In the custom.js file, I have:
function clearOtherRadioButtons(evnt)
{
// Have to try 'currentTarget', then 'srcElement'. Issue is Firefox
// supports 'currentTarget' but IE does not. IE supports 'srcElement'.
var btn = (evnt.currentTarget) ? evnt.currentTarget : evnt.srcElement;
var matchindexlocn = btn.id.indexOf('userselectedrecord');
var btnidstarttxt = btn.id.substring(0,
matchindexlocn + 18 ); // 'userselectedrecord' length = 18
var elementid = '';
var i = 0;
var ele = null;
var allElements = document.getElementsByTagName('*');
for(i = 0; i < allElements.length; i++)
{
elementid = allElements[i].id;
if ( (elementid.indexOf(btnidstarttxt) == -1) &&
(elementid.indexOf('_') > -1) )
{
try
{
ele = document.getElementById(elementid);
ele.checked = false;
} catch (e) {}
}
}
} // function clearOtherRadioButtons(evnt)
That's it. By way of some explanation re that if() in the Javascript:
elementid.indexOf(btnidstarttxt) == -1
--> This narrows the elements down to those that do not start with the JSF-added object ID text that is associated with the button group associated with the passed-in event. We do not want to unselect the button in the button group whose event was passed into the function.
elementid.indexOf('_') > -1
--> This narrows the elements down further to only actual buttons since JSF adds underscores to the button element IDs.
The reason the code that sets checked = false is in a try..catch block is just in case something goes wrong with any presumptions around the logic and the circumstance is not handled. If you know the environment and can control the element ids, you should be fine. However on the off chance that something changes due to, let's say for example, the app's host server getting some new version JSF library installed that overrides your app's classloading policy (in a work environment, these things as we know can happen beyond our control) that now assigns these element ids differently, the app won't break with a sudden nasty Javascript error or worse yet, the radio button selection just stops working. Worst I guess that can happen is that multiple radio btns can be selected at the same time and the user can submit them. I suppose a good precaution to take is to add yet more Javascript code to validate that only one option is being submitted by the user, or validate the user's submission server-side.
See, way too much work just to manage radio button state.

Grails: checkbox not being set back to false

I am developing a Grails (1.0.4) app where I want to edit a collection of collections on a single page in a grid view. I got it to work quite well depending only on the indexed parameter handling of Spring MVC, except for one thing:
boolean (or, for that matter, Boolean) values in the grid can be set via checkbox, but not unset, i.e. when I check the checkbox and update, the value is set to true, but afterwards when I edit again, uncheck the checkbox and update, it remains true.
This is the GSP code of the checkbox:
<g:checkBox name="tage[${indexTag}].zuweisungen[${indexMitarb}].fixiert" value="${z.fixiert}" />
And this is the HTML that is generated:
<input type="hidden" name="tage[0].zuweisungen[0]._fixiert" />
<input type="checkbox" name="tage[0].zuweisungen[0].fixiert" checked="checked" id="tage[0].zuweisungen[0].fixiert" />
I've found a Grails bug that describes exactly this effect, but it's marked as fixed in 1.0.2, and the problem mechanism described there (underscore in hidden field name is put in the wrong place) is not present in my case.
Any ideas what could be the reason?
This is the solution a guy named Julius Huang proposed on the grails-user mailing list. It's reusable but relies on JavaScript to populate a hidden field with the "false" response for an unchecked checkbox that HTML unfortunately does not send.
I hack GSP to send "false" when
uncheck the box (true -> false) with
custom TagLib.
By default checkBox send nothing when
uncheck, so I use the checkBox as
event handler but send hidden field
instead.
"params" in Controller can handle
"false" -> "true" without any
modification. eg. Everything remain
same in Controller.
The Custom Tag Usage in GSP (sample usedfunc_F is "true"),
<jh:checkBox name="surveyList[${i}].usedfunc_F" value="${survey.usedfunc_F}"></jh:checkBox>
Here is what the Tag generate,
<input type="hidden" name="surveyList[#{i}].usedfunc_F" id="surveyList[#{i}].usedfunc_F" value="false" />
<input type="checkbox" onclick="jhtoggle('surveyList[#{i}].usedfunc_F')" checked="checked" />
The Javascript
<script type="text/javascript">
function jhtoggle(obj) {
var jht = document.getElementById(obj);
jht.value = (jht.value !='true' ? 'true' : 'false');
}
</script>
This is my own solution, basically a workaround that manually does what the grails data binding should be doing (but doesn't):
Map<String,String> checkboxes = params.findAll{def i = it.key.endsWith("._fixiert")} // all checkboxes
checkboxes.each{
String key = it.key.substring(0, it.key.indexOf("._fixiert"))
int tagIdx = Integer.parseInt(key.substring(key.indexOf('[')+1, key.indexOf(']')))
int zuwIdx = Integer.parseInt(key.substring(key.lastIndexOf('[')+1, key.lastIndexOf(']')))
if(params.get(key+".fixiert"))
{
dienstplanInstance.tage[tagIdx].zuweisungen[zuwIdx].fixiert = true
}
else
{
dienstplanInstance.tage[tagIdx].zuweisungen[zuwIdx].fixiert = false
}
}
Works, requires no change in grails itself, but isn't reusable (probably could be made so with some extra work).
I think that the simplest workaround would be to attach a debugger and see why Grails is failing to populate the value. Considering Grails is open source you'll be able to access the source code and once you figure out the solution for it you can patch your version.
I have also found this other bug GRAILS-2861 which mentions the issue related to binding to booleans (see Marc's comment in the thread). I guess that is exactly the problem you are describing.
I would create a small sample app that demonstrates the problem and attach it to the Grails bug (or create a new one). Someone here may be able to debug your sample app or you'll have shown the bug isn't really fixed.
Try this out, set the logs to DEBUG, frist try the first 3 if they don't show the problem up, flip them all to DEBUG:
codehaus.groovy.grails.web.servlet="error" // controllers
codehaus.groovy.grails.web.pages="error" // GSP
codehaus.groovy.grails.web.sitemesh="error" // layouts
codehaus.groovy.grails."web.mapping.filter"="error" // URL mapping
codehaus.groovy.grails."web.mapping"="error" // URL mapping
codehaus.groovy.grails.commons="info" // core / classloading
codehaus.groovy.grails.plugins="error" // plugins
codehaus.groovy.grails.orm.hibernate="error" // hibernate integration
This should allow you to see exactly when and how the parameters setting is failing and probably figure out a work around.

Resources