Wednesday, October 17, 2007

Populate child menu's

WARNING - OUTDATED CONTENT!

This article is targeted on JSF 1.2. For JSF 2.0, populating a child menu has become so much easier using <f:ajax>. See also this post for a code snippet.

Introduction

Having multiple h:selectOneMenu instances in one form which depends on each other and of which its values have to be obtained from the backing bean can drive JSF developers nuts. Especially if those are to be implemented in a form with at least one required field or if they are even required themselves. Validation errors ('Value not valid'), IllegalArgumentExceptions (at SelectItemsIterator#next()), unexpected submits (missing or wrong values), etcetera are flying around. You would almost become suicidal.

Back to top

Onchange, valueChangeListener, immediate, renderResponse and binding

To populate a child menu of which its contents is to be determined based on the value of the parent menu, you have to submit the form to the server on change of the parent menu. This can easily be achieved using the onchange attribute where you can specify some Javascript which have to be invoked when the menu is changed. Submitting the current form to the server using Javascript is simple, specifying "this.form.submit()" ought to be enough, or just submit() if you're lazy in typing.

You can use a valueChangeListener to retrieve the new value of the menu. But you of course want to prevent validation on the other required fields. This can technically be achieved by adding immediate="true" to all menu's so that all converters, validators and valuechange events of the menu's gets fired in the APPLY_REQUEST_VALUES phase instead of the PROCESS_VALIDATIONS phase and by adding the following line to the end of the valueChangeListener method:


FacesContext.getCurrentInstance().renderResponse();

This line will force a phase shift of the current phase to the RENDER_RESPONSE phase. When a valueChangeListener is invoked with immediate="true", then this line will cause the PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES and INVOKE_APPLICATION being skipped, so that the other components which doesn't have immediate="true" set won't be converted, validated, applied nor invoked. Also see the former article Debug JSF lifecycle for more insights in the JSF lifecycle.

One concern is that the skipping of the UPDATE_MODEL_VALUES will also cause that the new values of the menu's which have immediate="true" set won't be set in the backing bean. This can partly be fixed by getting the new value from the ValueChangeEvent inside the valueChangeListener method and assign it to the appropriate property. But this won't work for other menu's of which the valueChangeListener isn't been invoked. This would cause problems if you select a child menu value and then select the parent menu back to null and then reselect it to same value again, the child menu which will show up again would remain the same selection instead of null while its child will not be rendered! To solve this we need to bind the menu's to the backing bean so that we can use UIInput#setValue() and UIInput#getValue() to set and get the actual values. The JSF lifecycle will set and get them in the RESTORE_VIEW and RENDER_RESPONSE phases respectively.

Back to top

Basic JSF code example

Here is a basic JSF code example which demonstrates three h:selectOneMenu components of which the listing of the next menu depends on the selection of the current menu. The next menu will be hidden until the selection of the current menu has a valid value. In this example we'll use a basic tree structure of area's (countries, cities and streets), which would make the most sense. There is also another required input field added to demonstrate the menu's working flawlessly in conjunction with other components.

One important detail to be mentioned is that the requireness of this menu group is set in a valueless h:inputHidden component instead of in the last menu. This is done so because of the fact that the last menu would not be rendered when its parent menu doesn't have a valid selection, so it would be pointless to set a required attribute on the last menu.

This example is developed and tested using JSF 1.2_05 in a Java EE 5.0 environment with a GlassFish V2 application server.

<h:form>
    <h:panelGrid columns="2">
        <h:outputText value="Choose area" />
        <h:panelGroup>
            <h:selectOneMenu
                binding="#{myBean.countryMenu}" converter="areaMenuConverter"
                onchange="this.form.submit();" valueChangeListener="#{myBean.changeCountryMenu}"
                immediate="true">
                <f:selectItem itemLabel="Please select country" />
                <f:selectItems value="#{myBean.countryItems}" />
            </h:selectOneMenu>
            <h:selectOneMenu
                binding="#{myBean.cityMenu}" converter="areaMenuConverter"
                onchange="this.form.submit();" valueChangeListener="#{myBean.changeCityMenu}"
                immediate="true" rendered="#{myBean.countryMenu.value != null}">
                <f:selectItem itemLabel="Please select city" />
                <f:selectItems value="#{myBean.cityItems}" />
            </h:selectOneMenu>
            <h:selectOneMenu
                binding="#{myBean.streetMenu}" converter="areaMenuConverter" 
                rendered="#{myBean.cityMenu.value != null}">
                <f:selectItem itemLabel="Please select street" />
                <f:selectItems value="#{myBean.streetItems}" />
            </h:selectOneMenu>
            <h:inputHidden
                required="#{myBean.streetMenu.value == null}" requiredMessage="Area is required." />
        </h:panelGroup>

        <h:outputText value="Enter input" />
        <h:inputText
            value="#{myBean.input}" 
            required="true" requiredMessage="Input is required." />

        <h:panelGroup />
        <h:commandButton value="Submit" action="#{myBean.submit}" />
    </h:panelGrid>

    <h:messages infoStyle="color: green;" errorStyle="color: red;" />
</h:form>

And here is the appropriate backing bean:

package mypackage;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.faces.application.FacesMessage;
import javax.faces.component.html.HtmlSelectOneMenu;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;

public class MyBean {

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

    private static World world = new World();
    private List<SelectItem> countryItems = new ArrayList<SelectItem>();
    private List<SelectItem> cityItems = new ArrayList<SelectItem>();
    private List<SelectItem> streetItems = new ArrayList<SelectItem>();
    private HtmlSelectOneMenu countryMenu;
    private HtmlSelectOneMenu cityMenu;
    private HtmlSelectOneMenu streetMenu;
    private String input;

    {
        // Prefill country menu.
        fillAreaItems(countryItems, world.getAreas());
    }

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

    public void submit() {
        // Show selection and input results as informal message.
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage("You have chosen: " + countryMenu.getValue() + ", "
                + cityMenu.getValue() + ", " + streetMenu.getValue()
                + " and you have entered: " + input));
    }

    // Changers -----------------------------------------------------------------------------------

    public void changeCountryMenu(ValueChangeEvent event) {
        // Get selected country.
        Country country = (Country) event.getNewValue();

        if (country != null) {
            // Fill city menu.
            fillAreaItems(cityItems, country.getAreas());
        }

        // Reset child menu's. This is only possible when using component binding.
        cityMenu.setValue(null);
        streetMenu.setValue(null);

        // Skip validation of non-immediate components and invocation of the submit() method.
        FacesContext.getCurrentInstance().renderResponse();
    }

    public void changeCityMenu(ValueChangeEvent event) {
        // Get selected city.
        City city = (City) event.getNewValue();

        if (city != null) {
            // Fill street menu.
            fillAreaItems(streetItems, city.getAreas());
        }

        // Reset child menu. This is only possible when using component binding.
        streetMenu.setValue(null);

        // Skip validation of non-immediate components and invocation of the submit() method.
        FacesContext.getCurrentInstance().renderResponse();
    }

    // Fillers ------------------------------------------------------------------------------------

    private static <A extends Area<?>> void fillAreaItems(List<SelectItem> areaItems, Set<A> areas) {
        areaItems.clear();
        for (A area : areas) {
            areaItems.add(new SelectItem(area, area.getName()));
        }
    }

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

    public List<SelectItem> getCountryItems() {
        return countryItems;
    }

    public List<SelectItem> getCityItems() {
        return cityItems;
    }

    public List<SelectItem> getStreetItems() {
        return streetItems;
    }

    public HtmlSelectOneMenu getCountryMenu() {
        return countryMenu;
    }

    public HtmlSelectOneMenu getCityMenu() {
        return cityMenu;
    }

    public HtmlSelectOneMenu getStreetMenu() {
        return streetMenu;
    }

    public String getInput() {
        return input;
    }

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

    public void setCountryMenu(HtmlSelectOneMenu countryMenu) {
        this.countryMenu = countryMenu;
    }

    public void setCityMenu(HtmlSelectOneMenu cityMenu) {
        this.cityMenu = cityMenu;
    }

    public void setStreetMenu(HtmlSelectOneMenu streetMenu) {
        this.streetMenu = streetMenu;
    }

    public void setInput(String input) {
        this.input = input;
    }

}

The relevant part of the faces-config.xml file look like:

<converter>
    <converter-id>areaMenuConverter</converter-id>
    <converter-class>mypackage.AreaMenuConverter</converter-class>
</converter>
<managed-bean>
    <managed-bean-name>myBean</managed-bean-name>
    <managed-bean-class>mypackage.MyBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

If you want to keep the bean in request scope (which is a very reasonable requirement), then you need to install Tomahawk and 'cache' the bean for the next request only using <t:saveState />.

Back to top

Menu structure

As said earlier, we're demonstrating the working of the menu's using a tree structure of area's: countries, cities and streets, because that would make the most sense. It is not necessary to take over exactly such a structure for your menu's. Just do whatever you find the best and easiest way to use, access and maintain menu items. At least I like the following relatively simple parent-child structure and the converter.

Here is the abstract class Area (please note the implementation of equals() and hashCode(), this is very important for JSF, also see Objects in h:selectOneMenu):

package mypackage;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public abstract class Area<A extends Area<?>> {

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

    private String name;
    private Set<A> areas;
    private Area<?> parent;

    // Constructors -------------------------------------------------------------------------------

    protected Area(String name, A[] areas) {
        this.name = name;
        this.areas = new TreeSet<A>(new AreaComparator<A>());
        for (A area : areas) {
            area.parent = this;
            this.areas.add(area);
        }
    }

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

    public String getName() {
        return name;
    }

    public Set<A> getAreas() {
        return areas;
    }

    public Area<?> getParent() {
        return parent;
    }

    // Helpers ------------------------------------------------------------------------------------

    public boolean equals(Object other) {
        return other instanceof Area
            && this.getClass().equals(other.getClass())
            && name.equals(((Area<?>) other).name);
    }

    public int hashCode() {
        return this.getClass().hashCode() + name.hashCode();
    }
    
    public String toString() {
        return name;
    }

}

class AreaComparator<A extends Area<?>> implements Comparator<A> {

    // Invokes natural sorting on area name.
    public int compare(A a1, A a2) {
        return a1.getName().compareTo(a2.getName());
    }

}

And here is the class representing the World which is nothing more or less than a placeholder of the tree structure of area's. Note the stub constructor, it prefills the tree structure.

package mypackage;

public class World extends Area<Country> {

    // Constructors -------------------------------------------------------------------------------

    public World(String name, Country[] countries) {
        super(name, countries);
    }

    public World() {
        // Stub constructor.
        // May be replaced by DAO, configuration files, or wherever you want to store this stuff.
        this("Earth", new Country[] {
            new Country("The Netherlands", new City[] {
                new City("Haarlem", new Street[] {
                    new Street("Palamedesstraat"),
                    new Street("Vergierdeweg"),
                    new Street("Marsstraat")
                }),
                new City("Amsterdam", new Street[] {
                    new Street("Gyroscoopstraat"),
                    new Street("Albert Cuypstraat"),
                    new Street("De Boelelaan")
                }),
                new City("Almere", new Street[] {
                    new Street("Tarantellastraat"),
                    new Street("Salsastraat"),
                    new Street("Hollywoodlaan")
                })
            }),
            new Country("United States", new City[] {
                new City("New York", new Street[] {
                    new Street("Central Park West"),
                    new Street("Park Avenue"),
                    new Street("Amsterdam Avenue")
                }),
                new City("Los Angeles", new Street[] {
                    new Street("Main Street"),
                    new Street("Broadway"),
                    new Street("Olympic Boulevard")
                }),
                new City("Miami", new Street[] {
                    new Street("Miami Avenue"),
                    new Street("Biscayne Boulevard"),
                    new Street("Venetian Way")
                })
            }),
            new Country("France", new City[] {
                new City("Paris", new Street[] {
                    new Street("Avenue des Champs Elysees"),
                    new Street("Quai d'Orsay"),
                    new Street("Rue La Fayette")
                }),
                new City("Lyon", new Street[] {
                    new Street("Cours Lafayette"),
                    new Street("Quai Victor Augagneur"),
                    new Street("Rue Garibaldi")
                }),
                new City("Marseille", new Street[] {
                    new Street("Boulevard Longchamp"),
                    new Street("Rue de Rome"),
                    new Street("Cours Lieutaud")
                })
            })
        });
    }

}

The Country class:

package mypackage;

public class Country extends Area<City> {

    // Constructors -------------------------------------------------------------------------------

    public Country(String name, City[] cities) {
        super(name, cities);
    }

}

The City class:

package mypackage;

public class City extends Area<Street> {

    // Constructors -------------------------------------------------------------------------------

    public City(String name, Street[] streets) {
        super(name, streets);
    }

}

The Street class:

package mypackage;

public class Street extends Area<Area<?>> { 

    // Constructors -------------------------------------------------------------------------------

    public Street(String name) {
        super(name, new Area[0]);
    }

}

And finally the Converter to be used in menu's to convert between the Area type and String type and vice versa:

package mypackage;

import java.util.Set;

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

public class AreaMenuConverter implements Converter {

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

    private static final String AREAS = "AreaMenuConverter.areas";

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

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value != null) {
            // Cast back to Area.
            Area<?> area = (Area<?>) value;

            // Store the areas as component attribute so that they are available in getAsObject().
            // Those represents the same values as those in f:selectItems.
            component.getAttributes().put(AREAS, area.getParent().getAreas());

            // Return String representation of area.
            return area.getName();
        }

        return null; // Value is null.
    }

    @SuppressWarnings("unchecked")
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value != null) {
            // Get the areas back which were stored as component attribute in getAsString().
            Set<Area<?>> areas = (Set<Area<?>>) component.getAttributes().get(AREAS);

            // Compare name of each area with selected value.
            for (Area<?> area : areas) {
                if (area.getName().equals(value)) {
                    // Return matched area object.
                    return area;
                }
            }
        }

        return null; // Value is null or doesn't have any match.
    }

}

That was it! You can in fact just copypaste and run it all without any changes and then play/experiment with it further.

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) October 2007, BalusC

43 comments:

José said...

Thank you for this blog entry, very nice. I feel though, that it shouldn't be this difficult for such a simple task...

How about a link where this can be seen?

BalusC said...

If validations and submits on change weren't required, then it would indeed be much more simple. Also, if you are ready for AJAX techniques, then you can also consider Ajax4jsf with a Phaselistener to fill the child menu. But this is actually out of the scope of this article.

No, there is no live example of this article. Just copy'n'paste the example to your JSF playground and run it.

Doss said...

when i can execute this sample program i got the below error event i have implemented the seriaziable interface for all area classes.
---------------------
StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.io.NotSerializableException: mexsyswar.com.mexsys.dom.Area$1
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1081)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
at java.util.TreeSet.writeObject(TreeSet.java:458)

---------------------
By Doss P

BalusC said...

Apparently you've configured your application server so that it should persist sessions. This will require that all session attributes are serializable.

The $1 in "Area$1" points to the first occurred inner class, which is the Comparator in this case.

Best what you can do is to implement a separate Comparator class (AreaComparator?) which on its turn implements Serializable. Finally construct and use it as follows:

Comparator<A> areaComparator = new AreaComparator<A>();

Russell said...

Thank you so much for this and all your JSF posts, I constantly come to your site and it helps me out a great deal.

Referring to your example in this post, I am curious if you (or anyone) have any suggestions about performing the same functionality for uses where the bean is set to 'request' scope?

I typically keep all my beans set to request scope (for many reasons, but not for discussion here), which obviously causes issues when submitting and 'refreshing' teh page...

Thanks again for your posts and your time!

-Russell

puiutzu said...

For Russell: try to use t:saveState.

BalusC said...

You might try to move the fillCityItems() and fillStreetItems() calls from the valueChangeListener methods to the getCityItems() and getStreetItems() respectively. You can get the Country and City objects by countryMenu.getValue() and cityMenu.getValue(). Call them using lazy loading, e.g.

public List<SelectItem> getCityItems() {
    if (cityItems == null) {
        Country selectedCountry = (Country) countryMenu.getValue();
        fillCityItems(selectedCountry.getAreas());
    }
    return cityItems;
}

With this you can remove the both fill* calls from the valueChangeListener methods.

Though I am not 100% sure if validation would bite it or not.

BalusC said...

Special for Doss: I've just changed the Area class so that it makes implementing Serializable more easy. I've refactored the anonymous inner Comparator class to a real inner AreaComparator class.

Raja said...

Very very good tutorial/example in JSF...

Juan Vidal said...

Tks for all this code... could you bring me an example about how ti fill de data with DAO... or something like that... i will appreciate!!!

Hanibal said...

This the greatest post ever ,it soled me alot of problems,can you please put and example using the Ajax to fill the menu
thnks

Kavita said...

Are there any ways to handle drop downs that get values from database.
For Example :
I am requesting data from the database based on a id(entered in an input text field), Next I want the drop down(bound to a database column) to change and reflect the appropriate value for the entered id
( I am trying to implement a non Ajax way)

BalusC said...

Just change the 'World' class accordingly.

Kavita said...

I have implemented the drop down, let's say I have a text field which takes in the 'country' and a drop down showing all 'capitals' from a database table.When the page loads, my text field is empty and my drop down shows complete list. But when I type a country , my drop down should auto select the 'capital' based on the 'country'.

Kavita said...

Never Mind I got it
I was missing the value attribute for selectOneMenu

srinivas said...

I have to display the retrive the records based on the dropdown selected value.This is what i need to implement.

In the existing code the record size is picking from properties file when the page is getting loaded.It was implemented using grid view which contains attributes as pagesize and onchangepazelistner.





I added one drop down box to my jsp with valuechangelistner method.



The functionality is not working properly.Eventhough i am submitting the page the records are not getting updated.But the records are getting update only when i click on the refresh button.


Help would be appriciated..

Kavita said...

I tried to recreate an example as follows :

h:selectOneMenu id="country" onchange="this.form.submit();" value="#{SessionBean1.selected_country}" valueChangeListener="#{SessionBean1.countryListprocessvaluechange}"
f:selectItems id="dropdown1SelectItems" value="#{SessionBean1.countryList}"
h:selectOneMenu
h:selectOneMenu id="capital" #{SessionBean1.selected_capital}"
f:selectItems id="dropdown1SelectItems1" value="#{SessionBean1.capitalsList}"

In my Session Bean:

public void countryListprocessvaluechange(ValueChangeEvent event){
if(event.getNewValue().equals("somecountry")) selected_capital="somecapital";
}

No immediate attributes at all, just a simple jsf project, but I cannot get the 'capitals' dropdown to show the selected value.

I may have wrong understanding but when eventually when JSF gets to Render Response phase it should invole the getter on selected_country and return the new value right?
I might be missing something.
Note: This can be easily done using binding instead of value. But I need clarification as to why?

Kavita said...

Very Sorry for the awful code formatting. I could not get myself
to post it as it is.

BalusC said...

Sorry, that's already been explained in the article's text. Read it carefully. Learn about the JSF lifecycle. Copy the provided code example unchanged to your sandbox environment and play with it.

Kavita said...

Got it! I just read the article very minutely! Thanks

botski007 said...

Brilliant piece! I have one question: does the scope of the managed bean have to be "session"? If so, can you point me to where this is documented?

BalusC said...

If you want to keep it request scoped, you at least need to save the state of the options. The Tomahawks t:saveState may come in handy.

deinte said...

Hi.

I have a similar page to the example with just two h:selectOneMenu.


public void selectOneMenu_01Changed(ValueChangeEvent event) {
//logic
}

public void selectOneMenu_02Changed(ValueChangeEvent event) {
//logic
}

but I can't identify wich element in the page was selected at least the first time, because two method are fired in the same order, so if I select selecOneMenu_01 in the page is fired: selecOneMenu_01, selecOneMenu_02, but if I select selecOneMenu_02 in the page is fired selecOneMenu_01, selecOneMenu_02.

I hope I have explained myself.

I have a simple standalone project. If someone want to take a look.

Thanks

Hari said...

Hi,

Thanks for the blog. I have changed all the names and implemented the same. I am getting the first dropdown list and when i changed the value of first dropdown i am getting an error page saying ..


javax.servlet.ServletException: java.io.InvalidClassException: com.sample.Model; no valid constructor
javax.faces.webapp.FacesServlet.service(FacesServlet.java:121)
com.sample.filter.AuthenticationFilter.doFilter(AuthenticationFilter.java:88)

BalusC said...

Supply a valid (no-arg) constructor.

Ogi said...

excellent excellent piece of writing. Thank you.

I am still trying to figure this out for "request" scope though. You previously suggest using t:saveState, from which library was that>

Jaimico said...

Ogi, the t:saveState is from the Apache Tomahawk project, you can download from here http://myfaces.apache.org/tomahawk/index.html, there're also instructions for installing, just one note, if you're going to use it with jsf 1.2 you'll have to download the tomahawk 1.1.8 for jsf 1.2, and you'll also need it dependency: FileUpload http://commons.apache.org/fileupload/).

Jaimico said...

Hello BalausC, your post is of great help when I'm creating a new record of... let's say a person who has a state of birth that depends on a country.

But I have a problem: when I'm editing an existent record the countries selectOneMenu appears with all the countries and is selected the country of birth of the person (which is OK), but the state appears empty, I think it's because the states are not populated until the countrie changes.

¿Is there a way of populate the states before the view shows?, I tried modifying the setter for the HtmlSelectOneMenu of the countries but it didn't worked because the component starts with a null value.

Thanks!!!

Jaimico said...

Well... I guess I managed to (kind of) resolve the problem, in the managed bean that loads the countries and states I look for the person bean, if I find it and then watch for the person's country and load all states related to that country.

It's just a workarround to win time, I'll see if I can find a better way n_n.

kapil said...

Hi,
I am having problem in changing the country combo. When I change the option and after the value change event, the original value is shown selected in the country combo instead of new value. Am I missing something here?

kapil said...

Actually it is working fine as long as I don't persist my form (containing other data). After I persist the data, same information is shown after opening the page again as the bean is in session scope. If I change scope to request, the combo change is working fine but when I submit, I receive error the value is not valid

Mark Snider said...

Hi BalusC,
I just wanted tou thank you! Your article helped me quickly fix a bug that I believe would have otherwise taken a lot of time!

Thanks,
Mark Snider
Research Programmer Ohio State University

Aleksandar said...

Hi BalusC,
I just wanted to thank you! Your article helped me quickly figure out mess with refreshing data in user interface.
Thank you,
Aleksandar Lukic
Software Developer
National Bank Of Serbia

Ramesh said...

Hi BalusC

I am having two select box JSF components in the jsp, and both has two separate custom value change listener’s class. When I try to change value of one select box component both the value change Listener is getting called. When I analyzed the problem I found that the old value passed is always empty in the Value change Listener class. Because of this the new value and old value is not getting matched and the value change Listener is getting called.

If i change the scope of the backing bean request to session, it is working fine. only i am facing issue in request scope.

Please share your ideas.

Suresh Kerai said...

Thanks JSF tutorial is very user full me.. thanks lot..

hajar said...

hey,
first i want to thank you, this article is very interesting.

second, i want you to help me if it's possible. i'm a beginner in jsf framework, i'm using your code in my application and it works very well, but i would like to use data from database instead of data from file World. my structure DB is like this :

country(code varchar(4), name varchar(35)) PRIMARY KEY (`code`);


city(id int, name varchar(35), code_pays varchar(4)) PRIMARY KEY (`id`);

street(id int, name varchar(50), id_city int) PRIMARY KEY (`id`)

sorry if my english is not good :p

and thanks for your help.

star said...

Hi!

Is is possible to run with Tomcat 6?

Ameya said...

@BalusC Can this work with Ajax I am looking for Dynamic DropDrown with Ajax using ajax4j

Tilman Hausherr said...

this line

area.parent = this;

doesn't work because parent is private.

I never got the example to work, because of this:
javax.servlet.ServletException: java.lang.InstantiationException: Country

not sure if this is related to me removing the "private".

BalusC said...

@Tilman: the example compiles and runs. What's the root cause of the `InstantiationException`?

Tilman Hausherr said...

root cause
java.lang.InstantiationException: Country
java.lang.Class.newInstance0(Class.java:340)
java.lang.Class.newInstance(Class.java:308)
javax.faces.component.StateHolderSaver.restore(StateHolderSaver.java:107)
javax.faces.component.ComponentStateHelper.restoreState(ComponentStateHelper.java:292)
javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1444)
javax.faces.component.UIOutput.restoreState(UIOutput.java:255)
javax.faces.component.UIInput.restoreState(UIInput.java:1359)
com.sun.faces.application.view.StateManagementStrategyImpl$2.visit(StateManagementStrategyImpl.java:231)
com.sun.faces.component.visit.FullVisitContext.invokeVisitCallback(FullVisitContext.java:147)
javax.faces.component.UIComponent.visitTree(UIComponent.java:1446)
javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.component.UIForm.visitTree(UIForm.java:333)
javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
com.sun.faces.application.view.StateManagementStrategyImpl.restoreView(StateManagementStrategyImpl.java:223)
com.sun.faces.application.StateManagerImpl.restoreView(StateManagerImpl.java:177)
com.sun.faces.application.view.ViewHandlingStrategy.restoreView(ViewHandlingStrategy.java:131)
com.sun.faces.application.view.FaceletViewHandlingStrategy.restoreView(FaceletViewHandlingStrategy.java:430)
com.sun.faces.application.view.MultiViewHandler.restoreView(MultiViewHandler.java:143)
com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:199)
com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110)
com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:312)
org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)

Tilman Hausherr said...

Some more things:
1) The crash happens after I've selected a country and then a city.
2) I use JDK 1.6 with Netbeans
3) I'm using annotations instead of the faces-config stuff.
4) I've just moved the whole stuff to a separate project to be sure, and remove the annotations and used the faces-config stuff; the crash still happens.

BalusC said...

The exception message suggests that you didn't put the class in a package. Classes in the default package are invisible to classes in a normal package. Always put public classes in a package.

But you told about annotations, which in turn suggests that you're using JSF 2.0. This article is written with JSF 1.2 in mind and I don't gurarantee that it works in JSF 2.0 due to changes in state saving. I'd recommend to utilize the JSF 2.0 <f:ajax> tag. Sorry, I don't have an article for that yet, but I'll keep it in mind :)