To make use of your own custom client-side JavaScript components from within a server-side Java Echo application, you will need to write create a new server-side Component
class and a ComponentSynchronizePeer
The Component
object will represent the objects state, while the ComponentSynchronizePeer
will be responsible for transferring that state information from server to client (and vice-versa).
To create a component that will provide a custom synchronization peer, simply define a Java class that extends Component
directly:
package example; import nextapp.echo.app.Component; import nextapp.echo.app.FillImage; public class SpinButton extends Component { public static final String PROPERTY_BACKGROUND_IMAGE = "backgroundImage"; public static final String VALUE_CHANGED_PROPERTY = "value"; private int value; public SpinButton() { super(); } public FillImage getBackgroundImage() { return (FillImage) get(PROPERTY_BACKGROUND_IMAGE); } public int getValue() { return value; } public void setBackgroundImage(FillImage newValue) { set(PROPERTY_BACKGROUND_IMAGE, newValue); } public void setValue(int newValue) { int oldValue = value; value = newValue; firePropertyChange(VALUE_CHANGED_PROPERTY, new Integer(oldValue), new Integer(newValue)); } }
The above code shows a Component
implementation for the SpinButton
example. A getter and setter method has been added for each of its two properties, value
and backgroundImage
.
The first property, backgroundImage
is known as a "style property", in that it will be stored in the component's internal style. Such properties are retrieved and stored using the Component
's get()
and set()
methods. Style properties should be used in most cases, such that values may be specified within a Style
or StyleSheet
object rather than having to be specified for each Component
instance..
The second property, value
is not a style property. A property like "value" has no business being overridden by a Style
or StyleSheet
, and could conceivably be implemented later using a proper model object (as is done with DocumentModel
for Echo's various TextComponent
implementations). Non-style or "model" properties are stored in instance variables.
Component synchronization peers implement the nextapp.echo.webcontainer.ComponentSynchronizePeer
interface. This interface has quite a number of methods in it to control every detail about how a component is synchronized between the client and server. A helper class is provided that offers default implementations for all but a minimum number of methods that must be implemented by the developer. Generally speaking, you'll want to extend this AbstractComponentSynchronizePeer
class and override any methods where you require non-default behavior.
To create a peer for the SpinButton
component, we'll extend AbstractComponentSynchronizePeer
Default implementations of all methods of ComponentSynchronizePeer
are provided by this class, save the following two:
getClientComponentType()
: specifies the client-side component type name.getComponentClass
: Specifies the Java component class.Additionally we'll need to have the client download the JavaScript module that provides the client-side component. To do this, we'll create a static JavaScriptService
instance containing the JavaScript code, and then override the init()
method of AbstractComponentSynchronizePeer
to allow this service to be rendered the first time such a component is used.
In Echo3, most of the Java-to-XML-to-JavaScript serialization work is handled automatically. You only need to inform Echo of a few key pieces of information about your component. In the simplest case, you only need to describe in your peer what the client component type is, what the corresponding server component type is, and what JavaScript module(s) need to be loaded on the client.
The following code shows our first go at a ComponentSynchronizePeer
implementation:
package example; import nextapp.echo.app.Component; import nextapp.echo.app.util.Context; import nextapp.echo.webcontainer.AbstractComponentSynchronizePeer; import nextapp.echo.webcontainer.ServerMessage; import nextapp.echo.webcontainer.Service; import nextapp.echo.webcontainer.WebContainerServlet; import nextapp.echo.webcontainer.service.JavaScriptService; public class SpinButtonPeer extends AbstractComponentSynchronizePeer { // Create a JavaScriptService containing the SpinButton JavaScript code. private static final Service JS_SERVICE = JavaScriptService.forResource( "Example.SpinButton", "example/SpinButton.js"); static { // Register JavaScriptService with the global service registry. WebContainerServlet.getServiceRegistry().add(JS_SERVICE); } public String getClientComponentType(boolean shortType) { // Return client-side component type name. return "Example.SpinButton"; } public Class getComponentClass() { // Return server-side Java class. return SpinButton.class; } public void init(Context context, Component component) { super.init(context, component); // Obtain outgoing 'ServerMessage' for initial render. ServerMessage serverMessage = (ServerMessage) context .get(ServerMessage.class); // Add SpinButton JavaScript library to client. serverMessage.addLibrary(JS_SERVICE.getId()); } }
The above code does not yet provide any capability to synchronize the non-style property value
defined in the our Component
class. This will be discussed in a later section.