[Image] EchoPoint
Helping you build truly dynamic and stateful web applications!
nothing

Advanced Component Creation Issues

By Brad Baker
Monday, May 05, 2003

This article discusses issues releated to advanced components creations for the Echo web framework.

Note that this is for people with a good understanding of Echo and who know how you can create a basic component.  The NextApp site has a good tutorial that explains components and how to build your own.  So if you are a bit sketchy in this area, I suggest you read this first and them come back to this article.

http://www.nextapp.com/products/echo/components/tutorial/

In particluar read sections 2 Rendered Components, 4 Ancillary Services and 5 Image Managers.  In fact while you are at it, read the lot.  It is concise and worth the effort.

NextApp have also posted a new series of whitepapers and architectural overviews that I suggest you read first.  These have only just been published and cover some of the issues talked about in this article.

http://www.nextapp.com/products/echo/doc/white_paper.html

http://www.nextapp.com/products/echo/doc/hltov/

This article will make references back to some of the EchoPoint components.  You may want to have access to the source code so you can follow along. 

 

The Basics

A custom component in Echo consists of two main classes.  The component class (derived from nextapp.echo.Component) holds all the properties and state for the component.  This is the class the developers will use to instantiate the component. 

The second class is a peer class (derived from nextapp.echoservlet.ComponentPeer) that is responsible for rendering the associated component as HTML.

In practice what this means is that component class can be a simple piece of code, contained all within the Java problem domain.  The peer class however is usually a lot more complicated because it must concern it self with rendering the component into an HTML format, and also be involved in the state management of the component.

Component Peer Id

Each ComponentPeer class has an Id object associated with it  As you might guess this object provides an unique identifier for the peer object.  You will need to use the object many times within your component peer.  A string representation can be retrived by calling getId().toString().

Also note that the string identifier returned may have underscrore characters in it.  So if you add your own underscores then dont assume that they will be the first ones in the resultant string.  I got caught out with this one in an early release.

The most useful purpose for the Id object is to provide an id attributes for the HTML elements.  This also allows a correlation between the HTML representation of a component peer and the component peer itself.

Component State Management

Some components will have to maintain some sort of state on the client browser.  Exactly how much state depends on whether the component is acting as what I call a client side mode or a server side mode.  Let me explain.

The ExpandableSection component needs to track with it is expanded or not.  If it is working in server side mode, then this tracking is easily done.  Each time its toggle handle is pressed, it sends back a message back to the server and the hence any "expanded" state property can be updated on the server and reflected back to the client when the component is re-rendered.. 

However if the component is working in client side mode, then no server side interaction is made.  Some ExpandableSection client side code must track whether the component is expanded or not.

The reason for this is two fold.  The first reason is the user may expand an ExpandableSection (in client mode) and then press another Button which causes a server interaction.  If no client side state is recorded, then the ExpandableSection peer will draw the component as collapsed, whereas the user would expect and expanded component to stay expanded.  Secondly if no client side code is maintaining the state of the component, then the peer cannot be informed of any state changes and hence the user cannot interact with the component correctly.  So it is important that client side state be maintained.

The first thing you need to know about is the interface nextapp.echoservlet.ClientInputProducer.  It has the following methods :

public interface ClientActionProducer {
      public
void
clientInput(String input);
      public Id getId();
}

If you component peer class implements this method, then it will be able to participate in client side state management.  By implementing this interface in your component peer, three things happen :

First, you are allocated a "magic" hidden field on the client with the name set to the value of getId().toString(). 

Second, you will then be able to call the JavaScript function E_setParameter() in your generated client side HTML.  This is invoked with the Id.toString() value of your component peer as well as a custom state string.

Third, your component peer will be informed of any client side updates via the clientInput() method.  It knows your peer has this method because you declared it to implement ClientInputProducer.

Exactly what goes in the custom state string is between your component peer and its supporting JavaScript code.  For example the SelectableTable component needs to keep track of which table rows have been selected and which are not selected.  It does this on the client side by tracking the selections in an internal array of boolean values and then creating a state string that looks like "true|false|true|true|false|false|false|".

The point is that it doesn't matter what is in the state string but rather how you component peer interprets it.  The SelectableTableUI class (which as you might guess is the peer for SelectableTable) takes the above string and breaks it down assuming that each represents a selection state in row order.  It then sets the state back into the SelectionModel for the associated SelectableTable component.

As a rule, you should save any client side state that the user would expect to see if they cause a server interaction. (Remember whenever a server side interaction is caused, the containing pane gets rendered again into HTML and JavaScript).

For example the ScrollablePanel component peer has JavaScript code that saves the scrollX and scrollY values of its scroll bar.  The JavaScript to do this is show below :

function ep_getScrollBarPositions(actualElement) {
    var text = actualElement.scrollLeft + "," + actualElement.scrollTop;
    return text;
}
function ep_saveScrollBarPositions(peerId, actualElement) {
   var text = ep_getScrollBarPositions(actualElement);
   E_setParameter(peerId,text);
}

The generated HTML DIV component looks a lot like :

<div onscroll="ep_saveScrollBarPositions('a_345',this)" onmouseout="ep_saveScrollBarPositions('a_345',this)" ...>

Notice that the E_setParameter() JavaScript function is called.  This function is provided by the base Echo support JavaScript and it sets the magic hidden state variable with a string, in this case an encoded string which might look like "234,0".  The ScrollablePanelUI peer will then interpret the clientInput string and set the scrollBarX and scrollBarY properties in the associated ScrollablePanel.

Saving client side component state helps provide some of the magic of Echo.  For example a scrollable component is back in the same scroll position between server side interactions.  No other web framework I know does this as well as Echo.

The following EchoPoint components and their peers use client side state management.  Each has some JavaScript backing them up allowing E_setParameter to be called.

  • DateField
  • DialogPanel
  • ExpandableMenu
  • ExpandableSection
  • HidingSlidingPanel
  • PickList
  • ScrollabelBox
  • ScrollablePanel
  • SelectableTable
  • SortableTable
  • SpinField
  • TitleBar
  • Tree

Raising and Receiving Component Actions

A component can cause an event to be raised on the client that causes and interaction with the server.

In order to raise an action event, a component peer must implement nextapp.echoservlet.ClientActionProducer.  This is defined as :

public interface ClientActionProducer {
      
public
void clientAction(String action);
}

Implementing this interface does three things.

First, it registers you component peer as one that will receive action events from the client browser.

Second, it allows you to call the JavaScript E_setAction() function on the client.  It is this method that causes the client to interact with the server when invoked.  A peer id and a command string value is supplied as in E_setAction(''a_2345","commandX") .

Third, your component peer is notified of action via the ClientActionProducer clientAction() method.  It is up to your component peer to decide what the command means.  For example the PushButton object does the following :

public void clientAction(String action) {
    AbstractButton button = (AbstractButton) getComponent();
    ActionEvent e =
new
ActionEvent(button, button.getActionCommand());
    button.fireActionPerformed(e);
}

This causes an nextapp.echo.event.Action event to be raised on the nextapp.echo.event.ActionListener  objects connnected to this button.

The DatePickerUI has a more complicated set of action commands that can be raised.  One set allow a individual date to be selected, another set allow navigation forward and back by a month and finally another set allow the user go back to the currently selected date.  By using different commands, the DatePickerUI is able to know how to update the DatePicker component so it reflects the user interactions.

An action is raised when the user clicks on an HTML element, typically an A tag.  It might look something like :

<a onclick="javascript:E_setAction("a_2345","commandX")" >press me</a>

There is a Java method provided by Echo that renders the E_setAction for you in code.  Personally I dont think its that useful since it still requires extra HTML text processing but hey lets not quibble.  I usually create a helper method in my component peer like :

protected String createScriptAction(String commandStr) {
   String scriptAction = "javascript:" + nextapp.echoservlet.ContentPaneUI.getScriptSetAction(
"\'" + getId() + "\'", "\'" + commandStr + "\'"
);
   return
scriptAction;
}

The above cause the E_setAction method to be returned as a string ready to be used on you rendered HTML.

Including your own JavaScript

The following describes how you can include a JavaScript file to help support your custom component. 

Echo uses the concept of a nextapp.echoservlet.Service when providing content.  A Service is invoked in response to some HTTP URL.  So for example the Window peer uses a service to return a HTML frame set.  The ContentPane peer is a service that returns rendered HTML for the components contained within it.  And an included JavaScript file should be provided as a Service.

The good news is you do not have to write a new Service for your JavaScript.  You can use the Echo provided class nextapp.echoservlet.StaticText.  This will render a text file back to the browser with your JavaScript file.  It also uses the .js extension on the file to set the correct mime type for the JavaScript file.

Since each instance of your component peer only needs the one copy of the JavaScript, you can make the StaticText object a static object.  And to keep you code delivery neat, you should put your JavaScript functions somewhere in you package structure and load the JavaScript file as a resource.  And guess what, StaticText has functions to do this for you.

For example the SelectableTableUI uses a JavaScript file called selectabletable.js to provide JavaScript support for a client side selection.  The peer class has the following declaration :

private static final Service SERVICE_SELECTABLETABLE_SCRIPT = StaticText.createFromResource("EP_STJS", ResourceNames.SELECTABLETABLE_SCRIPT);

static
{
   EchoServer.addGlobalService(SERVICE_SELECTABLETABLE_SCRIPT);
}

This loads one copy of the support JavaScipt file. 

The "EP_STJS" string is used by the Echo service management code to ensure a unique service name.  Think of it as a name space and therefore you need to think up one that wont be used by others.

The constant ResourceNames.SELECTABLETABLE_SCRIPT ends up evaluating to "/echopoint/ui/resource/static/selectabletable.js".  As you can see the JavaScript file is contained within the code base path.  The contents are loaded as a resource using this path. 

Now each instance of the SelectableTableUI peer can include this Service onto the browser client by calling :

public void render(RenderingContext rc, Element parent) {
...
...
rc.getDocument().addScriptInclude(SERVICE_SELECTABLETABLE_SCRIPT);
...
...

Once this is done, the JavaScript is guaranteed to be present and hence the functions within can be used. 

Echo generates a script include in the HTML like :

 ...
<script language="JavaScript1.3" src="/echopointtest/test?E_id=EP_STJS&E_z=fc9e_0"></script>
...

When writing your support JavaScript it is important to remember that the functions may be used by multiple component instances per HTML document. 

Therefore its important that you identify the component HTML that your support code is working with.  You will usaully use the component peer getId().toString() to identify the HTML elementsd before working with them in JavaScript.

Ensuring your Peer gets all the Events it should.

Your peer component should be listening to its associated component to ensure it can best reflect any updates to the component.  As discussed in the NextApp tutorial this involves adding listener interfaces at component peer registration time.  As a minimum you will want to listen to the PropertyChangeListener interface as in the following example code :

public void registered() {
        getComponent().addPropertyChangeListener(this);
}

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

public void propertyChange(PropertyChangeEvent e) {
       redraw();
}

If you component has other event listener interfaces you should add these to the peer and set the peer up as a listener to the component.  Other common event listener interfaces you may need to handle are :

  • ChangeListener
  • ItemListener
  • ListSelectionListener

Once you have added methods for these interfaces, simply call redraw() which will cause the Echo server to redraw the frame or window containing the document that is holding the component.

Serialization and your Component

One of the features of J2EE Servlet Engines is that HttpSession objects can be serialised to disk during their life time.  This is intended to allow better scalability and possible distribution of the session around a farm of servlet engines.

In the Echo web framework, all components and their peers are indirectly contained within the HttpSession object.  This is how each end user is giving a set of components to use.

This means that any component you create, plus any support classes such as its peer, must implement java.io.Serializable

In general this is simply a matter of adding the Serializable marker interface to your class declarations.  But you need to be sure about what classes are referenced by your component and expecially its component peer.  Any classes that are referenced need to be made Serializable.

The good news is that virtually all of the Echo and EchoPoint provided public classes are Serializable.

Component Peer HTML Output

Component peers in Echo do not directly write HTML out to the ServletOutputStream like you might in standard Servlets.  Instead it uses a tree like structure to represent the HTML output in Java first.  This is then converted into the output HTML.

This has several advantages.  The foremost of these advantages is that components can be rendered out of order.  Unlike say JSP, the order in which components are rendered does not matter.  This allows a component peer to add Script support to the head of the HTML document while it is being rendered, even though the output below logicall further down in the HTML document.

There are several common Java classes you will use in a custom component peer.  They are :

  • Element
  • ElementNames
  • ComponentStyle

Element represents an HTML element.  They can contain other Element objects as well as attributes, just like an HTML element.  So for example to create a HTML table element, with one row and one cell you would code  :

Element enclosingTableElement = new Element(ElementNames.TABLE);
enclosingTableElement.addAttribute(ElementNames.Attributes.BORDER, 0);
enclosingTableElement.addAttribute(ElementNames.Attributes.CELLPADDING, 0);
enclosingTableElement.addAttribute(ElementNames.Attributes.CELLSPACING, 0);

Element enclosingTrElement =
new
Element(ElementNames.TR);

Element enclosingTdElement =
new Element(ElementNames.TD);
enclosingTdElement.setWhitespaceRelevant(
true
);

enclosingTrElement.add(enclosingTdElement);
enclosingTableElement.add(enclosingTrElement);

As you can see from above the actual HTML element names have been encpasulated in Java constants via the ElementNames class.  For example ElementNames.TABLE equates to "table" and so on.  There is no requirement to use these names however you will get the type safety of the Java compiler and ensure you dont mistype you HTML output.

Element also has a couple of methods that can be very useful.  The first is setWhiteSpace() relevant.  If this is true, then no special line formatting will be used when the final HTML output is rendered out.  This might be useful for some HTML that does not like whitespace between them and the next HTML tag.  I seem to remember something about IMG and A tags needing to be on the same line from a look point of veiw. 

If white space is not relevant then the default false setting will produce HTML that is much more readable, which is you will be grateful for when debugging you component peer output.

Another class you will use is ComponentStyle.  This class is used to develop CSS styles for you HTML output.  And a really nice feature of Echo is if you add a ComponentStyle object to the HtmlDocument and one already exists with the same style values, then only one copy will be rendered.  This ensures the minimal number of CSS styles are auto-generated making the HTML document smaller.

A static helper forComponent() method is provided that allows you to get a ComponentStyle that is prebuilt for you ComponentPeer. 

ComponentStyle style = ComponentStyle.forComponent(this,true);
style.addElementType(ElementNames.TD);
String styleName= rc.getDocument().addStyle(style);

This creates a CSS style where the foreground, background and font attributes of the Component are preset.  Notice you need to indicate what HTML elements the component style applies to.  In this case it was ElementNames.TD which equates to "td".

You can add your own CSS attributes to the style, for example to set the scroll overflow property you might do the following :

style.addAttribute("overflow","auto");

Finally did you notice the line :

String styleName = rc.getDocument().addStyle(style);

This adds the component style to the HtmlDocument associated with the current RenderingContext object.  A generated name is returned for that CSS style result.  It is this name that you must add to your Elements in order for the elements to have this style, in the same way you would with the HTML class attribute. 

The really nice thing about this generated name is that if you already have a ComponentStyle with exactly the same attributes, then the name of this ComponentStyle is returned.  This greatly reduces the number of CSS style entries that are generated in the resulting HTML.  With the Echo inheritied forground amd background properties, you would be suprised how many rendered components use the same styles.

The code to use this ComponentStyle name looks like :

TD.addAttribute(ElementNames.Attributes.CLASS, styleName);

which is the wordy equivalent of :

TD.addAttribute("class", styleName);

Pick the coding style that suits you and stick with it. 

Personally I started out using the Echo method of Java constants for all strings.  However since HTML is fixed in stone (for all intentional purposes) I feel the use of inline string constants is safe and more terse.

 


Home

SourceForge Logo

The EchoPoint project is kindly hosted by SourceForge