Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for 
change notification.

The following page has been changed by MarceloLotif:
http://wiki.apache.org/tapestry/Tapestry5AnotherSelectWithObjects

New page:
== Tapestry5AnotherSelectWithObjects ==

We saw many ways of making a <SELECT> component filled with objects on other 
examples. This is an extension of the other methods, so if you want some more 
information, you may also see:

 * ["Tapestry5HowtoSelectWithObjects"]
 * 
[http://www.phy6.net/wiki/tiki-index.php?page=Tapestry+5+GenericSelectionModel 
Tapestry5 How to use the SELECT component]
 * ["Tapestry5DisplayableSelectionModel"]

Here, we will create an Annotation, a (not so) new feature from Java 1.5, 
widely know by Tapestry users, to suppress some steps from the other methods, 
like the instantiation of a SelectionModel and a ValueEncoder, or even create 
another component to do the job. Remember, this is an extension, so everything 
we saw in the other components are presented here. What I tried to do is an 
easier way of instantiate the component in the page class.

We will see 4 basic classes here:
 * '''GenericSelectionModel''' - This is the model that the component will use 
to render the options for your Select component.
 * '''GenericValueEncoder''' - This is the encoder that the component will use 
to get your selection back.
 * '''InjectSelectionModel''' - This is the annotation, the only class that you 
will effectively use.
 * '''InjectSelectionModelWorker''' - This is the class that will do all the 
dirty job for the annotation.

== GenericSelectionModel.java ==
Put this class on any package visible to the ''services'' package.
{{{
import java.util.ArrayList;
import java.util.List;

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

public class GenericSelectionModel<T> extends AbstractSelectModel {
        
        private PropertyAdapter labelFieldAdapter = null;
        private List<T> list;

        public GenericSelectionModel(List<T> list, String labelField, 
PropertyAccess access) {
                if(labelField != null && !labelField.equalsIgnoreCase("null")){
                        this.labelFieldAdapter = 
access.getAdapter(list.get(0).getClass()).getPropertyAdapter(labelField);
                }
                this.list = list;
        }

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

        public List<OptionModel> getOptions() {
                List<OptionModel> optionModelList = new 
ArrayList<OptionModel>();
                for (T obj : list) {
                        if (labelFieldAdapter == null) {
                                optionModelList.add(new 
OptionModelImpl(nvl(obj) + "", false, obj, new String[0]));
                        } else {
                                optionModelList.add(new 
OptionModelImpl(nvl(labelFieldAdapter.get(obj)), false, obj, new String[0]));
                        }
                }
                return optionModelList;
        }
        
        private String nvl(Object o) {
        if (o == null)
            return "";
        else
            return o.toString();
    }
        
}
}}}

== GenericValueEncoder.java ==
Add it to the same GenericSelectionModel's package.
{{{
import java.util.List;

import org.apache.tapestry.ValueEncoder;
import org.apache.tapestry.ioc.services.PropertyAccess;
import org.apache.tapestry.ioc.services.PropertyAdapter;

public class GenericValueEncoder<T> implements ValueEncoder<T> {

        private PropertyAdapter idFieldAdapter = null;
        private List<T> list;

        public GenericValueEncoder(List<T> list, String idField, PropertyAccess 
access) {
                if (idField != null && !idField.equalsIgnoreCase("null")){
            this.idFieldAdapter = 
access.getAdapter(list.get(0).getClass()).getPropertyAdapter(idField);
                }
                this.list = list;
        }

        public String toClient(T obj) {
                if (idFieldAdapter == null) {
                        return nvl(obj);
                } else {
                        return nvl(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();
    }
}
}}}

== InjectSelectionModel.java ==
Now, here is the little annotation that you will use on your page class. Create 
an ''annotations'' package and put this class inside it. Further, we will see 
it's usage.
{{{
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface InjectSelectionModel {

        String labelField() default "null";
        String idField() default "null";
        
}
}}}


== InjectSelectionModelWorker.java ==
Now, here comes the interesting part. This class do all the work and 
functionality that the annotation must do. It will add these things to your 
class:

 * A '''private PropertyAccess''' variable that the model and the encoder will 
use on their constructors.
 * A '''getSelectionModel()''' and '''getValueEncoder()''' methods to be used 
for the Select component.

So, this way, you don't need to add these lines on every page you create, with 
every Select you make. You just have to decorate the Select's list with this 
annotation and it will insert them for you.

Pot this class on your ''sevices'' package:

{{{
import java.lang.reflect.Modifier;

import org.apache.tapestry.ioc.internal.services.PropertyAccessImpl;
import org.apache.tapestry.ioc.internal.util.InternalUtils;
import org.apache.tapestry.ioc.util.BodyBuilder;
import org.apache.tapestry.model.MutableComponentModel;
import org.apache.tapestry.services.ClassTransformation;
import org.apache.tapestry.services.ComponentClassTransformWorker;
import org.apache.tapestry.services.TransformMethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teste.annotest.annotations.InjectSelectionModel;
import org.teste.annotest.components.GenericSelectionModel;
import org.teste.annotest.components.GenericValueEncoder;

public class InjectSelectionModelWorker implements 
ComponentClassTransformWorker {

    final private Logger _logger = 
LoggerFactory.getLogger(InjectSelectionModelWorker.class);
    
        public void transform(ClassTransformation transformation, 
MutableComponentModel componentModel) {

                for (String fieldName : 
transformation.findFieldsWithAnnotation(InjectSelectionModel.class)) {
                        InjectSelectionModel annotation = 
transformation.getFieldAnnotation(fieldName, InjectSelectionModel.class);
                        
                        if (_logger.isDebugEnabled()){
                                _logger.debug("Creating selection model getter 
method for the field " + fieldName);
                        }

                        String accessActualName = 
transformation.addField(Modifier.PRIVATE, 
"org.apache.tapestry.ioc.services.PropertyAccess", "_access");
                        transformation.injectField(accessActualName, new 
PropertyAccessImpl());
                        
                        addGetSelectionModelMethod(transformation, fieldName, 
annotation.labelField(), accessActualName);

                        if (_logger.isDebugEnabled()){
                                _logger.debug("Creating value encoder getter 
method for the field " + fieldName);
                        }

                        addGetValueEncoderMethod(transformation, fieldName, 
annotation.idField(), accessActualName);
                        
                }

        }

        private void addGetSelectionModelMethod(ClassTransformation 
transformation, String fieldName, String labelField, String accessName) {
                
                String methodName = "get" + 
InternalUtils.capitalize(InternalUtils.stripMemberPrefix(fieldName)) + 
"SelectionModel";

                String modelQualifiedName = 
(GenericSelectionModel.class).getName();
                TransformMethodSignature sig = 
                        new TransformMethodSignature(Modifier.PUBLIC, 
modelQualifiedName, methodName, null, null);

                BodyBuilder builder = new BodyBuilder();
                builder.begin();
                builder.addln("return new " + modelQualifiedName + "(" + 
fieldName + ", \"" + labelField +"\", " + accessName + ");");
                builder.end();

                transformation.addMethod(sig, builder.toString());

        }
        
        private void addGetValueEncoderMethod(ClassTransformation 
transformation, String fieldName, String idField, String accessName) {
                
                String methodName = "get" + 
InternalUtils.capitalize(InternalUtils.stripMemberPrefix(fieldName)) + 
"ValueEncoder";
                
                String encoderQualifiedName = 
(GenericValueEncoder.class).getName();
                TransformMethodSignature sig = 
                        new TransformMethodSignature(Modifier.PUBLIC, 
encoderQualifiedName, methodName, null, null);

                BodyBuilder builder = new BodyBuilder();
                builder.begin();
                String line = "return new " + encoderQualifiedName + "(" + 
fieldName + ",\"" + idField +"\", " + accessName + ");";
                builder.addln(line);
                builder.end();

                transformation.addMethod(sig, builder.toString());

        }

}
}}}


Bind them in your AppModule:
{{{
/**
     * Adds a number of standard component class transform workers:
     * <ul>
     * <li>InjectSelectionModel - generates the SelectionModel and ValueEncoder 
for a any marked list of objects.</li>
     * </ul>
     */
    public static void 
contributeComponentClassTransformWorker(OrderedConfiguration<ComponentClassTransformWorker>
 configuration)
    {
        configuration.add("InjectSelectionModel", new 
InjectSelectionModelWorker(), "after:Inject*");
    }
}}}

So, let's use it:
First, declare a list and populate it. Put the annotation on the list and pass 
the '''labelField'' and '''idField'' attributes to it. Then, create the item to 
get the selection. 

Here's the example:
{{{
public class Start {
        
        @InjectSelectionModel(labelField = "name", idField = "code")
        private List<Bank> _list = new ArrayList<Bank>();
        
        @Persist
        private Bank _item = null;

        public void onPrepare(){
                List<Banco> l = new ArrayList<Banco>();
                l.add(new Bank("Foo Bank 1", 1));
                l.add(new Bank("Foo Bank 2", 2));
                _list = l;
        }

        public Bank getItem() {
                return _item;
        }

        public void setItem(Bank item) {
                _item = item;
        }

        public List<Bank> getList() {
                return _list;
        }

        public void setList(List<Bank> list) {
                _list = list;
        }
}
}}}

On your template, use the core '''Select''' component and pass the parameters 
to it. The annotation will ever create the methods in these signatures: 
'''get<AttributeName>SelectionModel()''' and 
'''get<AttributeName>ValueEncoder()'''.

{{{
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";>
    <head>
        <title>t5 Start Page</title>
    </head>
    <body>
        <h1>t5 Start Page</h1>
        
        <t:form id="form1">
           <select t:type="select" value="item"
              model="listSelectionModel" encoder="listValueEncoder" />
                        
           <input type="submit" t:type="Submit" />
           <br/><br/>
           <t:if test="item">
              You just picked <b>${item.name}</b>, code <b>${item.code}</b>.
              <t:parameter name="else"> You have not selected yet. 
</t:parameter>
           </t:if>
                
        </t:form>
    </body>
</html>

}}}

If you wnat to have a '''List<String>''', just instantiate it and pass 
'''nothing''' as the ''labelField'' and ''idField'' for the annotation.

'''Important:''' your bean have to override the '''equals()''' method from the 
'''Object''' superclass, so the component can compare your current selection 
with the appropriate bean inside the list. The Eclipse IDE provide a simple 
default implementation that solves the problem. Go to the menu 
'''"Source/Generate hashCode() and equals()..."''' to use this feature.

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to