Dynamic reload of items in select many component - jsf

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);
}

Related

How to pass the selected values from selectcheckBoxMenu to my bean?

I'm using JSF 2.2.8 and primefaces 6.0, and i have a selectCheckBoxMenu i want to retrieve the selected values in my bean.
The selectCheckboxMenu is filled from the database but when i select the attributes and I save nothing happens it does not call the save function
Here is my selectCheckBoxMenu
<p:outputLabel for="ressource" value="Ressource"/>
<h:panelGroup >
<p:selectCheckboxMenu id="ressource" label="Ressource" value="#{affectationBean.selectedRessource}" multiple="true">
<f:selectItems value="#{affectationBean.ressources}" var="r" itemLabel="#{r.nom}" itemValue="r.idt_ressource" />
</p:selectCheckboxMenu>
</h:panelGroup>
<p:commandButton icon="ui-icon-save" actionListener="#{affectationBean.save}" value="Save" update="#affectation" ajax="false" style="display:inline-block;margin-top:5px"/>
Here is the the declaration of the selectedRessource and the actionListener save
private Long [] selectedRessource;
// Getters setters and Construct
public void save(){
for(int i=0 ;i<selectedRessource.length;i++){
system.out.println("id ===> " + selectedRessource[i]);
}
My suggestion would be:
First make sure everything is inside the h:form tag.
don't need to multiple = true as this tag does not take this attribute
i tested with below modification and got the selected multiple value in my bean. The only difference is i am using same value for itemLabel and itemValue but in your case it is object. i am using primefaces 6 also and dont even need to change actionListner to action. It is working as it is.sample xhtml
sample ResourceBean.java
<p:outputLabel for="ressource" value="Ressource"/>
<h:panelGroup >
<p:selectCheckboxMenu id="ressource" label="Ressource" value="#{resourceBean.selectedRessource}">
<f:selectItems value="#{resourceBean.ressources}" var="r" itemLabel="#{r}" itemValue="#{r}" />
</p:selectCheckboxMenu>
</h:panelGroup>
<p:commandButton icon="ui-icon-save" actionListener="#{resourceBean.save}" value="Save" ajax="false" style="display:inline-block;margin-top:5px"/>
The problem is in your p:commandButton, you have 3 options
change your method:
public void save(ActionEvent actionEvent){...}
change your action listener value:
actionListener="#{affectationBean.save()}"
or change your button to use action
action="#{affectationBean.save}"
DISCLAIMER: This is a workaround. It is not intended to be a permanent solution but will allow you to use selectCheckboxMenu and keep working.
There is an issue with this component that prevents it from passing values to the backing bean upon submit.
For some reason the array that should contain the selected values gets cleared out upon submit. Therefore I made an extra array that I did not declare in the tag, and updated in on every change event. Then on submit the values were still there. See below:
BackingBean.java
private String[] sCodes;
private String[] sCodes2; //extra array, not in form.xhtml
public void updateCodes()
{
sCodes2 = sCodes; //keeps the values in the other array
}
public void doSend() throws IOException
{
log.trace("selected codes: {} selected codes2 length: {}", sCodes.length, sCodes2.length);
}
form.xhtml
<p:selectCheckboxMenu id="codeCtl" value="#{bean.SCodes}" label="Codes" filter="true" filterMatchMode="startsWith" panelStyle="width:250px">
<f:selectItems value="#{bean.menuCodes}" />
<p:ajax event="change" listener="#{bean.updateCodes()}" />
</p:selectCheckboxMenu>
<p:commandButton value="submit" actionListener="#{bean.doSend}" id="ctlSubmit" update="appform"/>

<p:inputText> value not updated in model on change

I have a form that lets me edit a list of beans (one at a time), using buttons I can switch between the beans.
Keeping it simple :
public class MyBean {
private String text;
}
public class MyController {
private List<MyBean> availableBeans = new ArrayList<MyBean>(); // has five MyBeans with random text
private MyBean selectedBean; // initialized with first element of list
private int index = 0;
public void nextBean() { index++; }
public void previousBean() { index--; }
private void refreshBean() { selectedBean = availableBeans.get(index); }
}
For the html part I have something like
<h:form id="someForm">
<!-- stuff -->
<p:inputText value="#{myController.selectedBean.text}" />
<p:inplace editor="true" label="#{myController.selectedBean.text}" >
<p:inputText value="#{myController.selectedBean.text}" />
</p:inplace>
<!-- more stuff-->
</h:form>
If I change the text inside the inplace tag, the variable in myBean will be updated just fine, but If I only use inputText the bean will still have the old value, even if I change it on the webpage. Why is that?
Its because the p:inplace editor="true" implicitly submits the value to the server while <p:inputText does not do it implicitly,
You can solve it in several ways
1) add submit button like <p:commandButton to submit the value from p:inputText
2) use p:ajax event="keyup" or event="change",inside p:inputText
also take a look at the showcase p:ajax enables ajax features on supported components.
p.s , remove the value attribute from the p:inplace (there is no such attribute in p:inplace)
Lets give your components ids:
<h:form id="someForm">
<p:inputText id="first" value="#{myController.selectedBean.text}" />
<p:inplace id="second" editor="true" value="#{myController.selectedBean.text}">
<p:inputText id="third" value="#{myController.selectedBean.text}" />
</p:inplace>
</h:form>
According to the Primefaces Documentation 3.5 the component p:inplace has no attribute called value.
Do you submit the form someForm when changing the value of first? Otherwise the updated values from first won't be passed to MyController and MyBean. p:inplace submits the values automatically whereby you have to do it yourself it you use the standard p:inputText.

jsf datatable why commandLink clientid resolved before update model values phase

I have a datatable that iterates over a list, lets call it myList. I populate this myList based on some request parameters. Inside the datatable there are commandLinks. If i put a dummy entry into myList during apply request values phase, i can click on the first commandLink, and it works as it should (it is executed during invoke application phase, and by then the correct entries are in myList). If i dont do it, or i click on the second or later commandLink, nothing happens. So im guessing the clientId of the command button is resolved during apply request phase, even thought it is only used during the invoke application phase, which results in the broken commandLinks.
something like this:
<h:selectManyCheckbox styleClass="hidden"
value="#{cc.attrs.selectionList.selected}"
converter="#{cc.attrs.converter}" >
<f:selectItems value="#{cc.attrs.selectionList.all}"
var="item" itemValue="#{item}" itemLabel="" />
</h:selectManyCheckbox>
<h:dataTable value="#{cc.attrs.selectionList.selectedTest}" var="item">
<h:column>
<h:commandLink value="deselect" action="#{cc.attrs.selectionList.deSelect(item)}">
<f:ajax execute=":#{component.parent.parent.parent.clientId}"
render=":#{component.parent.parent.parent.clientId}" />
</h:commandLink>
</h:column>
</h:dataTable>
and the model:
public List<E> getSelected()
{
return myList;
}
public List<E> getSelectedTest()
{
if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.RESTORE_VIEW) && getSelectedList().isEmpty())
{
return Collections.singletonList(myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES) && getSelectedList().isEmpty())
{
return Collections.nCopies(2, myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.PROCESS_VALIDATIONS) && getSelectedList().isEmpty())
{
return Collections.nCopies(3, myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES) && getSelectedList().isEmpty())
{
return Collections.nCopies(4, myList.get(0));
}
return myList;
}
public void deSelect(E item)
{
myList.remove(item);
}
with this example, the top two commandLinks of the datatable works.
My question is why is this behaviour, and is there any way around without filling myList with dummy entries? I do not want to use any (viewscoped) backing bean to store the data.
During apply request values phase, JSF needs to iterate over the model in order to find the clicked command link. If the model changes incompatibly during the HTTP request wherein the form submit is processed (the postback) as compared to the initial HTTP request wherein the table with the command links is shown, then JSF may not be able to find the clicked command link and thus never queue the desired action, or the object representing the "current row" is not the same as the enduser intented.
If your bean is request scoped, then it should be written in such way that it initializes selectedTest in the constructor or #PostConstruct method based on some request parameter. At least, you should absolutely not perform business logic in getters.
You can pass the parameters necessary for reconstructing the selectedTest as <f:param> in the command link.
<h:commandLink ...>
<f:param name="some" value="#{bean.some}" />
</h:commandLink>
And prepare the model as follows:
#ManagedProperty
private String some;
#PostConstruct
public void init(){
selectedTest = populateItBasedOn(some);
}
// Don't change standard getters/setters!
See also:
commandButton/commandLink/ajax action/listener method not invoked or input value not updated - point 4
I managed to get my way around by binding the selectManyCheckbox itself to my componentBindings HashMap, and using that for the dataTable (with immediate="true" on the selectManyCheckbox):
<h:selectManyCheckbox immediate="true" styleClass="hidden"
binding="#{componentBindings[cc.attrs.selectionList]}"
value="#{cc.attrs.selectionList.selected}"
converter="#{cc.attrs.converter}" >
<f:selectItems value="#{cc.attrs.selectionList.all}" var="item"
itemValue="#{item}" itemLabel="" />
</h:selectManyCheckbox>
<h:dataTable value="#{componentBindings[cc.attrs.selectionList].value}" var="item">
<h:column>
<h:commandLink value="deselect" action="#{cc.attrs.selectionList.deSelect(item)}">
<f:ajax execute=":#{component.parent.parent.parent.clientId}"
render=":#{component.parent.parent.parent.clientId}" />
</h:commandLink>
</h:column>
</h:dataTable>
in faces-config.xml:
<managed-bean>
<description>Holder of all component bindings.</description>
<managed-bean-name>componentBindings</managed-bean-name>
<managed-bean-class>java.util.HashMap</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>

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.

immediate="true" and required fields problem

I'm having a little trouble with immediate="true" (JSF 1.2). My form is about a car accident: the user fills in some ubication information and then he can add as many affected items as he wishes (trees, fences, other cars, etc.)
Backing bean
private String location;
private List<T> items;
private HtmlDataTable itemsUI;
public void remove(ActionEvent e) {
String id = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("id");
items.remove(Integer.parseInt(id));
}
public void add(ActionEvent e) throws InstantiationException, IllegalAccessException {
items.add(element.newInstance());
}
JSPX
<h:inputText
required="true"
value="#{ACC01.location}"/>
<h:dataTable
binding="#{ACC01.itemsUI }"
value="#{ACC01.items}"
var="item">
<h:column>
<h:selectOneMenu
value="#{item.id}>
<f:selectItems
value="#{ACC01.possibleElements}" />
</h:selectOneMenu>
<h:commandLink
actionListener="#{ACC01.remove }"
value="Remove" >
<f:param
name="id"
value="#{ACC01.itemsUI.rowIndex }"/>
</h:commandLink>
</h:column>
</h:dataTable>
<h:commandLink
actionListener="#{ACC01.add}"
value="Add" />
The problem
If I set immediate="true" for the affected elements in the dataTable, when the user adds a new element the other elements return to their default values (e.g. if you had {'tree', 'car', 'fence'} they become {'default', 'default', 'default', new element})
If I don't use immediate, the affected elements keep the right values, but the user is forced to fill in the 'location' field before he can add or remove affected elements.
What I want is to be able to keep the affected elements' values and allow the user to add or remove them without having to fill the 'location' field first.
The workaround
After reading many posts about this topic, it seems that the only way to go is to avoid automatic validation and make it manually when the user submits the form.
Check for nulls on required fields, and programatically appending the error messages to facescontext. I really do not want to do that because i think it is ugly.
Can you please suggest better ways to achieve the behavior i need?
Thanks in advance
One of the previous developers at my current job came up with the following solution
JSPX
<h:inputText required="#{ACC01.saving}" value="#{ACC01.location}"/>
Backing Bean
private UICommand btn;
public boolean isSaving()
{
Map<?,?> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
return (params.containsKey(btn.getClientId(ctx)));
}
It might not be totally correct in your situation, but basically he created a binding from the button on screen to the 'btn' variable in the backing bean. Then inputText would only see the location text being required when the btn was clicked. In your case it's a command link so it might be a little different but I thought it might help.

Resources