I have the following menu items in index.xhtml
<p:menuitem ajax="false" value="A" action="#{bean.start('A')}" />
<p:menuitem ajax="false" value="B" action="#{bean.start('B')}" />
The following code on my backing bean doesn't work when the bean is ViewScoped (I don't want to make it SessionScoped):
#Named
#ViewScoped
public class Bean implements Serializable {
private LetterEnum letter;
public String findBSEmpty(String str) {
if (saison.equals("A")) {
this.letter = LetterEnum.A;
return "a.xhtml";
} else {
this.letter = LetterEnum.B;
return "b.xhtml";
}
}
public void doSomthing(){
//Method called from other a.xhtml and b.xhtml
//Processing data depending on the value of "letter"
}
I know that view scoped beans are recycled after every view change and I have must start from index.xhtml and depending on the choice of the user, the application will display a.xhtml or b.xhtml to continue processing other data.
Am I missing somthing?
What are the best practices about this kind of navigation?
BTW, I am using JSF 2.2 with Payara 4
I have Bean validation working nicely in my application. Now I want to check that a new user does not choose a username that has already been chosen.
In the actionlistener I have the code that checks the database but how do I force the user to be sent back to the page they were on if they choose an already existing username?
Introduction
You can do it, but JSF ajax/action/listener methods are semantically the wrong place to do validation. You actually don't want to get that far in JSF lifecycle if you've wrong input values in the form. You want the JSF lifecycle to stop after JSF validations phase.
You want to use a JSR303 Bean Validation annotation (#NotNull and friends) and/or constraint validator, or use a JSF Validator (required="true", <f:validateXxx>, etc) for that instead. It will be properly invoked during JSF validations phase. This way, when validation fails, the model values aren't updated and the business action isn't invoked and you stay in the same page/view.
As there isn't a standard Bean Validation annotation or JSF Validator for the purpose of checking if a given input value is unique according the database, you'd need to homegrow a custom validator for that.
I'll for both ways show how to create a custom validator which checks the uniqueness of the username.
Custom JSR303 Bean Validation Annotation
First create a custom #Username constraint annotation:
#Constraint(validatedBy = UsernameValidator.class)
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public #interface Username {
String message() default "Username already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
With this constraint validator (note: #EJB or #Inject inside a ConstraintValidator works only since CDI 1.1; so if you're still on CDI 1.0 then you'd need to manually grab it from JNDI):
public class UsernameValidator implements ConstraintValidator<Username, String> {
#EJB
private UserService service;
#Override
public void initialize(Username constraintAnnotation) {
// If not on CDI 1.1 yet, then you need to manually grab EJB from JNDI here.
}
Override
public boolean isValid(String username, ConstraintValidatorContext context) {
return !service.exist(username);
}
}
Finally use it as follows in model:
#Username
private String username;
Custom JSF Validator
An alternative is to use a custom JSF validator. Just implement the JSF Validator interface:
#ManagedBean
#RequestScoped
public class UsernameValidator implements Validator {
#EJB
private UserService userService;
#Override
public void validate(FacesContext context, UIComponent component, Object submittedAndConvertedValue) throws ValidatorException {
String username = (String) submittedAndConvertedValue;
if (username == null || username.isEmpty()) {
return; // Let required="true" or #NotNull handle it.
}
if (userService.exist(username)) {
throw new ValidatorException(new FacesMessage("Username already in use, choose another"));
}
}
}
Finally use it as follows in view:
<h:inputText id="username" ... validator="#{usernameValidator}" />
<h:message for="username" />
Note that you'd normally use a #FacesValidator annotation on the Validator class, but until the upcoming JSF 2.3, it doesn't support #EJB or #Inject. See also How to inject in #FacesValidator with #EJB, #PersistenceContext, #Inject, #Autowired.
Yes you can. You can do validation in action listener method, add faces messages if your custom validation failed, then call FacesContext.validationFailed() just before return.
The only problem with this solution is, it happens after the JSF validation and bean validation. I.e., it is after the validation phase. If you have multiple action listeners, say listener1 and listener2: if your custom validation in listener1 failed, it will continue to execute listener2. But after all, you'll get validationFailed in AJAX response.
It's better to use action method instead of actionListener for this purpose. Then you can return null (reloads page that triggered the action) from this method if the username exists. Here's an example:
in the facelet:
<h:commandButton action="#{testBean.doAction}" value="and... Action"/>
in the bean:
public String doAction() {
if (userExists) {
return null;
} else {
// go on processing ...
}
}
If you want to provide feedback to end-user:
xhtml:
<p:commandButton value="Go" process="#this" action="#{myBean.checkEntity()}" oncomplete="if(args.validationFailed){PF('widgetOldInfoNotice').show();}"/>
<p:confirmDialog id="dialogOldInfoNotice" header="NOTICE" severity="alert" widgetVar="widgetOldInfoNotice">
-- feedback message--
<p:button value="Ok" onclick="PF('widgetOldInfoNotice').hide();"/>
</p:confirmDialog>
bean:
public String checkEntity() {
if (!dao.whateverActionToValidateEntity(selectedEntity)) {
FacesContext context = FacesContext.getCurrentInstance();
context.validationFailed();
return "";
}
return "myPage.xhtml";
}
You can define a navigation case in the faces-config.xml file. This will allow you to redirect the user to a given page depending on the return value of the bean.
In the example below a suer is redirected to one of two pages depending on the return value of "myMethod()".
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-action>#{myBean.myMethod()}</from-action>
<from-outcome>true</from-outcome>
<to-view-id>/correct.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{myBean.myMethod()}</from-action>
<from-outcome>false</from-outcome>
<to-view-id>/error.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
I have a p:selectOneMenu in my application, and when the selection takes place I need to call multiple back-end methods from different managed beans in order to perform different actions.
XHTML code:
<p:selectOneMenu id="selectMenu" value="#{userBean.selectedSite}"
converter="siteConverter" style="width:150px">
<p:ajax event="change" listener="#{bean2.changeSite}"
render="#form :comp1 :comp2 :comp3 :comp4" />
<f:selectItems value="#{userBean.sites}" var="site"
itemValue="#{site}" itemLabel="#{site.description}" />
<p:ajax event="change" listener="#{bean1.reset}"
update="#form :comp1 :comp2 :comp3 :comp4" />
</p:selectOneMenu>
Managed bean 1:
#ManagedBean(name="bean1")
#ViewScoped
public class Bean1 implements Serializable {
// ...
public void reset() {
loadEvents();
resetEvent();
}
}
Managed bean 2:
#ManagedBean(name="bean2")
#SessionScoped
public class Bean2 implements Serializable {
// ...
public void changeSite() {
FacesContext context = FacesContext.getCurrentInstance();
Bean1 bean = (Bean1) context.getApplication().evaluateExpressionGet(context, "#{bean1}", Bean1.class);
reload();
bean.loadEvents();
}
}
If, instead of using two different p:ajax components, I use a single p:ajax that calls a single method from Bean1, the page components listed under "update" are not correctly updated.
XHTML:
<p:ajax event="change" listener="#{bean1.singleMethod}"
update="#form :comp1 :comp2 :comp3 :comp4" />
Managed bean 1:
#ManagedBean(name="bean1")
#ViewScoped
public class Bean1 implements Serializable {
// ...
public void () singleMethod() {
FacesContext context = FacesContext.getCurrentInstance();
Bean2 bean = (Bean2) context.getApplication().evaluateExpressionGet(context, "#{bean2}", Bean2.class);
bean2.changeSite();
reset();
}
}
Changing the selected values updates the server side objects, but the page is not updated: if I press F5, the page shows the actual situation.
Moving the single global method to the session scoped bean (Bean2) does the job.
I have a JSF form in which there is one field(textfield), value in textfield say profileId, which I need to use in many pages, so how can we store it in a session, and also how can we retrieve it as we need?
In simple words set a variable value in JSF session and also get it.
Bind it to a session scoped managed bean.
#ManagedBean
#SessionScoped
public class Profile {
private Long id;
// ...
}
with
<h:inputText value="#{profile.id}" />
You can access it in other beans by injecting it as #ManagedProperty.
#ManagedBean
#ViewScoped
public class OtherBean {
#ManagedProperty("#{profile}")
private Profile profile;
public void submit() {
System.out.println(profile.getId());
}
// ...
}
I want to do multiple actions on different managed beans with the same button, one being scoped session and the other request. In my example I use the same bean for both.
index.xhtml
<h:form>
<p:commandButton image="ui-icon ui-icon-notice" action="#{controller.inc()}" update="result">
<f:actionListener type="controller.Controller" />
</p:commandButton>
</h:form>
<p:panel id="result">
#{controller.count}
</p:panel>
controller.Controller.java
#Named(value = "controller")
#SessionScoped
public class Controller implements ActionListener, Serializable
{
int count = 0;
public Controller(){
System.out.println("new");
}
public void inc(){
count += 1;
}
public int getCount(){
return count;
}
#Override
public void processAction(ActionEvent event) throws AbortProcessingException{
count += 1000;
}
}
When I press the button the count increases by 1, instead of 1001, and creates a new bean. What did I do wrong ?
Thanks.
That's expected behaviour. The <f:actionListener type> creates and gets its own bean instance on every declaration. It does not reuse the same session scoped bean which is managed by JSF.
You need to use binding instead to bind to the already-created session scoped bean instance.
<f:actionListener binding="#{controller}" />