Renderers and Editors for Atomic Types
Posted by Eitan Suez Thu, 24 Aug 2006 20:46:00 GMT
Let's take a look at an example model object, say a Person:
public class Person
extends AbstractComplexEObject
{
private final StringEO name = new StringEO();
private final TextEO bio = new TextEO();
private final DateEO birthDay = new DateEO();
private final ImgEO picture = new ImgEO();
...
We see four properties are defined: the person's name, bio, birthday, and their picture.
Here is the corresponding Form View in JMatter:

JMatter uses a different editor for each property. For every atomic type, such as a StringEO, a DateEO, or an SSN, there exists a corresponding renderer and editor that JMatter uses to, well, render and edit each property.
Let's explore the contract for writing renderers and editors for atomic types.
JMatter defines two interfaces: AtomicRenderer and AtomicEditor:
public interface AtomicRenderer
{
public void render(AtomicEObject value);
}
public interface AtomicEditor extends AtomicRenderer
{
public int bind(AtomicEObject value);
}
Here is JMatter's actual implementation of AtomicRenderer for rendering StringEO's:
public class StringRenderer extends JLabel implements AtomicRenderer
{
public void render(AtomicEObject value)
{
setText(value.toString());
}
}
With very little explanation, we readily understand this contract: JMatter will ask renderers to render a piece of information, by calling the render() method and passing it the value in question. Our implementation here uses a JLabel and its setText() method is used to display the information.
Let's take a look at an implementation of an AtomicEditor:
public class StringEditor extends JTextField implements AtomicEditor, ActionNotifier
{
public StringEditor() { super(12); }
public void render(AtomicEObject value)
{
if (value.field() != null && value.field().displaysize() > 0
&& value.field().displaysize() != getColumns())
{
setColumns(value.field().displaysize());
}
StringEO eo = (StringEO) value;
if (!getText().equals(eo.stringValue()))
setText(eo.stringValue());
}
public int bind(AtomicEObject value)
{
StringEO eo = (StringEO) value;
eo.setValue(getText());
return 0;
}
}
In this case, the editor extends a JTextField and the editor is sensitive to JMatter's metamodel. That is, I've recently defined a new piece of meta-information on fields, which can be used to control the editor's text field's length (displaysize()). The AtomicEditor interface extends the AtomicRenderer's interface, so both methods, render() and bind() must be implemented.
The bind() method is relatively easy to understand: the framework will pass the editor a model object, and the editor's job is to bind its editor's value to the model object. So in this case, we retrieve the JTextField's value (with getText()) and invoke StringEO.setValue() to do the binding.
The contract with bind has one additional requirement: the editor has the ability to veto the action if some kind of validation error took place. Take a look at USZipEditor for a moment:
public int bind(AtomicEObject value)
{
try
{
value.parseValue(getText());
return 0;
}
catch (java.text.ParseException ex)
{
value.fireValidationException(ex.getMessage());
return 1;
}
}
Here, the USZipEditor asks a USZipCode model object to try to parse the text. If the text contains invalid characters, a ParseException is caught, and we're returning a one (1): the number of validation errors encountered during the binding attempt. So we see that a zero return value indicates an "all's well" condition.
Finally, the binding of renderers to types is determined by the implementation of the interface ViewMechanism, which among other methods, defines methods such as these:
public AtomicRenderer getStringRenderer();
public AtomicEditor getStringEditor();
public AtomicRenderer getTextRenderer();
public AtomicEditor getTextEditor();
public AtomicRenderer getSSNRenderer();
public AtomicEditor getSSNEditor();
public AtomicRenderer getDateRenderer();
public AtomicEditor getDateEditor();
public AtomicRenderer getImageRenderer();
public AtomicEditor getImageEditor();
...
Taking a peek at JMatter's SwingViewMechanism implementation, we see the bindings:
public AtomicRenderer getStringRenderer()
{
return new StringRenderer();
}
public AtomicEditor getStringEditor()
{
return (AtomicEditor) borrowObject(StringEditor.class);
}
In summary, the mechanism for writing renderers and editors is clean and simple. It should be trivial for anyone to write beautiful, custom renderers and editors for all the atomic types supported by JMatter (and the list is long). Adding new atomic types is also fairly straightforward, and hopefully will be the subject of a future blog entry.
Now that you know how to write your own renderers and editors for JMatter, you have the freedom to customize JMatter in that respect for any applications you're developing.
And if by any chance you develop editors that are superior to the existing implementations, consider submitting your implementations as candidates to become the default implementation in future JMatter releases.


