Wednesday, December 19, 2007

Action dependent requireness

Introduction

There are scenarios where you want to depend the "requireness" (between quotes, this word doesn't occur in an English dictionary) of an UIInput element on the UICommand action invoked. There are also a lot of hacks and workarounds written about it, e.g. using immediate="true" and valueChangeListener or binding attributes to retain the value (which gives you only two scenario cases) or moving the validation to the backing bean actions (which gives you endless scenario cases), etcetera.

Those workarounds require extra logic in the backing bean. It would be great if JSF offers an default possibility to let the requireness of the UIInput depend on the UICommand action invoked. Having an attribute like requiredActions which accepts a commaseparated string of the ID's of the buttons would be great. Unfortunately such an attribute isn't available. But don't feel disappointed, there is a possibility to use the request parameter map to determine which UICommand action invoked. Its client ID is namely available in the request parameter map. So just checking the presence of the client ID in the #{param} ought to be enough. KISS! ;)

Back to top

Basic JSF example

Here is a sample form. It represents a login form with two input fields and two buttons. The username as well as the password fields are required when you press the "Login" button, while the password field isn't required when you press the "Forget password" button. This is done by checking the presence of the client ID of the "Login" button in the required attribute of the password field. You can also check the absence of the client ID of the "Forget password" button as well.

The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSF 1.2_07 (currently called Mojarra).

<h:form id="form">
    <h:panelGrid columns="3">
        <h:outputLabel for="username" value="Username" />
        <h:inputText id="username" value="#{myBean.username}"
            required="true" />
        <h:message for="username" style="color: red;" />

        <h:outputLabel for="password" value="Password" />
        <h:inputSecret id="password" value="#{myBean.password}"
            required="#{!empty param['form:login']}" />
        <h:message for="password" style="color: red;" />

        <h:panelGroup />
        <h:panelGroup>
            <h:commandButton id="login" value="Login" action="#{myBean.login}" />
            <h:commandButton id="forget" value="Forget password" action="#{myBean.forget}" />
        </h:panelGroup>
        <h:message for="form" style="color: green;" />
    </h:panelGrid>
</h:form>

The appropriate test backing bean (request scoped) look like:

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class MyBean {

    // Init ---------------------------------------------------------------------------------------

    private String username;
    private String password;

    // Actions ------------------------------------------------------------------------------------

    public void login() {

        // Just for debug. Don't do this in real! Hash the password, compare in DB and forget it ;)
        System.out.println("Login username: " + username);
        System.out.println("Login password: " + password);

        // Show succes message.
        FacesContext.getCurrentInstance().addMessage("form", new FacesMessage("Login succesful!"));
    }

    public void forget() {

        // Just for debug.
        System.out.println("Forget username: " + username);

        // Show succes message.
        FacesContext.getCurrentInstance().addMessage("form", new FacesMessage("New password sent!"));
    }

    // Getters ------------------------------------------------------------------------------------

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    // Setters ------------------------------------------------------------------------------------

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

Now, when you hit the "Login" button, it will validate the username as well as the password fields, but when you hit the "Forget password" button, it will validate the username field only!

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) December 2007, BalusC

14 comments:

Murat MorkoƧ said...

Hii BalusC,

Are there any possibility for more than one button submit control for any form element(like password). For example "Remember Me" button on page. This action also require password.

Thanks
Murat

BalusC said...

You can use the following in the password field:

required="#{empty param['form:forget']}"

or:

required="#{!empty param['form:login'] || !empty param['form:remember']}"

LookingForSpring said...

Hi BalusC,
Do you know of any good diagram/schematic representing the layout of faces-config.xml.
If so, do you think you could post a url.

I tend to find DTD's rather arduous to interpret and am currently stuck trying to figure out why an app is not working correctly.
I'm getting the following message in the Glassfish logs:

Exception sending context initialized event to listener instance of class com.sun.faces.config.ConfigureListener javax.faces.FacesException: Can't parse configuration file: jndi:/server/database/WEB-INF/faces-config.xml: Error at line 9 column 15: cvc-complex-type.2.4.a: Invalid content was found starting with element 'var'. One of '{"http://java.sun.com/xml/ns/javaee":description,

Now I have a hunch it's because perhaps managed-bean and application nodes are sequenced the wrong way around but would like to verify from diagram.

Regards, Jeremy
PS: Your linkedin url seems to be broken.

BalusC said...

Download JSF 1.2 specification (follow the link 'JSF 1.2 specification (JSR252)' at the favorites column) and check chapters 10.4.3 and 10.4.4.

Linkedin public profile URL's are indeed broken (not only for my profile, but also for others). I've reported this bug to them.

Parminder Singh said...

Do you have a pattern to initialize local fields in a backing bean for JSF 1.1 scenario? In other words, is there a pattern that can address what annotations like @PostConstruct provide, etc in JSF 1.1?

deinte said...

Hi.

I'm implementing a form which has an action. Associated to this action is has a method which return a String mapped in faces-config.xml to make my navigation. This is the typical use, but what about if I want that this method make some operations and call another method inside of it,let say method_01 or method_02 wich return the string. It's like a chaining method.
Question is how can I call method_01, method_02, etc..???

I hope I have explain myself.

Thanks

BalusC said...

Just do as you say it. There's nothing special what you need to do.

public String submit() {
return anotherMethodReturningString();
}

Or so.

deinte said...

Hi.

Thanks for answer.

I think I forgot something.
What you call
anotherMethodReturningString();
is correct but suppose that you only have the name of the method.

My application is a tree. Each node navigate to a page (always the same), but in the node I have defined the name of the method I have to use. So, depending of the node I must call different method and I have just the name ( a string).
so it will be something like this:
public String submit() {
//pseudo
// read the method name from the node (node)
// call that method
// but how I call that method

return method_01
}

I hope I have explain myself better..

Any suggestions?

Thanks

BalusC said...

Use the reflection API.

deinte said...

Thanks

First time I hear about it.

I' try it..


Thanks

deinte said...

Hi, balus

I have tried the reflection api and works great, very elegant.

Thanks a lot

Kapil Avasthi said...

Hi BalusC,

I have a page with certain inputs fields when I search on these particular fields corrseponding result displayed on the same page in datatable.

My Datatable has some check box in every row on selection of these check boxes a action is called which is taking these values to next page(meanwhile all the search input Paramers are in request).
On the next page there is two buttons Submit & Back. When I click on back button to go back to previous page its invoking validation phase which is not required.
So can you please help me out to handle this issue, I have removed immediate ="true" on this Back button, when I am using immediate="true" button on Back Button, all the values are gone on the previous page.

Thanks
Kapil

Geoff said...

could this be used to optionaly validate a field depending on how the form is submitted?

i.e. I have a select box that changes the number of text boxes on the form(via reflection). It has an onchange"submit()" if data in the form is incorrect validation kicks in and intercepts building the form correctly.

I would think that immediate="#{empty param['form:save']}" would work ... meaning "don't validate this because the form is being submitted by something other than the save button. but it doesn't act as I expect... any pointers?

BalusC said...

Set dropdown component to immediate="true" and add FacesContext#responseComplete() in valueChangeListener method.

More explanation can be found in "Populate child menu's" article of October 2007.