You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by Apache Wiki <wi...@apache.org> on 2008/02/19 19:57:15 UTC

[Tapestry Wiki] Update of "Tapestry5AnotherSelectWithObjects" by MarceloLotif

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: dev-unsubscribe@tapestry.apache.org
For additional commands, e-mail: dev-help@tapestry.apache.org