Skip to: Site menu | Main content

Adding Custom Editor Print

Introduction

When do you need a custom editor? When you find yourself repeating the same custom code for several custom edit pages. This section briefly discusses the necessary steps to add a custom Trails editor to your application. As an example we create a custom component that takes its entry as an url for an image and displays that image. For another sample, take a look at FileUpload editor example.

We need:

  • to create a new custom tapestry component
  • to update/create the library specification
  • to update/create the application specification
  • to update/create the Editors page
  • to add an entry in editorMap
  • to create an annotation
  • to create an annotation handler

Creating the CustomEditor component

First we need to create a tapestry component. A typical tapestry component has 3 parts, the HTML template, the component specification and the java code. If you want to know more about tapestry components check its user guide.

The template: CustomEditor.html
<span jwcid="$content$">
    <span jwcid="@TextField" value="ognl:model" />
    <br>
    <div align="center">
        <img jwcid="@Any" src="ognl:model" align="ognl:descriptorExtension.align" />
    </div>
</span>




The component specification: CustomEditor.jwc
component-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<component-specification class="org.trails.examples.customeditor.components.CustomEditor" allow-body="yes" allow-informal-parameters="yes">

</component-specification>






At this time it is still necessary to have a component specification, even if it is empty (this limitation may be lifted in the next release).

The java code: CustomEditor.java
package org.trails.examples.customeditor.components;

import org.apache.tapestry.annotations.Parameter;
import org.trails.component.TrailsComponent;
import org.trails.examples.customeditor.extensions.CustomEditorDescriptorExtension;

@ComponentClass(allowBody = false, allowInformalParameters = false)
public abstract class CustomEditor extends TrailsComponent {

  @Parameter(required = true)
  public abstract Object getModel();

  @Parameter(required = true)
  public abstract CustomEditorDescriptorExtension getDescriptorExtension();

}






  • In this case the java code is rather simple, and we could avoid it doing the component specification completely in the ".jwc" file. But we are getting ready for tapestry 4.1.
  • You can also extend from org.apache.tapestry.BaseComponent. You don't need to extend from TrailsComponent unless you want to use Trails i18n messages.

Adding the CustomEditor component to the Editors page

The Editors page provides a central place to store our custom editors. The code for this component is copied from the Trails sources and adapted (i.e. stripped) to suit our needs. The HTML template defines a Tapestry Block component that is referenced by the editorMap in the applicationContext.xml.

The html template Editors.html
<td jwcid="customEditor@Block" >
        <label><span jwcid="@Insert" value="ognl:descriptor.displayName" /></label>
        <span class="editor">
            <span jwcid="customEditorField" />
        </span>
        <br/>

    </td>




The page specification The page specification Editors.page
<!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<page-specification class="org.trails.page.EditorBlockPage">

    <property name="model" />
    <property name="descriptor" />
    <property name="editPageName" />

    <component id="customEditorField" type="CustomEditor"
        inherit-informal-parameters="no">
        <binding name="descriptorExtension">
            ognl:descriptor.getExtension('org.trails.examples.customeditor.extensions.CustomEditorDescriptorExtension')
        </binding>
        <binding name="model">model[descriptor.name]</binding>
    </component>

</page-specification>






Defining the component library



customeditor.library
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library-specification
      PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
      "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<library-specification>

    <description>The customeditor library</description>
    <library id="contrib" specification-path="/org/apache/tapestry/contrib/Contrib.library"/>

    <component-type type="CustomEditor" specification-path="CustomEditor.jwc"/>
    <page name="Editors" specification-path="/org/trails/examples/customeditor/components/Editors.page" />

</library-specification>






Adding our custom library

Before a component library may be used, it must be listed in the application specification. Often, an application specification is onlyneeded so that it may list the libraries used by the application.

Open the trails.application file in the WEB-INF directory of your Trails application and add a our custom Tapestry library specification:

trails.application
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application
      PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
      "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<application name="trails" >

    <description></description>
    <library id="trails" specification-path="/org/trails/component/trails.library"/>
    <library id="contrib" specification-path="/org/apache/tapestry/contrib/Contrib.library"/>
    <library id="tacos" specification-path="/net/sf/tacos/Tacos.library"/>
    <library id="customeditor" specification-path="/org/trails/examples/customeditor/components/customeditor.library"/>

    <meta key="org.apache.tapestry.accepted-locales" value="en,es_AR,pt"/>

</application>






Creating a descriptor extension.

Trails makes use of the Extension Objects Pattern (Erich Gamma)to allow for further extensions to descriptors interfaces without requiring recompiles, or interface extension bloat. You can add new functionality to existing descriptors without having to modify every derivative type in and outside of Trails.

CustomEditorDescriptorExtension.java
package org.trails.examples.customeditor.extensions;

import org.trails.descriptor.IDescriptorExtension;

public class CustomEditorDescriptorExtension implements IDescriptorExtension {

  public enum Align {
    left, center, right
  };

  private Integer height = 200;

  private Integer width = 200;

  private Align align = Align.left;

  public Align getAlign() {
    return align;
  }

  public void setAlign(Align align) {
    this.align = align;
  }

  public Integer getHeight() {
    return height;
  }

  public void setHeight(Integer height) {
    this.height = height;
  }

  public Integer getWidth() {
    return width;
  }

  public void setWidth(Integer width) {
    this.width = width;
  }

}






Creating our own annotation

To be able to identity in which properties we want to use our new component we use a annotation.

Trails identifies its annotations using the annotation @DescriptorAnnotation.

CustomEditor.java
package org.trails.examples.customeditor.extensions;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.trails.descriptor.annotation.DescriptorAnnotation;
import org.trails.examples.customeditor.extensions.CustomEditorDescriptorExtension.Align;

@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.FIELD, ElementType.METHOD })
@DescriptorAnnotation(CustomEditorAnnotationHandler.class)
public @interface CustomEditor {

  public int height() default 200;

  public int width() default 200;

  public Align align() default Align.left;

}






Creating our annotation handler

The annotations handler is responsible for process the annotation information and for adding the extension to the property descriptor (IPropertyDescriptor).

CustomEditorAnnotationHandler.java
package org.trails.examples.customeditor.extensions;

import org.trails.descriptor.IPropertyDescriptor;
import org.trails.descriptor.annotation.AbstractAnnotationHandler;
import org.trails.descriptor.annotation.DescriptorAnnotationHandler;

public class CustomEditorAnnotationHandler extends AbstractAnnotationHandler implements
    DescriptorAnnotationHandler<CustomEditor, IPropertyDescriptor> {

    public IPropertyDescriptor decorateFromAnnotation(CustomEditor customEditor, IPropertyDescriptor descriptor) {

    CustomEditorDescriptorExtension extension = new CustomEditorDescriptorExtension();
    extension.setAlign(customEditor.align());
    extension.setWidth(customEditor.width());
    extension.setHeight(customEditor.height());

    descriptor.addExtension(CustomEditorDescriptorExtension.class.getName(), extension);

    return descriptor;

  }

}






  • As long as you remember how you named it you can use whatever you want as the extension name/key e.g: descriptor.addExtension("MyCustomEditor", extension);

Adding an entry to the editors map

In the Spring bean configuration file src/main/resources/applicationContext.xml, the editors used by Trails are specified in the editorServicebean. Every editor available to Trails is listed in the {{editorMap }}defined in this bean. When Trails renders an editor for some bean property, it iterates over the editorMap entries. The map's keys are evaluated as OGNL expressions; the first editor whose key evaluates to true is used.

In our case the entry look like this: 

in applicationContext.xml
<entry>
                <key><value>supportsExtension('org.trails.examples.customeditor.extensions.CustomEditorDescriptorExtension')</value></key>
                <bean class="org.apache.tapestry.util.ComponentAddress">
                    <constructor-arg index="0"><value>customeditor:Editors</value></constructor-arg>
                    <constructor-arg index="1"><value>customEditor</value></constructor-arg>
                </bean>
            </entry>






The entry's value is a ComponentAddress object, that specifies where the custom editor can be found. In this case, it's defined in the Editors component in the customeditor namespace, which we defined before. The id of the editor is customEditor.

Use it



MyDomainObject.java
package org.trails.examples.customeditor.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.trails.examples.customeditor.extensions.CustomEditor;
import org.trails.examples.customeditor.extensions.CustomEditorDescriptorExtension.Align;

@Entity
public class MyDomainObject {

  private Integer id;
  private String name;
  private String url;


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @CustomEditor(align = Align.left)
  public String getUrl() {
    return url;
  }

  public void setUrl(String url) {
    this.url = url;
  }
 }






 Now we are ready to try it, type "mvn jetty:run" or "mvn tomcat:run"

bonus: Customizing the list page



in MyDomainObjectList.html
<table class="list" jwcid="@trails:ObjectTable" criteria="ognl:criteria" object="ognl:object">
        <div jwcid="urlColumnValue@Block">
             <img jwcid="@Any" src="ognl:object.url"/>
        </div>
    </table>






 
CUSTOM