2

Well, I need some clarification on selectOneMenu tag, using EL expression to get/set values

        <h:selectOneMenu id="variantsMenu"
            value="#{missionHandler.selectedVariant}"
            converter="#{missionHandler.variantConverter}">
            <f:selectItem itemLabel="-- Select a Variant --" />
            <f:selectItems value="#{missionHandler.variants}" var="variant"
                itemValue="#{variant}" itemLabel="#{variant.commercialName}" />
        </h:selectOneMenu>

I have this code, it displays a list containing ("-- Select a Variant--", Variant 1, Variant 2, ... Variant n) When I click on a Variant, I would expect the selectedVariant property in the missionHandler managedBean to be updated automatically. But this is not the case. The variantConverter converter is not even called to convert the String "variant.commercialName" to a real Variant object. The variant object implements hashCode() and equals()

  • What concept of EL expression / tag am I missing ?
  • What would be the solution to my update problem ?

    EDIT : managedBean (simplified)

    @ManagedBean
    @ViewScoped
    public class MissionHandler implements Serializable {
    
    private static final long serialVersionUID = 2462652101518266609L;
    
    private List<FlightFeasibilityException> exceptions;
    
    @EJB
    private VariantDao variantDao;
    
    private Variant selectedVariant;
    
    private List<Variant> variants;
    
    private VariantConverter variantConverter;
    
    public MissionHandler() {
        /** Create an empty list of exceptions */
        exceptions = new ArrayList<FlightFeasibilityException>();
    }
    
    @PostConstruct
    public void init() {
        System.out.println("init done");
    }
    
    public List<FlightFeasibilityException> getExceptions() {
        return exceptions;
    }
    
    public void setExceptions(List<FlightFeasibilityException> exceptions) {
        this.exceptions = exceptions;
    }
    
    public Variant getSelectedVariant() {
        return selectedVariant;
    }
    
    public void setSelectedVariant(Variant selectedVariant) {
        this.selectedVariant = selectedVariant;
    }
    
    public List<Variant> getVariants() {
        return variants;
    }
    
    public void setVariants(List<Variant> variants) {
        this.variants = variants;
    }
    
    public VariantConverter getVariantConverter() {
        return variantConverter;
    }
    
    public void setVariantConverter(VariantConverter variantConverter) {
        this.variantConverter = variantConverter;
    }
    

    }

Converter

@FacesConverter(forClass=Variant.class)
public class VariantConverter implements Converter, Serializable {

    private static final long serialVersionUID = 7053414108213420057L;

    private VariantDao variantDao=new VariantDaoImpl();

    @Override
    public Object getAsObject(FacesContext context, UIComponent component,
            String value) {
        System.out.println("convert to Object " + value);
        Variant variant = variantDao.find(value);
        System.out.println("got variant " + variant.getCommercialName());
        return variant;
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component,
            Object value) {
        System.out.println("convert to String " + value);
        return ((Variant) value).getCommercialName();
      }

    }

and the xhtml file

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    <title>Mission Page</title>
    <link href="./css/styles.css" rel="stylesheet" type="text/css" />
</h:head>
<h:body>
    <rich:panel>
        <f:facet name="header">
            Mission Information
        </f:facet>
        <a4j:outputPanel layout="block">
            <h:form>
                <h:outputText style="font-weight: bold;" value="Mission Id: " />
                <h:inputText id="missionId" label="missionId"
                    value="#{missionHandler.mission.id}" disabled="true"
                    style=" width : 50px;">
                </h:inputText>
                <h:outputText style="font-weight: bold;" value="Mission Status: " />
                <h:inputText id="missionStatus" label="missionStatus"
                    value="#{missionHandler.mission.status}" disabled="true">
                </h:inputText>
            </h:form>
            <br />
            <h:form>
                <h:selectOneMenu value="#{missionHandler.selectedAircraftType}"
                    converter="#{missionHandler.acTypeConverter}">
                    <f:selectItem itemLabel="-- Select an A/C Type --" />
                    <f:selectItems value="#{missionHandler.aircraftTypes}" var="type"
                        itemValue="#{type}" itemLabel="#{type.typeOACI}" />
                    <f:ajax listener="#{missionHandler.changeSelectedAircraftType}"
                        render="@form" />
                </h:selectOneMenu>
                <h:selectOneMenu id="variantsMenu"
                    value="#{missionHandler.selectedVariant}"
                    converter="#{missionHandler.variantConverter}"
                    rendered="#{not empty missionHandler.selectedAircraftType}">
                    <f:selectItem itemLabel="-- Select a Variant --" />
                    <f:selectItems value="#{missionHandler.variants}" var="variant"
                        itemValue="#{variant}" itemLabel="#{variant.commercialName}" />
                </h:selectOneMenu>
            </h:form>
        </a4j:outputPanel>
    </rich:panel>
….. A LOT OF OTHER THINGS …..
</h:body>
</html>
facewindu
  • 705
  • 3
  • 11
  • 31
  • You're attempting to instantiate the converter yourself. While I cannot categorically say it's wrong to do so, I can tell you it's the container's responsibility to manage the converter. Post the managed bean code here and also the converter – kolossus Feb 25 '13 at 22:30
  • done. please have a look at it – facewindu Feb 25 '13 at 22:50
  • Your code looks valid (even if you've made some questionable choices in it). I'm now interested in your xhtml file. Do you have nested ``s? Do you have a large number of components in a single ``? – kolossus Feb 26 '13 at 00:20
  • @kolossus : I've added the xhtml file. In parallel to me first question, could you tell me what is questionable in my code writing ? I'm always interested in learning some best practice on the way ... I'm not particularly fond of the database access on each "converter::getAsObject" call. I tried to initialize the converter with a list once and for all (in the managed bean init method) but they I got nullPointerExceptions. So I left it that way until I have time to rethink about it. – facewindu Feb 26 '13 at 07:10
  • 1)Doing stuff in the constructor of the managed bean==bad, use `@PostConstructor` instead. 2)Manually instantiating the DAO in your converter==bad design. Ideally, your DAO should be a managed object(preferably an EJB) that you can inject into the converter. 3)Trying to manually manage the converter. Declare the converter as a standalone component and let the container do it's work – kolossus Feb 26 '13 at 15:46
  • 1) OK, I'll move the exceptions list creation to the @PostConstruct method. 2) I tried to avoid initializing the DAO in the converter but I ran into nullPointer problems. What's the good thing to do ? Declare the Converter as a @ Stateless bean (or sth similar), and inject vie @ EJB the DAO (as I do in the managed bean) 3) I get the idea about manually managing the converter. I'll change that as well. – facewindu Feb 26 '13 at 16:24

3 Answers3

2

The value in the managed bean is not directly updated when you select an item from the menu because the form was not submitted.

Either submit it via a <h:commandButton ...> or use an ajax event <f:ajax event="change" ...> within the menu.

djmj
  • 5,579
  • 5
  • 54
  • 92
  • I see what you mean: #EL expression can be used to access getters and setters (compared to old $EL expression that could access only getters) but this does not mean that the getter method will be called every time the value of the #EL expression changes ? You still need to call some sort of ajax or manual submit method. – facewindu Feb 26 '13 at 07:13
  • user1446127, JSF generates HTML. Rightclick page in browser and do *view source*. HTML is completely dumb and stateless unless you use JavaScript/ajax to make it somewhat interactive depending on user interface events (keyboard/mouse). This "issue" is completely unrelated to JSF. You'd have had exactly the same problem when manually writing HTML code, or when using another server side language which also generates HTML like PHP, ASP, etc. – BalusC Feb 26 '13 at 11:58
2

A couple of notes on your code:

  1. Doing stuff in the constructor of the managed bean==bad design, use @PostConstructor instead.

  2. Manually instantiating the DAO in your converter==bad design. Ideally, your DAO should be a managed object(preferably an EJB) that you can inject into the converter. Being a managed object means it would (in theory) probably be a singleton and managed properly by the container. At least this way, you won't feel bad about it.

  3. Trying to manually manage the converter. Declare the converter as a standalone component and let the container do it's work so you can avoid unnecessary debugging.

  4. variantsMenu does not have an ajax handler attached to it so you won't get the value updated in the backing bean until the entire form is submitted

    <h:selectOneMenu id="variantsMenu"
                value="#{missionHandler.selectedVariant}"
                converter="#{variantConverter}"
                rendered="#{not empty missionHandler.selectedAircraftType}">
                <f:ajax listener="#{missionHandler.changeVariant}"       render="@form" />
                <f:selectItem itemLabel="-- Select a Variant --" />
                <f:selectItems value="#{missionHandler.variants}" var="variant"
                    itemValue="#{variant}" itemLabel="#{variant.commercialName}" />
            </h:selectOneMenu>
    
kolossus
  • 20,559
  • 3
  • 52
  • 104
  • 1. why is it bad to do stuff in constructor? I only do stuff in `@PostConstruct` if there is a reason for (example viewParam dependant initialization logic), with 2. I aggree about the instantiating and passing the dao to the converter as a param is also an option which I mostly use. 3. I manage most of my entity converters myself, since the conversion depends very often on dynamic data. – djmj Feb 26 '13 at 15:55
  • 1
    @djmj, it's primarily a point of good design as the `@PostConstruct` is a container-provided callback. Because it's container provided, it's a safer place to carry out initialization ops: **If the method throws an unchecked exception the class MUST NOT be put into service except in the case of EJBs where the EJB can handle exceptions and even recover from them.**--That is part of the contract for `@PostConstruct`. There are no guarantees for anything you do in a constructor. In future JEE versions, more safety features can be built into the annotation that your code will be prepared for. – kolossus Feb 26 '13 at 16:03
  • @djmj,wont managing your converters within your managed bean unnecessarily tie the converter to the scope of your managed bean? Probably resulting in a converter living longer than it should? – kolossus Feb 26 '13 at 16:07
  • Good point about the `@PostConstruct` and Exceptions, but I assumed that no Exceptions would be thrown in Constructor anyways. – djmj Feb 26 '13 at 16:33
  • `Converter` This depends on the use case of my converters. If data is already loaded in bean as in the example there is no need to hit network or db again and the scope is already defined by the scope of the data objects. I wrote very generic converter for my entities which is within a client-dao instance which is created in the view-scoped beans. At the site I don't have to worry about converters since the component always uses the converter of the provided dao. Instead of writing many almost identical global converters This approach is very flexible if data is already in bean (dao) or lazy. – djmj Feb 26 '13 at 17:28
  • 1
    @djmj for viewParam an "@PostConstruct" is still too early. You need eg PreRenderView event handler for that. – Mike Braun Feb 26 '13 at 18:28
  • Oh yeah you are right thats how i did it. But I needed @PostConstruct for looking up other beans. – djmj Feb 26 '13 at 23:54
1

Unless I'm missing something, I don't think you ever instantiate your converter. You bind to a bean to get the converter, but where's the value?

I can understand you're not fond about the database access in the converter. It's almost never necessary to do this if you make use of one of the select item converters in OmniFaces.

Mike Braun
  • 3,729
  • 17
  • 15
  • well, I jsut read some stuffs about OmniFaces. I will try and keep you posted. But from what I understand, I must load my list from the database at some point (let's says in the @PostConstruct method of my managed bean). What I don't understand with the OmniFaces item converters is : how are they able to convert String to Object without knowing about the list I loaded or the database ? – facewindu Feb 26 '13 at 14:21
  • I implemented OmniFaces items converter, and along with the other things listed in this thread, everything is good now. Thanks ! – facewindu Feb 26 '13 at 17:44
  • Great it works! I think OmniFaces works by some reverse conversion algo: if 2 items convert to same string, it must be same object. Your list of objects from which you build select items must be there after post back. There'd also match by position in list. If you use this your list not only has to be there but it needs exact same objects in same order. This is same requirement as datatable has. View scope will satisfy this automatically. – Mike Braun Feb 26 '13 at 18:00