This Confluence has been LDAP enabled, if you are an ASF Committer, please use your LDAP Credentials to login. Any problems file an INFRA jira ticket please.

Child pages
  • Using Select With a List

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Added anonymous inner class and multiple contribution examples

...

But then, that is a contortion of the toString()'s purpose, and if you go to that much trouble you're already half way to creating a ValueEncoder.

ValueEncoder

In addition to a SelectModel, your Select menu is likely to need a ValueEncoder. While a SelectModel is concerned only with how to construct a Select menu, a ValueEncoder is used when constructing the Select menu and when interpreting the encoded value that is submitted back to the server. A ValueEncoder is a converter between the type of objects you want to represent as options in the menu and the client-side encoded values that uniquely identify them, and vice-versa.

...

If you're using one of the database ORM integration modules (Tapestry-Hibernate, Tapestry-JPA (starting in Tapestry 5.3), or Tapestry-Cayenne), the ValueEncoder is automatically provided for each of your mapped entity classes. The Hibernate module's implementation is typical: the primary key field of the object (converted to a String) is used as the client-side value, and that same primary key is used to look up the selected object.

...

Code Block
titleColorEncoder.java (perhaps in your com.example.myappname.encoders package)
public class ColorEncoder implements ValueEncoder<Color>, ValueEncoderFactory<Color> { 

    @Inject
    private ColorService colorService;

    @Override
    public String toClient(Color value) {
        // return the given object's ID
        return String.valueOf(value.getId()); 
    }

    @Override
    public Color toValue(String id) { 
        // find the color object of the given ID in the database
        return colorService.findById(Long.parseLong(id)); 
    }

    // let this ValueEncoder also serve as a ValueEncoderFactory
    @Override
    public ValueEncoder<Color> create(Class<Color> type) {
        return this; 
    }
} 

Alternatively, if you don't expect to need a particular ValueEncoder more than once in your app, you might want to just create it on demand, as an anonymous inner class, from the getter method in the component class where it is needed. For example:

Code Block
titleSelectWithListDemo.java (a page class, partial)

    . . .

    public ValueEncoder<Color> getColorEncoder() {

        return new ValueEncoder<Color>() {

            @Override
            public String toClient(Color value) {
                // return the given object's ID
                return String.valueOf(value.getId()); 
            }

            @Override
            public Color toValue(String id) { 
                // find the color object of the given ID in the database
                return colorService.findById(Long.parseLong(id)); 
            }
        }; 
    }

Notice that the body of this anonymous inner class is the same as the body of the ColorEncoder top level class, except that we don't need a create method.

Applying your ValueEncoder Automatically

If your ValueEncoder implements ValueEncoderFactory (as the ColorEncoder top level class does, above), you can associate your custom ValueEncoder with your entity class so that Tapestry will automatically use it every time a ValueEncoder is needed for items of that type (such as with the Select, RadioGroup, Grid, Hidden and AjaxFormLoop components). Just add lines like the following to your module class (usually AppModule.java):

Code Block
titleAppModule.java (partial)
...
    public static void contributeValueEncoderSource(MappedConfiguration<Class<Color>,
    			                    ValueEncoderFactory<Color>> configuration) { 
        configuration.addInstance(Color.class, ColorEncoder.class);
    }

Skipping the ValueEncoder

If your Select's "If you are contributing more than one ValueEncoder, you'll have to use raw types, like this:

Code Block
titleAppModule.java (partial)

...
    public static void contributeValueEncoderSource(MappedConfiguration<Class,
                        ValueEncoderFactory> configuration)
    {
        configuration.addInstance(Color.class, ColorEncoder.class);
        configuration.addInstance(SomeOtherType.class, SomeOtherTypeEncoder.class);
    }

What if I omit the ValueEncoder?

The Select component's "encoder" parameter is optional, but if the "value" parameter is bound to a complex object (not a simple String, Integer, etc.) and you don't provide a ValueEncoder with the "encoder" parameter (and one isn't provided automatically by, for example, the Tapestry Hibernate integration), you'll receive a "Could not find a coercion" exception (when you submit the form) as Tapestry tries to convert the selected option's encoded value back to the object in your Select's "value" parameter. To fix this, you'll either have to 1) provide a ValueEncoder, 2) provide a Coercion, or 3) use a simple value (String, Integer, etc.) for your Select's "value" parameter, and then you'll have to add logic in the corresponding onSuccess event listener method:

...