Updating form component in ui:repeat - jsf

I have an ajax call that updates a value on my dto and should update the form to stop rendering a certain component. When mode of trial is changed, it should update a java value (which is working correctly and also stop the rendering on the time limit field.
If update="#form" is set, the rendering works correctly however all values are cleared from the form as well which is not acceptable. If I try to target the update at the id of the field, it doesn't work, I assume this has something to do with the repeat. The id of the component when inspecting the web page is 'offencesForm:offences:0:statutoryTimeLimit' however updating that does not work either.
Any help updating the rendering of the component without clearing the values of the rest of the form, would be great.
The dropdown that is change to trigger the method which is hit.
<ui:repeat id="offences" value="#{casefileDto.offences}" var="_offence" varStatus="n">
<!-- Update Time Limit value in dto & update component to render statutoryTimeLimit by modeOfTrial value -->
<ecis:field label="Mode of trial" starred="true" help="#{label['casefile.help.modeOfTrial']}" helpLayout="hover">
<p:selectOneMenu value="#{_offence.modeOfTrial}"
converter="omnifaces.SelectItemsIndexConverter"
required="${param['skipValidation'] == null}" requiredMessage="Please select the mode of trial">
<f:selectItem noSelectionOption="true" itemLabel="" itemValue="#{null}"/>
<f:selectItems value="#{conditionCache.modeOfTrialConditions}" />
<p:ajax event="change" update="statutoryTimeLimit" listener="#{_offence.onTrialModeSelect()}"/>
</p:selectOneMenu>
</ecis:field>
<!-- Render based off modeOfTrial values -->
<ecis:field id="statutoryTimeLimit" label="Statutory time limit expires on" rendered="#{supplementary and _offence.timeLimitRequired()}"
help="Calculated from 'Date of offence' plus six months." helpLayout="hover">
<h:outputText value="#{_offence.statutoryTimeLimit}" >
<f:convertDateTime pattern="#{cvCache.dateFormat}" />
</h:outputText>
</ecis:field>
</ui:repeat>
Java code
public boolean timeLimitRequired() {
return modeOfTrial != null && modeOfTrial.getValue().contains("(SST)");
}
public void onTrialModeSelect() {
if (getModeOfTrial().toString().equals("Not suitable for summary trial (NSST)")) {
setStatutoryTimeLimit(null);
} else {
onOffenceDateSelect();
}
}
Any help would be much appreciated.

Related

Ajax not function on first submit, after page refresh only result will refresh

I am using Java EJB with JSF.
What I doing is just a simple search result from database and display it on a table using JSF with ajax. But why when first submitting, the result is not appear. And second time only the result will appear.
Below is my JSF page code
<h:form id="wholeForm">
<p:panelGrid id="resultTable" styleClass="result_table1" >
<p:row>
<p:column>
<div align="center" id="font-size1">
<h:selectOneRadio value="#{scanResult.typeOfScan}">
<f:selectItem itemValue="Carrier" itemLabel="Carrier"></f:selectItem>
<f:selectItem itemValue="Slot" itemLabel="Slot"></f:selectItem>
<f:selectItem itemValue="HD" itemLabel="HD"></f:selectItem>
<p:ajax event="change"></p:ajax>
</h:selectOneRadio>
Scan: <p:inputText styleClass="searchField" id="counter" value="#{scanResult.serialNumber}" a:autofocus="true">
<p:ajax event="keydown" update="wholeForm" onstart="if (event.keyCode != 13) { return false;}" listener="#{scanResult.checkResult()}" />
</p:inputText>
<br/><br/>
</div>
</p:column>
</p:row>
</p:panelGrid>
<p:panelGrid id="resultTable1" styleClass="result_table1" rendered="#{not empty scanResult.scanResultCarrier}" >
<c:forEach items="#{scanResult.scanResultCarrier}" var="result" >
<!-- ..do something and call out result -->
</p:column>
</p:row>
</c:forEach>
</p:panelGrid>
</h:form>
And my managed bean is as below
#Named(value = "scanResult")
#SessionScoped
public class scanResult implements Serializable {
//Some code here
public void checkResult() {
scanResultCarrier = new ArrayList<>();
scanResultSlot = new ArrayList<>();
System.out.println("checking");
if (typeOfScan.equals("Carrier")) {
System.out.println("Serial " + serialNumber);
scanResultCarrier = scanresultcarrierFacade.searchResultCarrier(serialNumber);
System.out.println(scanResultCarrier.size());
} else if (typeOfScan.equals("Slot")) {
scanResultSlot = scanresultslotFacade.searchResultSlot(serialNumber);
} else if (typeOfScan.equals("HD")) {
} else {
}
serialNumber = "";
}
}
I use System.out.println(lsitofresult.size()); to print out my result and the result is not blank. Mean that I successful retrieve my result from database.
But my result table not able to show out after I click on enter.
Then I notice that my url is as below
http://localhost:8080/outgoingScanSystem-war/faces/index.xhtml;jsessionid=8b6cefa932ff60984607ee38ec13
And after I refresh my page, the result will appear again.
And my url change to :
http://localhost:8080/outgoingScanSystem-war/faces/index.xhtml
May I know why? Is it related to URL? I have no idea where should I start my troubleshoot. Anyone can give me some guideline?
I see some problems here. First of all, you're using the session scope while your managed bean should be view scoped (there's no reason to use the session here, see the link below). That's the cause why your results are getting displayed when you refresh the page.
Second, I'd rather put the search results out from the form. I see no reason for them to be in the same form of the search itself. Then, when performing a searh you should only update the result list:
<p:ajax event="keydown" update="resultTable1" onstart="if (event.keyCode != 13) { return false;}" listener="#{scanResult.checkResult()}" />
Third, as you're using Primefaces, I encourage you to take a look to its p:remoteCommand tool in order to perform ajax-based calls to the beans from your JS code directly. This way you should avoid your second problem, which seems to be that you're preventing the standard form sending on the ajax start event (which might not even get called). You could do something like this:
<p:remoteCommand name="search" update="resultTable1" actionListener="#{scanResult.checkResult}" />
<p:inputText styleClass="searchField" id="counter" value="#{scanResult.serialNumber}"
a:autofocus="true" onkeypress="if (event.keyCode != 13) {search(); return false;}">
<!-- Update the input text value in the bean for each pressed key -->
<p:ajax event="keydown" />
</p:inputText>
See also:
How to choose the right bean scope?
Jsf calling bean method from input text when pressing enter

How to make selected h:selectOneRadio of h:dataTable remain selected on postback?

In normal circumstances like this:
<h:form>
<h:selectOneRadio value="#{bean.gender}">
<f:selectItem itemValue="Male" itemLabel="Male" />
<f:selectItem itemValue="Female" itemLabel="Female" />
<f:selectItem itemValue="Other" itemLabel="Other" />
</h:selectOneRadio>
<h:commandButton value="Submit" action="#{bean.action}" />
</h:form>
Selecting one radio button disselects the other & the radio button will be remain selected on the postback. (when the same view is rendered)
However, when we're dealing with an iterating component like <h:dataTable>, the selection is lost.
Consider the snippet:
<h:form id="hashMapFormId">
<b>HASH MAP:</b>
<h:dataTable value="#{playersBean.dataHashMap.entrySet()}" var="t" border="1">
<h:column>
<f:facet name="header">Select</f:facet>
<h:selectOneRadio id="radiosId" onclick="deselectRadios(this.id);"
value="#{playersBean.select}">
<f:selectItem itemValue="null"/>
</h:selectOneRadio>
</h:column>
</h:dataTable>
<h:commandButton value="Show Hash Map Selection"
action="#{playersBean.showSelectedPlayer()}" />
</h:form>
With disselecting the other radio buttons when one radio button is selected being implemented by simple JavaScript-
function deselectRadios(id) {
var f = document.getElementById("hashMapFormId");
for (var i = 0; i < f.length; i++)
{
var e = f.elements[i];
var eid = e.id;
if (eid.indexOf("radiosId") !== -1) {
if (eid.indexOf(id) === -1) {
e.checked = false;
} else {
e.checked = true;
}
}
}
}
Fire the GET request:
Select a radio button:
Now press the submit button, response:
You see that the radio button gets dis selected on postback. How to solve this shortcoming?
I know it very well that this is due to this component attribute itemValue being null:
<f:selectItem itemValue="null"/>
This trick is a leftover from JSF 1.x / 2.0/2.1 when it wasn't possible to use a <h:selectOneRadio> for single row selection in a <h:dataTable>. This trick originated in my 10 year old blog article Using Datatables - Select row by radio button.
The root problem is, HTML radio buttons are grouped based on their name attribute, so the webbrowser knows which others to unselect when one is selected. But JSF generates by design a different one for each <h:dataTable> item, with the row index inlined and therefore they can't be grouped and hence the JavaScript based workaround.
Since JSF 2.2, with the new passthrough elements and attributes feature, it's however possible to force the name attribute to the value of your choice and capture the selected item via a helper <h:inputHidden>. This is fleshed out in another blog article of me, from previous year: Custom layout with h:selectOneRadio in JSF 2.2. The article uses <ui:repeat> as an example, this can be rewritten to <h:dataTable> as below.
<h:form>
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<input type="radio" jsf:id="item" a:name="#{hiddenItem.clientId}"
value="#{item.id}" a:checked="#{item.id eq bean.selectedItemId ? 'checked' : null}" />
</h:column>
<h:column>#{item.id}</h:column>
<h:column>#{item.name}</h:column>
</h:dataTable>
<h:inputHidden id="selectedItem" binding="#{hiddenItem}" value="#{bean.selectedItemId}"
rendered="#{facesContext.currentPhaseId.ordinal ne 6}" />
<h:commandButton id="submit" value="Submit" action="#{bean.submit}" />
</h:form>
#Named
#ViewScoped
public class Bean implements Serializable {
private List<Item> items;
private Long selectedItemId;
// ...
public void submit() {
System.out.println("Selected item ID: " + selectedItemId);
}
// ...
}
And yes, the selected radio button remains selected on postback this way. You can also pass whole entities, this only requires a converter on the <h:inputHidden>.

Dynamic reload of items in select many component

I want to achieve sth similar to http://www.primefaces.org/showcase/ui/pprSelect.jsf but i need a collection of double-combos, so i wrapped it in ui:repeat
I need on the backend check which element from collection of double-combos was changed and what I need to reload. For communication is used p:ajax as in the example, but AjaxBehaviorEvent not bring me any idea of index of element ( i mean index of double-combos element generated by ui:repeat)
My client code, the idea is to update bean:selectedIndex everytime when a ajax event will be raised ( on change value of selectOneMenu ), and value of bean:selectedIndex will be set as index of changed selectOneMenu
private List<State> productStates
private int selectedIndex;
private List<Group> groups;
private Map<Integer, Collection<Device>> availableDevicesMap;
<ui:repeat var="state" value="#{bean.productStates}" varStatus="iter">
<p:selectOneMenu id="devGroup" value="#{state.group}">
<f:selectItems value="#{bean.groups}" />
<p:ajax update="refreshable" process="devGroup, #this" listener="#{bean.refreshDevicesForState}" >
<f:setPropertyActionListener target="#{bean.selectedIndex}" value="#{iter.index}"/>
</p:ajax>
</p:selectOneMenu>
<!-- THIS WILL BE UPDATED -->
<h:panelGroup id="refreshable">
<p:selectManyButton id="devices" value="#{state.devices}" >
<f:selectItems value="#{bean.availableDevicesMap[status.index]}" />
</p:selectManyButton>
</h:panelGroup>
</ui:repeat>
Backend which doesn't work as expected. setPropertyActionListener is not invoked and selectOneMenu component hasn't got selected group as value
public refreshDevicesForState(AjaxBehaviorEvent e) {
SelectOneMenu menu = (SelectOneMenu)e.getComponent();
// this value is not as selected on frontend
Group group = (Group)menu.getValue();
// selectedIndex will not be set, so I assume that setPropertyActionListener didn't invoked
availableDevicesMap.put(selectedIndex, group.getDevices());
}
I tried also with code below which works but in my opinion it is ugly
// id will be grandpaId:parentId:index:myId
String selectedIndex = IdHelper.getIdPart(e.getComponent().getClientId(), -2);
State state = productStates.get(Integer.parseInt(selectedIndex));
I am using latest primefaces on glassfish and Mojarra as jsf reference implementation
Thank you for any help
In more general sense:
I have list of objects on backed bean, lets say Cars
List<Car> cars
on frontent I iterate over them and create select brand and select model combos for every car. When user select brand for i.e 4th car i want to get to know on backend that 4th car will be changed and i will reload list of available model for this one car
<ui:repeat var="state" value="#{bean.cars}" >
<p:selectOneMenu id="brands"/>// select brand
<p:selectOneMenu "models"/>// show available models depends on selected brand
</ui:repeat>
How to handle it correct in the JSF world ?
My first suggestion is to use converter for Group.
SelectOneMenu cannot set custom class, only with the help of a converter. (an example is at autocomplete: http://www.primefaces.org/showcase/ui/autoCompletePojo.jsf)
Second, in your bean handler, productStates variable contains already the selected values (of selectOneMenus). You can use it easier, than access it from the event.
If the values of selectOneMenus depend on State, you have to modify this:
<f:selectItems value="#{bean.groups}" />
to be able to express which group values should be displayed.
If you want debug it (without eclipse debugging), you can use messages, for example:
add this to xhtml:
<p:growl id="msgs" showDetail="true"/>
and in bean:
public refreshDevicesForState(AjaxBehaviorEvent e) {
...
FacesMessage msg = new FacesMessage("Selected", "any debug info" + productStates.get(0).getGroup());
FacesContext.getCurrentInstance().addMessage(null, msg);
}
I modified my answer, according to your mods. I would do it this way:
xhtml:
<ui:repeat var="state" value="#{bean.productStates}" varStatus="iter">
<p:selectOneMenu id="devGroup#{iter.index}" value="#{state.group}"
valueChangeListener="#{bean.updateSubProperty}" immediate="true">
<f:selectItems value="#{bean.groups}" />
<f:attribute name="index" value="#{iter.index}" />
</p:selectOneMenu>
<p:selectOneMenu id="subDevGroup#{iter.index}">
...
</p:selectOneMenu>
</ui:repeat>
bean:
public void updateSubProperty(ValueChangeEvent vce) {
String index = vce.getComponent().getAttributes().get("index").toString();
int i = Integer.parseInt(index); //this is the index of the selected selectOneMenu
///...
//update sub selectOneMenu
RequestContext.getCurrentInstance().update("subDevGroup" + index);
}

rerender component based on selectOneMenu onchange

Maybe someone could clear this up for me.
I have an h:selectOneMenu that should do something as soon as a user selects something from the list and then rerender a link based on the results.
<h:selectOneMenu value="#{backingBean.itemSelected}" valueChangeListener="#{backingBean.methodThatDoesStuff}" onchange="submit()" >
<s:selectItems var="_items" value="#{backingBean.itemList}"
label="#{_items.Name}" />
<s:convertEntity />
</h:selectOneMenu>
My assumption was that when the user selected an item it would populate a field in the backing bean and then run the methodThatDoesStuff.
But that isn't happening. backingBean.itemSelected isn't set until AFTER the method is called. I can get the event and use it directly in the method:
public void methodThatDoesStuff(ValueChangeEvent event){
Item item = (Item)event.getNewValue();
.... do stuff, set a flag to display link later
}
This works.
But this leaves my aComponent set to the previous value, so the 1st time it will be null, then will change the settings based on the last itemSelected(so I'm always one behind:
public void methodThatDoesStuff(){
Item item = this.itemSelected;
.... do stuff, set a flag to display link later
}
I've tried:
<h:selectOneMenu value="#{backingBean.itemSelected}" >
<s:selectItems var="_items" value="#{backingBean.itemList}" label="#{_items.Name}" />
<a:support event="onchange" ajaxSingle="true" reRender="aComponent" action="#{backingBean.methodThatDoesStuff}" />
<s:convertEntity />
</h:selectOneMenu>
But that doesn't work whether I use ValueChangeEvent or not.
Is using the event object the only way to get the currently selected item?
This is JSF 1.2 and Seam 2
Set attribute immediate="true" on your selectOneMenu.

JSF dataTable with selectOneListbox

I have a dataTable that lists some objects and I want to set a property for those objects using a selectOneListbox. This is my dataTable
<h:dataTable value="#{someHandler.entities}"
binding="#{someHandler.dataTable}" var="entitiy">
<h:column>
<f:facet name="header">
<t:outputText value="Level" />
</f:facet>
<h:selectOneListbox id="level" value="#{entitiy.level}" size="1"
valueChangeListener="#{someHandler.changeLevel}"
onchange="submit()">
<f:selectItem itemValue="-" itemLabel="-" />
<f:selectItem itemValue="ALL" itemLabel="ALL" />
(and so on)
</h:selectOneListbox>
</h:column>
<h:column>
<f:facet name="header">
<t:outputText value="Name" />
</f:facet>
<h:outputText value="#{entitiy.name}" />
</h:column>
</h:dataTable>
The valueChangeListener looks like this:
public void changeLevel(ValueChangeEvent event) {
String newLevel = (String) event.getNewValue();
Logger logger = (Logger) dataTable.getRowData();
logger.setLevel(Level.toLevel(newLevel));
}
(dataTable is an HtmlDataTable object.)
However, the event object is always the same - no matter which row the selectOneListbox was in. (It seems always the logger in the first row). The Logger object I get is also not the one I want.
Any ideas? Thank you!
And anothers questions? Is the entitiy.setLevel() method called even though I have a valueChangeListener? I use entitiy.level because I want to show the chosen level as a default for those entity.
Thank you!
There are two potential problems here:
First, the onchange="submit()" submits the entire form. The valueChangeListener will be invoked on all input elements of which the submitted value differs from the initial value.
You need to preset the value behind #{entitiy.level} with the same value as the default menu option. E.g. in the constructor.
public Entity() {
level = "-";
}
Or, better, make the default value null.
<f:selectItem itemValue="#{null}" itemLabel="-" />
so that the valueChangeListener won't be invoked when the particular menu is not changed.
Or, when you are already on JSF 2.x (please always mention exact JSF impl/version in your JSF questions), you can use <f:ajax> tag for this without the need for a valueChangeListener with a hacky onchange="submit()".
Second, you need to ensure that the datatable value #{someHandler.entities} returns exactly the same list during the submit as it was during the initial request. So, do the data loading in the bean (post)constructor. In JSF 2.x you'd like to put the bean in the view scope as well.
Unrelated to the concrete problem, you can also just use <h:selectOneMenu> instead of a <h:selectOneListbox size="1">.

Resources