Rendered Components

Rendered components have their own renderers which create HTML and JavaScript code for the client browser.

Rendered components are useful when you need to create components that either cannot be created by extending existing components or are significantly more efficient when rendered using a purpose-built renderer. To create a rendered component, you must create a minimum of two classes: one to represent the component object itself, and a second to render an HTML representation to the client browser.

Throughout this chapter, references will be made to an example component that creates a horizontal rule. A horizontal rule is rendered in HTML such as this: <hr size=4>. The source files for this component will be introduced as necessary.

The Component Class

The component class must be derived from the nextapp.echo.Component class. To create the horizontal rule example, the first step is to create a HorizontalRule component class, shown below:

package renderedsample;

import nextapp.echo.Component;

public class HorizontalRule extends Component {

    private int size;
    
    public HorizontalRule(int size) {
        super();
        
        this.size = size;
    }
    
    public int getSize() {
        return size;
    }
    
    public void setSize(int newValue) {
        int oldValue = size;
        
        size = newValue;
        
        firePropertyChange("size", oldValue, newValue);
    }
}

The Rendering Peer Class

The rendering peer class is responsible for producing an HTML representation of the component that will be rendered to the client browser. All rendering peers must be derived from the nextapp.echoservlet.ComponentPeer class. You should familiarize yourself with the API of the ComponentPeer class after reading the tutorial.

A well-designed component class should not have any knowledge of or dependency on the rendering peer class. Therefore there should not be any references from the component to the rendering peer; all communication should be done through events. The Echo server will automatically create an instance of the rendering peer for each component based on the associations provided in the peer bindings properties file, which will be discussed later.

While ComponentPeer is an abstract class, it does not specifically require any method implementations to be provided. For a peer to be of any use, some of ComponentPeer's methods that are by default empty implementations will need to be overridden. The rendering peer for the HorizontalRule example is shown below:

package renderedsampleui;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import renderedsample.HorizontalRule;

import nextapp.echoservlet.ComponentPeer;
import nextapp.echoservlet.EchoServer;
import nextapp.echoservlet.RenderingContext;
import nextapp.echoservlet.html.Element;

public class HorizontalRuleUI extends ComponentPeer
implements PropertyChangeListener {

    public static void register() {
        EchoServer.loadPeerBindings(
                "renderedsampleui.PeerBindings");
    }

    public void propertyChange(PropertyChangeEvent e) {
        redraw();
    }
    
    public void registered() {
        getComponent().addPropertyChangeListener(this);
    }
    
    public void render(RenderingContext rc, Element parent) {
        HorizontalRule hr = (HorizontalRule) getComponent();
        Element element = new Element("hr", false);
        element.addAttribute("size", hr.getSize());
        parent.addElement(element);
    }
    
    public void unregistered() {
        getComponent().removePropertyChangeListener(this);
    }
}

Rendering

When a rendering peer is to be rendered to the client browser, its render() method is called. The first parameter is a RenderingContext that contains environmental information. The second parameter, parent, is the Element of the HTML document in which this component's HTML will be contained.

The RenderingContext object provides two methods, getDocument() and getConnection(). The getDocument() method returns an HtmlDocument object, which can be used for purposes such as adding JavaScript includes and CSS styles. The getConnection() method returns a Connection object that wraps the Servlet container's HttpServletRequest and HttpServletResponse objects along with pertinent application-specific client-connection related data. For more information on these objects, see the Application Container API Documentation.

Responding to Component Changes

All Components should produce PropertyChangeEvents when changes occur. The rendering peer should register with the component to receive these events such that changes will trigger the peer to be re-rendered on the client. The registered() and unregistered() methods of ComponentPeer are invoked when a peer object is associated or disassociated with a component instance. This is the appropriate place to add and remove PropertyChangeListeners from the component. The simple way to do this, as shown in the example, is to have the rendering peer implement PropertyChangeListener and provide the following implementations:

    public void propertyChange(PropertyChangeEvent e) {
        redraw();
    }
    
    public void registered() {
        getComponent().addPropertyChangeListener(this);
    }

    public void unregistered() {
        getComponent().removePropertyChangeListener(this);
    }

The propertyChange() implementation calls the ComponentPeer class' redraw() method, which will cause the Echo server to redraw the frame or window containing the document that is holding the horizontal rule.

Associating Components with their Rendering Peers

Peer binding properties files associate component classes with their peers. Each line of the properties file should contain the fully qualified class name of the component followed by the fully qualified class name of its rendering peer. This file should end with a ".properties" extension. For the HorizontalRule example, the properties file will have only one line as it only has one component:

renderedsample.HorizontalRule renderedsampleui.HorizontalRuleUI

The server needs to be manually told to load the peer binding association properties files. This is done by calling a class (static) method in the EchoServer object, loadPeerBindings(). If the properties file is named "PeerBindings.properties" and is located in the renderedsampleui package, the following call to loadPeerBindings() would properly load it:

EchoServer.loadPeerBindings("renderedsampleui.PeerBindings");

As the developer of a rendered component, you should not require your user-developers to manually load peer bindings. Instead, the rendering peer class should provide a class method, register() that calls loadPeerBindings().

Using External Rendered Components in Applications

Before a component can be used in an application, it's peer bindings must be loaded (NOTE: Echo's built-in components are automatically registered). This operation should be performed in a static initializer, by calling the rendering peer class' register() method:

    static {
        renderedsampleui.HorizontalRuleUI.register();
    }

The best practice is to include the calls to any register() methods in an application's EchoServer class.