0

In my application I have a relationship between cities (A) and postalcodes (B).

So I have two SelectOneMenu's where the first represents A's and the second represents the B's of the selected A which are updated via ajax.

Since I use these two menu's multiple times i wrote a custom tag for them which usually works fine aslong the A and B values are assigned to existing variables in my session bean.

But there is a case where I want to pass a new relationship object to my bean which is not working using a tag component.

The properties A and B of the new request scoped AToB Object which I want to pass as an argument to the add method of my logic session bean are null!

*Additional Notes

  1. Using the tag component with existing session bean variables works.
  2. Using tag component to add a new request bean based relationship object does not work.
  3. The selected A is always (in case 1 and 2) passed correctly to the bean using ajax change event.
  4. The converters for the SelectOneMenu's always work. (Tested in debug mode)
  5. Using the tag component JSF never calls the setters to set A and B of the new AToB Object, tested in debug mode.
  6. Using jsf default h:commandButton instead of primefaces produces same error.
  7. It works perfectly not using the tag component!

Environment:

Mojarra 2.14
GlassFish 3.01

I simplified the classes removing constructors and getter & setters.

DTO, Pojo:

public Class A{}

public Class B{}

public Class AToB
{
    private A a;
    private B b;
}

Session Bean:

public Class Bean
{
    private List<A> as;
    private List<B> bs;

    private AConverter aConverter;
    private BConverter bConverter;

    //updates bs list
    public void updateBsByA(AjaxBehaviorEvent event) {}

    //does something with the given AToB
    public void add(AToB aToB)
}

faces-config.xml:

<managed-bean>
    <managed-bean-name>beanLogic</managed-bean-name>
    <managed-bean-class>package.Bean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
    <description>new AToB Object I want to add at database</description>
    <managed-bean-name>newAToB</managed-bean-name>
    <managed-bean-class>package.Bean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

abSelect tag file:

<ui:composition
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui"
    xmlns:abcd="http://store24.de/jsf/admin">
    <h:selectOneMenu value="#{tagA}" converter="#{beanLogic.aConverter}">
        <f:selectItems value="#{beanLogic.as}"/>
        <p:ajax event="change" listener="#{beanLogic.updateBsByA}" update="selectB"/>
    </h:selectOneMenu>
        <h:selectOneMenu id="selectB" value="#{tagB}" converter="#{beanLogic.bConverter}">
        <f:selectItems value="#{beanLogic.bs}"/>
    </h:selectOneMenu>
</ui:composition>

abcd.taglib.xml

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib 
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
    version="2.0">
    <namespace>http://abcd.com/jsf</namespace>
    <tag>
        <tag-name>abSelect</tag-name>
        <source>tags/abSelect.xhtml</source>
        <attribute>
            <display-name>a</display-name>
            <name>tagA</name>
            <required>true</required>
            <type>package.A</type>
        </attribute>
        <attribute>
            <display-name>a</display-name>
            <name>tagB</name>
            <required>true</required>
            <type>package.B</type>
        </attribute>
    </tag>
</facelet-taglib>

calling site:

<abcd:abSelect a="#{newAToB.a} b="#{newAToB.b}"/>
<p:commandButton value="add" action="#{beanLogic.add(newAToB)}"/>

Instead adding a new AToB without the tag component works (see above #2)

<h:selectOneMenu value="#{newAToB.a}" converter="#{beanLogic.aConverter}">
    <f:selectItems value="#{beanLogic.as}"/>
    <p:ajax event="change" listener="#{beanLogic.updateBsByA}" update="selectB"/>
</h:selectOneMenu>
    <h:selectOneMenu id="selectB" value="#{newAToB.b}" converter="#{beanLogic.bConverter}">
    <f:selectItems value="#{beanLogic.bs}"/>
</h:selectOneMenu>
<p:commandButton value="add" action="#{beanLogic.add(newAToB)}"/>
djmj
  • 5,579
  • 5
  • 54
  • 92
  • You Could use more descriptive names for beans - and don't use Bean as a managed bean's name - I dont know why, but this sometimes makes bean "invisible" for JSF. You could also write the whole code of this composite component (not sure if it can be also called custom tag jn JSF) and how do you use it. – Damian Mar 10 '12 at 02:54
  • I added full code. Yes "bean" was just to simplify my code. – djmj Mar 10 '12 at 04:19

1 Answers1

0

Do you really need AToB class? Can't private A a; and private B b; be in session scoped bean? The main problem I have found recreating your code is that request scooped AToB is recreating every time I wanted to use it - just simply add System.out.println() to it's constructor to see it. I don't know why it is happening. But I would use JSF dependency injection to do what you probably want (Inject AToB to the session bean).

You are probably missing Converters (not working without them ) - change p:commandButton to h:commandButton to see what I mean. I dont know why Primefaces's buttons don't throws any error, but I have seen this before.

It would be easier if you would use string to present A and B in selectMany, and decode and recreate it just before processing.

That is what works for me:

web.xml

  ...
  <!-- registering my custom tag -->
    <context-param>
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
        <param-value>/WEB-INF/mytags.taglib.xml</param-value>
    </context-param>

mytags.taglim.xml

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib 
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
    version="2.0">
    <namespace>http://mytags.com/jsf</namespace>
    <tag>
        <tag-name>abSelect</tag-name>
        <source>tags/mytags/abselect.xhtml</source>
        <attribute>
            <display-name>a</display-name>
            <name>tagA</name>
            <required>true</required>
            <type>mytagstest.A</type>
        </attribute>
        <attribute>
            <display-name>a</display-name>
            <name>tagB</name>
            <required>true</required>
            <type>mytagstest.B</type>
        </attribute>
    </tag>
</facelet-taglib>

faces-confix.xml

...
<!-- Converters -->
<converter>
    <converter-id>aConverter</converter-id>
    <converter-class>mytagstest.AConverter</converter-class>
</converter>
<converter>
    <converter-id>bConverter</converter-id>
    <converter-class>mytagstest.BConverter</converter-class>
</converter>

abselect.xml

<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
>
<h:selectOneMenu value="#{tagA}">  <!--  {aBBean.a} or {tagA} -->
    <f:converter converterId="aConverter" />
    <f:selectItems value="#{aBBean.as}"/> 
    <!--I dont know why you are tyring to update sth in  aBBean, new value from menu is used to update tagA -->
    <f:ajax event="change" listener="#{aBBean.updateBsByA}" execute="@form"/>
</h:selectOneMenu>
<h:selectOneMenu id="selectB" value="#{tagB}"> <!--  {aBBean.b} or {tagB} -->
    <f:converter converterId="bConverter" />
    <f:selectItems value="#{aBBean.bs}"/>
    <f:ajax event="change" listener="#{aBBean.updateBsByA}" execute="@form"/>
</h:selectOneMenu>

A: #{aBBean.a} B: #{aBBean.b}

Notice that if you don't really need AToB you can just skip atributes in your tag, and saves your user choice in your session bean. This would be probably much cleaner.

A.java (notice equals and hashCode method)

package mytagstest;

import java.io.Serializable;

public class A  implements Serializable{

    private String name ="";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }



    public A() {
    }


    A(String n) {
      this.name = n;
    }

    @Override
    public String toString() {
        return "A{" + "name=" + name + '}';
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final A other = (A) obj;
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 19 * hash + (this.name != null ? this.name.hashCode() : 0);
        return hash;
    }



}

Converter for this class (if you replace p:commandButton with h:commandButton you will see that there are some error that creating converter fix )

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;


public class AConverter implements Converter {



    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        // Convert the unique String representation of an object to the actual object.
        return new A(value); //Dont need to create new
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        // Convert the Foo object to its unique String representation.
        return ((A) value).getName();
    }

}

Class B and BConverter looks almost the same.

AToB.java

@ManagedBean
@RequestScoped
public class AToB implements Serializable {

    private A a;
    private B b;

    public AToB() {
        System.out.println("Creating AToB");
    }

    public A getA() {
        System.out.println("Get a");
        return a;
    }

    public void setA(A a) {
        System.out.println("set b");
        this.a = a;
    }

    public B getB() {
        System.out.println("get b");
        return b;
    }

    public void setB(B b) {
        System.out.println("set b");
        this.b = b;
    }
}

ABBEan (Session scoped bean, there are methods for both cases - using AToB and not)

   @ManagedBean
    @SessionScoped
    public class ABBean implements Serializable {

        private List<A> as = new ArrayList<A>();
        private List<B> bs = new ArrayList<B>();
        private A a;
        private B b;

        public A getA() {
            System.out.println("Get a");
            return a;
        }

        public void setA(A a) {
            System.out.println("set b");
            this.a = a;
        }

        public B getB() {
            System.out.println("get b");
            return b;
        }

        public void setB(B b) {
            System.out.println("set b");
            this.b = b;
        }

        public void updateAB() {
            System.out.println("Selected A and B are:");
            System.out.println(a + " " + b);
        }

        public ABBean() {
            System.out.println("Creating ABBean");
            A a1 = new A("a1");
            B b1 = new B("b1");
            this.a = a1;
            this.b = b1;

            as.add(a1);
            as.add(new A("a2"));
            bs.add(b1);
            bs.add(new B("b2"));
        }

        //updates bs list
        public void updateBsByA(AjaxBehaviorEvent event) {
            System.out.println("updateBsByA");
            System.out.println("Selected A and B are:");
            System.out.println(a + " " + b);
        }

        public List<A> getAs() {
            return as;
        }

        public void setAs(List<A> as) {
            this.as = as;
        }

        public List<B> getBs() {
            return bs;
        }

        public void setBs(List<B> bs) {
            this.bs = bs;
        }

        //does something with the given AToB
        public void add(AToB aToB) {
            System.out.println("AToB added! - aToB");
            System.out.println(aToB.getA() + " " + aToB.getB());

        }

        public void add(A a, B b) {
            System.out.println("AToB added! - A a, B b");
            System.out.println(a + " " + b);

        }
    }

usage:

<h:form>  
  <mt:abSelect  tagA="#{aBBean.a}" tagB="#{aBBean.b}"/>
  <p:commandButton   value="add2works" action="#{aBBean.updateAB}"/> <!-- works -->
</h:form>

or

  <h:form>  
  <mt:abSelect  tagA="#{aToB.a}" tagB="#{aToB.b}"/>
    <p:commandButton  value="add7" action="#{aBBean.add(aToB)}"/>
 </h:form>
Damian
  • 2,930
  • 6
  • 39
  • 61
  • Thanks for that very long answer but I did not added the converters and full A, B and AToB code to narrow the error down. Since I mentioned that "It works perfectly not using the tag component!", I thought that would narrow down the error. I will edit my question to add more informations – djmj Mar 10 '12 at 22:11
  • I added new point #5 which seems like a bug to me, why should it work in the ajax change event and not in the final button event. – djmj Mar 10 '12 at 22:38
  • 1
    Ok. But what about resigning from newAToB ? Or just injecting it into session bean using @ManagedProperty(value="#{newAToB}" ? – Damian Mar 10 '12 at 22:45
  • That is of cause one choice, as you previously mentioned I could also keep that newAToB in session scope. But I try to reduce my session load and my session beans code as much as possible. And since JSF 2.0 "supports" passing arguments to beans that's my first option. – djmj Mar 10 '12 at 22:54
  • edit: The second option will work only with newAtoB beaing in session scope too, obviously ; ). – Damian Mar 10 '12 at 22:56
  • No, as said before (see #7). It works perfectly without wrapping the code in a tag component! I tested it now using a and b as single arguments. It does not work either. Somehow a new request bean cannot be initialized or its property inside a tag! – djmj Mar 10 '12 at 23:00
  • 1
    I mean something that I have showed in my code - just add a and b field to your session bean instead holding them in request scoped bean, you can also mart them transient, presenting them from being serialized - and stored in session. – Damian Mar 10 '12 at 23:07
  • Have you tried if you request bean is created only once in you code? I was not true for me, so this also would mean that it can't be used to store any value. Making it View scoped works for me, but storing a and b in session bean, still seems the easiest way. And View scope in only available in pure JSF not in CDI that you may want to use with EJB container. I have always been using JSF's composite components instead custom tags, and all worked for me. – Damian Mar 10 '12 at 23:22