views:

101

answers:

3

I have next form:

<h:form>
    <h:selectManyListbox value="#{reports.selectedCategories}"
                         converter="#{categoryConverter}">
        <f:selectItems value="#{reports.categories}"/>
    </h:selectManyListbox>
    <h:commandButton value="Submit" action="#{reports.action}" />
</h:form>

I use custom converter to transform Category to string and vice versa:

public class CategoryConverter implements Converter {

    @Autowired
    private CategoryService categoryService;

    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println ("CONVERTER: GET AS OBJECT");
        return categoryService.findByName (value);
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        System.out.println ("CONVERTER: GET AS STRING");
        return ((Category) value).getName ();
    }
}

And finally my backing bean code:

public class ReportsController {

    private Category[] selectedCategories;

    public void setSelectedCategories (Category[] categories) {
        System.out.println ("SET CATEGORIES");
        this.selectedCategories = categories;
    }

    public Category[] getSelectedCategories () {
        System.out.println ("GET CATEGORIES");
        return selectedCategories;
    }

    public void action () {
        System.err.println ("ACTION");
    }

    ....
    //methods for accessing data

}

When I start my app, I see correct listbox with 2 items and a button "Submit". But when I press submit nothing happens. The only output I see is:

GET CATEGORIES
CONVERTER: GET AS STRING
CONVERTER: GET AS STRING

Neither setSelectedCategories () nor action () methods are ever called. And I don't understand why. Any suggestions?

UPD: the problem is even more stupid: when I commented all stuff related to Category class (i.e. my selectManyListbox, my converter, all related setters and getters) and left only method action, it still isn't being called.

<h:form>
    <h:commandButton value="Submit" action="#{reports.action}" />
</h:form>

UPD2: here is the output from phase listener

START PHASE RESTORE_VIEW 1
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
GET STR
END PHASE RENDER_RESPONSE 6

It seems that all phases worked fine but actually it's not true. I'm a bit confused.

+1  A: 

Because under "converter" you should provide converter name as is defined in faces-config.xml:

<converter>
    <converter-id>myConverter</converter-id>
    <converter-class>com.example.converters.MyConverter</converter-class>
</converter>

also having <h:messages/> in the form is usually helpful in this situations, because you see what went wrong.

Slartibartfast
But my converter is actually found, and its getAsString method is being called successfully.
Roman
+2  A: 

When a JSF form "doesn't work", this is usually an indication that something (usually a component, validator or converter) has instructed the lifecycle that some of the input is invalid and that it should stop processing and re-render the view. This prevents junk data getting into your model and stops your business logic from acting on invalid input.

alt text Figure from JEE5 Tuorial

This will be down some way to the configuration of your view and the logic of any code you've plugged into it. As Slartibartfast says, adding a <h:messages /> tag to the view will often inform you of which components triggered this response. Configuring a PhaseListener is often useful during debugging to tell you which phases completed during a request.


public void action () {
    System.err.println ("ACTION");
}

This action method should return a String:

public String action () {
    System.err.println ("ACTION");
    return null;
}

Some implementations tolerate a void return type, but it contravenes the spec. The action attribute must be bound to an application action as defined in the spec:

Application Actions

...

  • The method must be public.
  • The method must take no parameters.
  • The method must return Object.

The action method will be called by the default ActionListener implementation, as described in Section 7.1.1 “ActionListener Property” above. Its responsibility is to perform the desired application actions, and then return a logical “outcome” (represented as a String) that can be used by a NavigationHandler in order to determine which view should be rendered next.

McDowell
The both impls are however (fortunately) forgiving in this. `void` actions is also allowed and this is ideal if you want to avoid all the pains around url-masking/backbutton/SEO/etc issues. Just let the form submit to self and show result conditionally and use `GET` (outputlinks) only for plain vanilla navigation.
BalusC
@BalusC - ah, I hadn't tested this recently; if memory serves, this used to cause an error.
McDowell
+2  A: 

When a JSF form submit fails, then you need to make sure of the following:

  1. UICommand components must be nested in an UIForm component.

    <h:form>
        <h:commandButton value="submit" action="#{bean.submit}" />
    </h:form>
    
  2. You cannot nest multiple UIForm components in each other, this is prohibited by HTML specification. Watch out with included pages/subviews, those accounts up in the component tree! if the jsp:include is already inside a h:form, you need to remove the h:form from the include page!

    <h:form>
        <h:form>
            <h:commandButton value="this will not work" action="#{bean.fail}" />
        </h:form>
    </h:form>
    
  3. No validation/conversion error should have been occurred (use h:messages to get them all).

    <h:form>
        <h:commandButton value="submit" action="#{bean.submit}" />
        <h:messages />
    </h:form>
    
  4. If UICommand components are nested in an UIData component (e.g. h:dataTable), be sure that exactly the same DataModel (the object behind the value attribute) is preserved. Easiest way to test this is placing the bean in session scope. If that fixes the problem, then you need to review the data loading logic.

  5. The rendered and disabled attributes of the component and all of the parent components should not evaluate negatively. Easiest way to test this is placing the bean in session scope. If that fixes the problem, then you need to review the code responsible for this.

  6. Be sure that no PhaseListener or any EventListener has changed the request lifecycle to skip the invoke action phase.

  7. Be sure that no Filter/Servlet in the same request chain has blocked the request of the FacesServlet somehow.

As to your actual problem; you didn't get PropertyNotFoundExceptions or similar and your PhaseListener has executed all appropriate lifecycle phases nicely, so I think that your problem is actually caused by 2. Nested forms.

BalusC
I found the problem a little earlier then your answer appeared but my problem is described in your 2 item. There are a template in the project which adds form-wrapper to every page.
Roman
Ah, my guess was thus right? You're welcome.
BalusC