NOTE: For the recommended, clustering-safe way to handle Selects with lists of objects, please see the Using Select with a List cookbook recipe.

Thiago H. de Paula Figueiredo: this approach is plain wrong. You have to retrieve a list of objects when you need just one of them (wrong) or put a list in a session (very wrong). In addition, the same class implementing two completely different interfaces is not a good idea. Just create one instance of OptionModelImpl for each of your options (objects) and pass them to a SelectModelImpl.

Davor Hrg: Thiago's comment should be considered, but the fact is that you should not use a select component in any situation where the number of elements is so big the performance or memory footprint becomes a problem (in that case the list is definitely too long for users to scroll through, and use AutoComplete instead). Also Thiago provides no example page of his so called simpler solution.

Creating a <SELECT> is a bit complicated and there is only support for strings and enums currently. (T5.0.13)

see also: Tapestry5SelectObject

Tapestry Select uses:

  • SelectModel to get groups and options, and it is responsible for displayed text (<option value=..>TEXT</option>)
  • ValueEncoder to generate value parameter for <option> tag and restore the selected value back after form is submitted

Eums and strings are ok, but many people will want to use Objects (Beans, POJO-s from database via ORM). This has proven to be a dificult task, especially since we are all new to T5.

Here is an example of a SelectionModel that simplifies using Select component with objects. It requires 5 parameters

  • Collection<T> - the list of objects that can be selected (a Collection type of list)
  • Class<T> - Superclass of all objects in the list (this is because of Generics deletion at runtime) so an adapter can be produced (even byte code generated one might be implemented in Tapestry-ioc later on)
  • name of the identifier property - a property that identifies the object (usually id if object is from database), it will be used as value attribute for the <option>
  • name of the name property - if name is composed of few properties, add a transient property to your Object
  • PropertyAccess - this is an Tapestry-ioc service that the selection model uses to access properties from the object and you must supply it. You can inject it into your page easily
    @Inject private PropertyAccess _access;
    

Another catch is that Value encoder needs the original list to recreate the object later after the form is submitted. So we implement ValueEncoder interface directly in our SelectModel implementation, and supply the model both as model and encoder parameter of Select component.

<t:select model="myModel" encoder="myModel" value="someBean"/>

Here's code for the SelectModel implementation: GenericSelectModel

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.tapestry.OptionGroupModel;
import org.apache.tapestry.OptionModel;
import org.apache.tapestry.ValueEncoder;
import org.apache.tapestry.internal.OptionModelImpl;
import org.apache.tapestry5.internal.OptionGroupModelImpl;
import org.apache.tapestry.ioc.services.PropertyAccess;
import org.apache.tapestry.ioc.services.PropertyAdapter;
import org.apache.tapestry.util.AbstractSelectModel;

/** Generic selection model for a list of Objects.
 * use:
 * <pre>@Inject private PropertyAccess _access;</pre>
 * in your page to ge the {@link PropertyAccess} service.<br>
 * !Notice: you must set the created instance both as model and encoder parameter for the {@link Select} component.*/
public class GenericSelectModel<T> extends AbstractSelectModel implements ValueEncoder<T> {

    private PropertyAdapter labelFieldAdapter;
    private PropertyAdapter idFieldAdapter;
    private Collection<T>         list;

    public GenericSelectModel(Collection<T> list, Class<T> clazz, String labelField, String idField, PropertyAccess access) {
        this.list = list;
        if (idField != null)
            this.idFieldAdapter = access.getAdapter(clazz).getPropertyAdapter(idField);
        if (labelField != null)
            this.labelFieldAdapter = access.getAdapter(clazz).getPropertyAdapter(labelField);
    }

    public void addOptionGroup(String label, boolean disabled, List<T> options) {
        List<OptionModel> optionModels = new ArrayList<OptionModel>();
        if (labelFieldAdapter == null) {
            for (T obj : options) {
                optionModels.add(new OptionModelImpl(nvl(obj), obj));
            }
        } else {
            for (T obj : options) {
                optionModels.add(new OptionModelImpl(nvl(labelFieldAdapter.get(obj)), obj));
            }
        }

        if (optionGroups == null) {
            optionGroups = new ArrayList<OptionGroupModel>();
        }

        optionGroups.add(new OptionGroupModelImpl(label, disabled, optionModels, new String[0]));
    }

    public List<OptionGroupModel> getOptionGroups() {
        return null;
    }

    public List<OptionModel> getOptions() {
        List<OptionModel> optionModelList = new ArrayList<OptionModel>();
        if (labelFieldAdapter == null) {
            for (T obj : list) {
                optionModelList.add(new OptionModelImpl(nvl(obj)));
            }
        } else {
            for (T obj : list) {
                optionModelList.add(new OptionModelImpl(nvl(labelFieldAdapter.get(obj)), obj));
            }
        }
        return optionModelList;
    }

    // ValueEncoder functions
    public String toClient(T obj) {
        if (idFieldAdapter == null) {
            return obj + "";
        } else {
            return idFieldAdapter.get(obj) + "";
        }
    }

    public T toValue(String string) {
        if (idFieldAdapter == null) {
            for (T obj : list) {
                if (nvl(obj).equals(string)) return obj;
            }
        } else {
            for (T obj : list) {
                if (nvl(idFieldAdapter.get(obj)).equals(string)) return obj;
            }
        }
        return null;
    }

    private String nvl(Object o) {
        if (o == null)
            return "";
        else
            return o.toString();
    }
}

here's an example Bean

public class SomeBean {
    Long id;
    String name;

    public SomeBean(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Example page class

public class SelectTest {
    @Persist
    private SomeBean _someBean;
    @Inject
    private PropertyAccess _access;
    
    private GenericSelectModel<SomeBean> _beans;

    public SelectTest(){
        ArrayList<SomeBean> list = new ArrayList<SomeBean>();
        list.add(new SomeBean(1L,"Mirko"));
        list.add(new SomeBean(2L,"Slavko"));
        list.add(new SomeBean(3L,"Jozo"));
        _beans = new GenericSelectModel<SomeBean>(list,SomeBean.class,"name","id",_access);
    }
    
    public SomeBean getSomeBean(){
       return _someBean;
    }

    public void setSomeBean(SomeBean _someBean){
       this._someBean = _someBean;
    }
    
    public GenericSelectModel<SomeBean> getBeans(){
       return _beans;
    }
}

the test page:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
    <head>
	    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>Select test</title>
    </head>
    <body style="font-family:Courier new">
    
	<form t:type="Form">
		<t:select model="beans" encoder="beans" value="someBean"/>
		<t:submit/>
	</form>
value: ${someBean}
    </body>
</html>
  • No labels