Introduction

One of the best ways to avoid code duplication in your presentation layer is to create custom components. As you become more familiar with how to do this you will notice that your user interface can be created by composing these custom components. In general we find that as you start recursively composing these components, a set of components can be created that corresponds closer and closer with more advanced concepts in your problem domain.

In jZeno creating custom components is done using the same techniques as for creating screens and dialogs. In fact the main difference between these three aggregates is the base class they derive from (CustomComponent, Screen and Dialog).

The custom components that you will be creating will themselves function as dynamic components. This means that they will have their own databinding, etc...



The EMailEditor

We will explain the details you need to know in order to build a custom component with the help of a small example component. The example we will be building is an email editor. We will be constructing it out of 2 basic components allready available in jZeno : DynaTextField and DynaLink.

In short the component we will be building allows people to enter an email-address, and verify this mail address in an imaginary LDAP server by clicking on an icon to the right of the text-part of the editor. The component is also going to do it's own validation (is the entered value a valid email address ?) and error marking.

We will start out with the basic template for custom components. As you write more an more of these components the folowing will become a standard structure you can start from.



package get.started;

import net.sf.jzeno.echo.EchoSupport;
import net.sf.jzeno.echo.components.CustomComponent;

public class EMailEditor extends CustomComponent {
	private static final long serialVersionUID = 1L;
    
	public EMailEditor() {
		this(null,null,null);
	}
	
	public EMailEditor(Class beanClass, String propertyPath, String constructionHints) {
		super(beanClass, propertyPath, "");
		
		// ... custom construction code here ...
		
		EchoSupport.executeHints(this, constructionHints);
	}
}
	


First of all the custom component must always derive from CustomComponent. Secondly, as your new component is automatically a dynamic component, you need to respect the contract for these components. This implies that the component must implement a certain interface (PropertyComponent), which has been done for you, as you are deriving from the CustomComponent class. More importantly, the contract for dynamic components also includes having at least the folowing 2 constructors :

Our template fulfills the first requirement by calling this(null,null,null), which is the easiest, and most reliable way of implementing that part of the contract. The second constructor requires a bit more explanation.

First of all the arguments beanClass and propertyPath together are required to set up the data binding of our custom component. Luckily for us that support for data binding is implemented in our base class. So basically we need to pass these arguments into the super constructor, as shown in the example.

Now the third argument, constructionHints is a bit trickier, it specifies a list of 'construction hints'. This is basically a string with a comma seperated list of properties that need to be set on the newly created component. Now you may be wondering why we are not simple passing our third argument into the super constructor as well. The answer lies in the order of construction of the part of the object belonging to the base class, and the other part, belonging to our own class. Imaging trying to set a property that is implemented in our deriving class (EMailEditor), by specifying it in the construction hints. If we were to pass the construction hints directly to the super constructor, than the constructor in CustomComponent will try to execute the set method of a property in our deriving class (EMailEditor). The code inside that set method might rely on initialisations performed in the constructor of the deriving class (a very reasonable assumption). But remember that this call to the set method is being done before the actual constructor in the EMailEditor is executed, so the assumption is false.

To get around this problem we pass no construction arguments to the super constructor. And only after executing any initialisations inside our own constructor, we execute all construction hints by calling EchoSupport.executeHints()

Next, we will be adding our text field and icon :



package get.started;

import net.sf.jzeno.echo.EchoSupport;
import net.sf.jzeno.echo.components.CustomComponent;
import net.sf.jzeno.echo.databinding.DynaTextField;
import net.sf.jzeno.echo.databinding.DynaLink;

public class EMailEditor extends CustomComponent {
	private static final long serialVersionUID = 1L;
    
	private DynaTextField textField;
    
	private DynaLink link;
	
	private DynaGrid mainGrid;
    
	public EMailEditor() {
		this(null,null,null);
	}
	
	public EMailEditor(Class beanClass, String propertyPath, String constructionHints) {
		super(beanClass, propertyPath, "");
		
		mainGrid = new DynaGrid(null,null,"width=-1,columns=2");
		textField = new DynaTextField(getClass(), "email", "");
		mainGrid.addRight(textField);
		link = new DynaLink(null, null, "iconName=lookup.png,actionCommand=lookup");
		mainGrid.addLeft(link);
		add(mainGrid);
		
		EchoSupport.executeHints(this, constructionHints);
	}
}
	


I've added 2 instance variables inside the class to hold references to the 2 components. Basically we've taken care that our layout has 2 columns on the screen. The left column will contain the text field, and the right one will contain our link (icon).

Next I've added code to the constructor to instantiate both controls. Notice that the text field is bound to the property email, and the link is unbound, but is configured to be an icon, and to have the method lookup as it's event handler.

For layout purposes I've added the textField right aligned in it's cell, and the link left aligned. This way they will be nicely adjacent.

Next I'm going to add the property email, and the event handler. The implementation of this has a little twist to it :



package get.started;

import net.sf.jzeno.echo.EchoSupport;
import net.sf.jzeno.echo.components.CustomComponent;
import net.sf.jzeno.echo.databinding.DynaTextField;
import net.sf.jzeno.echo.databinding.DynaLink;

public class EMailEditor extends CustomComponent {
	private static final long serialVersionUID = 1L;
    
	private DynaTextField textField;
    
	private DynaLink link;

	private DynaGrid mainGrid;
	
	public EMailEditor() {
		this(null,null,null);
	}
	
	public EMailEditor(Class beanClass, String propertyPath, String constructionHints) {
		super(beanClass, propertyPath, "");
		
		mainGrid = new DynaGrid(null,null,"width=-1,columns=2");
		textField = new DynaTextField(getClass(), "email", "");
		mainGrid.addRight(textField);
		link = new DynaLink(null, null, "iconName=lookup.png,actionCommand=lookup");
		mainGrid.addLeft(link);
		add(mainGrid);
		
		EchoSupport.executeHints(this, constructionHints);
	}

	public void setEmail(String email) {
		setValue(email);
	}
	
	public String getEmail() {
		return (String) getValue();
	}
	
	public void lookup() {
		LDAPBusinessFacade businessFacade = BusinessFactory.getLDAPBusinessFacade();
		String completedEmail = businessFacade.lookupAddress(getEmail());
		if(completedEmail != null) {
			setEmail(completedEmail);
		}
	}
}
	

What are the getValue() and setValue() methods ? Remember when I said that our custom component will be a fully fledged dynamic component ? That meant among other things that it would have it's own data binding. The way to access/change the value your aggregate (in this case CustomComponent) is bound to, you can use the get and setValue methods. In our case I'm using a simple trick to connect my internal text field to the external data binding of my EMailEditor.

The event handler lookup is also implemented to use an imaginary business facade to provide email completion...


Validation & Error Marking

Any dynamic component has a list of validator objects. In order to implement the default validation of your new custom component, you will need to add a validator to yourself. Typically we implement this in the form of an inner class. This class is very simple and is required to implement the net.sf.jzeno.echo.Validator interface. This interface has a single method getValidationErrors(). In your implementation you should check the state of the EMailEditor, and return a list of ValidationError objects (net.sf.jzeno.echo.ValidationError) if there are errors, or otherwise an empty list.



package get.started;

import net.sf.jzeno.echo.EchoSupport;
import net.sf.jzeno.echo.components.CustomComponent;
import net.sf.jzeno.echo.databinding.DynaTextField;
import net.sf.jzeno.echo.databinding.DynaLink;
import net.sf.jzeno.echo.Validator;
import net.sf.jzeno.echo.ValidationError;

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

import org.apache.commons.validator.GenericValidator;

public class EMailEditor extends CustomComponent {
	private static final long serialVersionUID = 1L;
    
	private DynaTextField textField;
    
	private DynaLink link;

	private DynaGrid mainGrid;
	
	public EMailEditor() {
		this(null,null,null);
	}
	
	public EMailEditor(Class beanClass, String propertyPath, String constructionHints) {
		super(beanClass, propertyPath, "");
		
		mainGrid = new DynaGrid(null,null,"width=-1,columns=2");
		textField = new DynaTextField(getClass(), "email", "");
		mainGrid.addRight(textField);
		link = new DynaLink(null, null, "iconName=lookup.png,actionCommand=lookup");
		mainGrid.addLeft(link);
		add(mainGrid);

		EchoSupport.executeHints(this, constructionHints);
		addValidator(new DefaultValidator());
	}

	public void setEmail(String email) {
		setValue(email);
	}
	
	public String getEmail() {
		return (String) getValue();
	}
	
	public void lookup() {
		LDAPBusinessFacade businessFacade = BusinessFactory.getLDAPBusinessFacade();
		String completedEmail = businessFacade.lookupAddress(getEmail());
		if(completedEmail != null) {
			setEmail(completedEmail);
		}
	}

	public class DefaultValidator implements Validator {
		public List getValidationErrors(PropertyComponent pc) {
			List ret = new ArrayList();

			if(!GenericValidator.isEmail(getEmail()) {
				ret.add(new ValidationError(pc,"Please enter a valid e-mail !");
			}
		
			return ret;
		}
	}
}
	


Notice that we are using the Apache commons GenericValidator. This class has convenient methods for validating all sorts of data. Also notice the extra line in the constructor that adds an instance of the DefaultValidator to your custom component.

If you want your error messages to be internationalized, you should add an entry for the used string in i18n.properties. (By default if the string is not found in the translation property file, the original 'key' is displayed directly)


Example 2 : PersonEditor

In this second example we will show a component that corresponds to some concept from the domain model. In our imaginary example here we will build an editor that can edit the details of a Person object. Potentially more than one screen in an application may need the capability of editing these details, and thus we create a custom component for handling this task.

First we'll take a look at the Person class, which is a straightforward domain model object, mapped with hibernate, etc... :


package get.started;

import net.sf.jzeno.model.*;

public class Person extends AbstractMutableEntity {
	private static final long serialVersionUID = 1L;

	private String firstName;
	
	private String lastName;
	
	private Integer age;    
	
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	
	public String getFirstName() {
		return firstName;
	}
	
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	
	public String getLastName() {
		return lastName;
	}
	
	public void setAge(Integer age) {
		this.age = age;
	}
	
	public Integer getAge() {
		return age;
	}
}
	

Next we'll take a look at the implementation of the editor :


package get.started;

import net.sf.jzeno.model.*;
import net.sf.jzeno.echo.databinding.*;
import net.sf.jzeno.echo.editor.*;
import net.sf.jzeno.echo.component.*;

public class PersonEditor extends CustomComponent implements Precreation {
	private static final long serialVersionUID = 1L;

	private DynaTextField firstNameEditor;
	
	private DynaTextField lastNameEditor;
	
	private IntegerEditor ageEditor;
	
	private DynaGrid mainGrid;
	
	public PersonEditor() {
		this(null,null,null);
	}
	
	public PersonEditor(Class beanClass, String propertyPath, String constructionHints) {
		super(beanClass, propertyPath, "");
		
		firstNameEditor = new DynaTextField(getClass(),"person.firstName","required=true,autoTrim=true");
		lastNameEditor = new DynaTextField(getClass(),"person.lastName","required=true,autoTrim=true");
		ageEditor = new IntegerEditor(getClass(),"person.age","required=true,allowZero=false");
		
		mainGrid = new DynaGrid(null,null,"width=-1,cellMargin=2,columns=2");
		mainGrid.addRight(new DynaLabel("First Name : "));
		mainGrid.addLeft(firstNameEditor);
		mainGrid.addRight(new DynaLabel("Last Name : "));
		mainGrid.addLeft(lastNameEditor);
		mainGrid.addRight(new DynaLabel("Age : "));
		mainGrid.addLeft(ageEditor);
		add(mainGrid);
		
		EchoSupport.executeHints(this, constructionHints);
		applyContext();
	}
	
	public String getPerson() {
		return (Person) getValue();
	}
	
	public void applyContext() {
		if(FastFactory.isInsideContext()) {
			// Only use SecuritySupport and/or ServletSupport
			// in here, _never_ inside of the constructor.
		}
	}
}
	

As you notice the component implements the Precreation interface. More information about this can be found here. In fact all of your Screens, Dialogs and CustomComponents should really implement this interface.

The most notable difference in this component, compared to the EMailEditor, is that we are not simply binding to properties on our own component. In fact we are allways binding to a property path (e.g.: person.firstName) that is relative to a 'delegating' property (person). In other words all property paths start with a first property (person) that is itself implemented by delegating to getValue(). The getValue() method is used to read the databinding of the PersonEditor object as a whole. Remember that custom dynamic components can be built by composing them from other dynamic components, yet at the same time the custom dynamic component has it's own databinding, as specified by the user of that class. In this way you can encapsulate all sorts of functionality inside a your own components.


Using The PersonEditor

Next we'll look at how to use the PersonEditor from a different part of the presentation layer :


package get.started;

import net.sf.jzeno.model.*;
import net.sf.jzeno.echo.databinding.*;
import net.sf.jzeno.echo.editor.*;
import net.sf.jzeno.echo.component.*;

public class TestScreen extends Screen implements Precreation {
	private static final long serialVersionUID = 1L;

	private Person person;
	
	private PersonEditor personEditor;
	
	public TestScreen() {
		person = new Person(),
		
		personEditor = new PersonEditor(getClass(),"person","");
		add(personEditor);
		
		applyContext();
	}
		
	public String getPerson() {
		return person;
	}
	
	public void applyContext() {
		if(FastFactory.isInsideContext()) {
			if(!SecuritySupport.getCurrentUserHasPermission("editPeopleDetails")) {
				EchoSupport.setReadOnlyRecursively(personEditor);
			}
		}
	}
}
	

As you can see the usage of your own custom dynamic component is the same as if you would be using any of the built-in dynamic components.


Some More Tips

Even if you want to create a specialised version of one of the existing dynamic components, it's a good idea to simply use the same structure as described above (instead of subclassing, say a DynaButton). There's no real performance impact by delegating to the dynamic component you want to modify, compared to subclassing it. Anything you could do with subclassing can be done with delegation. And by always using the same coding style for all you custom components you will make life easier for yourself (or the poor guy who will be maintaining your code :-)). In short : a single style for creating all custom component, screens and dialogs should suffice !

When you are using your new EMailEditor you may come accross situations where more validation is required than the default. You do not have to change your EMailEditor class for this special case, as you can allways add extra Validators to any dynamic component. So that also goes for any of the built-in dynamic components.