I have a form in JSF2 with 2 checkboxes (<h:selectBooleanCheckbox>). At least one of them should be checked. Both checked are also ok, but when none is checked, there should be error.
This validation should work also on ajax, i.e. when user checks/unchecks, error message should disappear/appear.
So I binded both checkboxes to UISelectBoolean elements and added validator to each. In validator I check value of the second checkbox, if it is false, and current checkbox is also set to false, errormessage is produced. Smth like:
if (newValue == false && secondCheckbox.getValue() == false) {
throw new ValidationException();
}
Problem is when user unchecks checkbox on the page, while the other is also unchecked, this false value doesn't go to the model, nor event to UISelectBoolean.
Scenario:
Initially both are unchecked
User checks checkbox1.
newValue is true, so this is valid, goes to UISelectBoolean and model.
User unchecks checkbox1. Checkbox2 is false, newValue is false, so exception is thrown.
Because of failed validation, this false value doesn't go to the model, nor even to UISelectBoolean element.
User checks checkbox2. True value goes to the model and UISelectBoolean.
User unchecks checkbox2. Checkbox1 is still unchecked on the page, but in the model and UISelectBoolean there is still true. So validation passes and there is no error message. On the page both checkboxes are unchecked, but in model checkbox1 is still true.
How to solve such a problem?
Consider <h:selectManyCheckbox required="true">. Here's a kickoff example:
<h:form>
<h:selectManyCheckbox id="options" value="#{bean.checked}" required="true">
<f:selectItems value="#{bean.options}" />
<f:ajax render="optionsMessage" />
</h:selectManyCheckbox>
<h:message id="optionsMessage" for="options" />
<h:commandButton value="Submit" action="#{bean.submit}">
<f:ajax execute="#form" render="#form" />
</h:commandButton>
<h:messages globalOnly="true" />
</h:form>
With
#ManagedBean
#RequestScoped
public class Bean {
private List<String> checked;
private List<String> options = Arrays.asList("first", "second");
public void submit() {
System.out.println("Checked: " + checked);
}
// ...
}
The f:ajax works fine here. Probably you've used event="click" instead of (the default) event="valueChange" which will cause that the checkmark won't be retained after render. The click is namely called right before the checkmark get visually set. The render would block the checkmark being visually set.
Related
My screen looks like this
The problem is that, when I press the Reset Button, the Code input field does not clear, as it was supposed to.
The jsf is the following:
<h:form id="form1">
<h:inputText id="code" value="#{MyBean.data.code}" />
<a4j:commandButton immediate="true"
action="#{MyBean.clear}" render="form1" update="#form">
</a4j:commandButton>
<h:outputText value="#{session.lastAccessedTime}">
<f:convertDateTime pattern="HH:mm:ss.SSS" type="date" />
</h:outputText>
</h:form>
The bean code is the following:
public class MyBean {
DataInnerClass data = new DataInnerClass();
//getter and setter for data
public class DataInnerClass {
private String code;
//getter and setter for code
public DataInnerClass() {
super();
}
}
public void clear() {
data = new DataInnerClass();
Logger.getLogger( MyBean.class.getName() ).log(Level.SEVERE, "data.code="+data.code);
//logs data.code=null
}
}
When I press the Reset button, the log shows that the value of the field code has become null (its no longer 'ZZ'); i also know that the screen refreshes successfully, because i have displayed the current time, which updates after every click. So the backing bean property changes, the screen refreshes, and still the input field keeps the same value. Do you have any idea why this is happening?
I found useful BalusC's answer on: How can I populate a text field using PrimeFaces AJAX after validation errors occur?
I understand from there that, in the case of some validation error, the input field will keep the value entered by the user, and will be out of sync with the backing bean. I must emphasize that in my case, there were no validation errors, but still, adding an <f:ajax resetValues> to the commandButton worked for me.
A concrete example of the resetValues attribute is on https://jsflive.wordpress.com/2013/06/20/jsf-22-reset-values/
Finally, my button tag looks like this:
<a4j:commandButton immediate="true"
action="#{MyBean.clear}" render="form1" update="#form">
<f:ajax render="code" resetValues="true" />
</a4j:commandButton>
On page I have checkbox which enables/disables h:inputText and h:selectOneMenu. That works fine until I refresh the page. Value of checkbox is probably cached by browser.
So, when the checkbox was set on "true" after refresh both controls are enabled. selectOneMenu is rendered corresponding to value set in init() method, but selectBooleanCheckbox and inputText isn't. How can I fix that? I use Firefox browser.
Backing bean is simple, one property and one method - init in which value is set to false.
#ManagedBean(name="newBean")
#ViewScoped
public class NewBean implements Serializable {
private boolean booleanValue;
public NewBean() {
}
public void init()
{
booleanValue = true;
}
public boolean isBooleanValue() {
return booleanValue;
}
public void setBooleanValue(boolean booleanValue) {
this.booleanValue = booleanValue;
}
}
Page source:
<h:body>
<f:metadata>
<f:viewAction action="#{newBean.init()}"></f:viewAction>
</f:metadata>
<h:form>
<h:outputText value="#{newBean.booleanValue}"></h:outputText>
<br/>
<h:selectBooleanCheckbox value="#{newBean.booleanValue}" id="selected">
<f:ajax event="change"
render="enabledInput disabledInput enabledMenu"></f:ajax>
</h:selectBooleanCheckbox>
<br/>
<h:inputText disabled="#{newBean.booleanValue}" id="enabledInput"></h:inputText><br/>
<h:selectOneMenu disabled="#{!newBean.booleanValue}" id="enabledMenu">
<f:ajax></f:ajax>
</h:selectOneMenu>
</h:form>
</h:body>
EDIT: Added some screenshots so it will be easier to understand the problem.
Correct: Page opened by user (checkbox is checked, and it corresponds to bean value, textbox is disabled, menu is enabled):
Correct: User unchecked checkbox (checkbox is unchecked, value on bean is false too, textbox is enabled, menu is disabled):
ISSUE: after unchecking user refreshed page. Value on bean is true, but checkbox is still unchecked. Also, textbox and menu are BOTH enabled.
Hey there I didn't quite understand your problem but I'd like to point two things.
<f:metadata>
<f:viewAction action="#{newBean.init()}"></f:viewAction>
</f:metadata>
Must be in the < h:form /> for the action to take effect.
And also if you want the method init to take effect before you create the new instance of the view add the annotation #PostConstruct or simply put the code
" booleanValue = true; " inside the constructor of the view.
Hopefully I may be of help.
Assume we have a form. One p:inputText visible but user can add many more using p:commandButton. All this values have to be provided when submitting with another p:commandButton. Issue arises when user tries to add more than one empty input fields. All of them are marked required="true" so validation error appears when one field is empty and user try to add another.
The best would be to allow to add as many fields as user needs, then fill them in and submit.
JSF:
<h:form id="myForm">
<p:commandButton value="add" actionListener="#{testBean.addNewItem()}" update="#form"/>
<p:commandButton value="done" update="#form,:p"/>
<br/>
<ui:repeat value="#{testBean.list}" var="l">
<p:inputText value="#{l.name}" required="true"/>
<br/>
</ui:repeat>
</h:form>
<p:messages autoUpdate="true"/>
<p:panel id="p">
#{testBean.list}
</p:panel>
Backing bean does nothing fancy. Only provides getter and setter for list. It also adds empty string to the list.
#ManagedBean
#ViewScoped
public class TestBean implements Serializable {
private List<Item> list = new ArrayList<Item>();
public List<Item> getList() { return list; }
public void setList(List<Item> list) { this.list = list; }
public void addNewItem() { list.add(new Item()); }
}
I could:
Remove requirement for field - not an option.
Add immediate="true" for adding button. Validation is not a problem now but it causes all values that was filled in but not submitted to disappear. And I need to update #form because only then newly added fields will be rendered by ui:repeat.
I tried to add process="#this" for adding button. Unfortunately that didn't change a thing. Input field values are not processed, but form needs to be updated. I am loosing not submitted values as above.
What am I missing? Is there any workaround?
Just let the required attribute check if the "done" button is pressed. The button's own client ID is present as a request parameter if that's the case. Request parameters are available by #{param} mapping. You can use button's binding attribute to bind the physical component to the view so that you can grab its UIComponent#getClientId() elsewhere. Finally just do the boolean logic.
E.g.
<p:commandButton binding="#{done}" ... />
...
<p:inputText ... required="#{not empty param[done.clientId]}" />
Will something like this work?
<p:inputText value="#{l.name}" required="#{l.name != null ? true : false}"/>
This will enable the newly added inputText components to not be required but enforce the items already in the list to be required.
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);
}
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.