Creating Plugins

Requires Platform 4.2+

Plug-ins

The Nexaweb Client plug-in architecture allows developers to extend the functionality of the Nexaweb Client for Nexaweb Java applications by implementing their own tags, attributes and XML syntax. Developers commonly extend the functionality through new UI components; however, you can also implement logical operations, layout panes, or any other custom logic.

A typical use case involves including advanced charting capabilities in the Nexaweb Client. Although the client comes with some built in charting ability, it may not be sufficient to display certain advanced charts like those found in financial applications. Using the plug-in architecture, developers can integrate a 3rd-party chart component of their choice and define their own XML tags and format to display that chart. That chart can then be imbedded inside of standard XAL.

Example:

<window>
    <borderPane/>
    <label text="A custom chart" borderPosition="north" />
   
    <!-- a specialized chart created via the plug-in architecture -->
    <financialChart title="Dow Jones by year" 
       borderPosition="center" onSelect="myPage.jsp>
    <xAxis start="1950" end="2005"/>
    <yAxis start="0" y="5000"/>
    <pointData points="1900,2000,2020 ... "/>
    </financialChart>
</window>

Technical Overview

At the most basic level, a plug-in is a class that extends com.nexaweb.client.tags.AbstractTagImpl and listens for changes on XML elements. These changes come in the form of com.nexaweb.xml.events.StructureChangeEvent and com.nexaweb.xml.events.AttributeChangeEvent. These classes are called tag-handlers, as each tag name has its own handling class. These classes can also be referred to as bridge classes because they form a bridge between XML and implementation classes, referred to as peers.

AbstractTagImpl is the base class for all plug-ins. In addition, a number of subclasses exist to ease the creation of certain types of plug-ins. For example, you can use ContainerBridge or SwingBridge as subclasses to create UI Components based on java.awt.Component or javax.swing.JComponent. In addition, you can use JComponent to implement custom java.awt.LayoutManager classes.

In the example above, the developer has some third-party charting components that serve as the peers. The handling classes that extend AbstractTagImpl are responsible for manipulating these peers based on changes in the XML, as well as responding to changes in the peer classes.

AbstractTagImpl and its subclasses automatically listen for StructureChangeEvents and AttributeChangeEvents their associated XML elements. It is the responsibility of the individual subclasses to perform some meaningful operation when attributes change or child tags are added and removed. Similarly, subclasses can listen for changes to a peer object and fire events or update the UI Document Object Model (DOM) based on those changes. The bridge class is a two way bridge in that is listens for XML changes and passes them on to the peer, and listens for peer changes and fires events or updates the DOM based on those changes.

Care should be taken to ensure that plug-ins will run in the desired JVMs. A UI component plug-in based on Swing will only run on Swing-compatible (1.2+) JVMs.

Developing a JButton Plug-in

This section develops a simple plug-in that will map the <jButton> tag to a JButton component. The <jButton> tag will understand basic attributes such as backGroundColor. In addition the <jButton> tag will implement an attribute named "armed" as a boolean, and fire onCommand events when activated.

The first step in developing the <jButton> tag is creating the tag-handler that will map to the <jButton> tag. JButton is a Swing-based component, so our class will extend SwingBridge. When a <jButton> tag is first encountered, the parser will instantiate an instance of our JButtonBridge and call some initialization methods on it. One of those methods, init(), in turn calls createUiComponent(). We must override this method to create our desired UI component as follows:

public class JButtonBridge extends SwingBridge {
        public void createUiComponent(){
        setUiComponent(new JButton() );
    }
}

At this point, our bridge class will create a JButton component, and will automatically respond to basic attributes like bgColor. We would also like this tag to respond to the "armed" attribute. We can do this by overriding onAttributeSet() to handle the new attribute.

public void attributeSet(AttributeChangeEvent e) 
    throws AttributeConversionException {
    String attributeName = e.getName();
    String attributeValue = e.getNewValue();
    //if the attribute is "armed" set it to true or false
    if ("armed".equals(attributeName) ){
        ( (JButton)getUiComponent() ).getModel().
           setArmed(AttributeConverter.toBoolean(attributeValue));
    }
    //otherwise just fall through
    else super.attributeSet(e);
}

AttributeConverter is a utility class that provides methods that convert Strings to booleans, Color, enumerations and other data types. Overriding attributeSet and using AttributeConverter makes any errors in the XML report in the usual manner.

Now that our <jButton> can respond to an attribute change, we would also like the <jButton> to report "onCommand" events when the JButton ActionListener is called. We do that by implementing ActionListener and adding ourselves as a listener to the JButton. Then when actionPerformed() is called we simply call a method from AbstractTagImpl that handles the firing of the event. The following is the final code for JButtonBridge with the most recent changes highlighted.

 public class JButtonBridge extends SwingBridge 
                                  implements ActionListener{
    public void createUiComponent(){
        JButton b = new JButton();
        setUiComponent(b);
        //listen for action events so we can report them
        b.addActionListener(this); 
    }
     
    public void actionPerformed(ActionEvent e){
        //call the method from AbstractTagImpl that 
        //reports the event
        //fill in a ClientEvent object and calls the 
        //appropriate MCO/JSP/event handler
        fireEvent("onCommand",null);
    }
     
    public void attributeSet(AttributeChangeEvent e) 
                         throws AttributeConversionException{
        String attributeName = e.getName();
        String attributeValue = e.getNewValue();
        //if the attribute is "armed" set it to true or false
        if ("armed".equals(attributeName) ){
            ( (JButton)getUiComponent() ).getModel().
             setArmed(AttributeConverter.toBoolean(attributeValue) );
        }
        //otherwise just fall through
        else super.attributeSet(e);
    }
}

Now that our JButtonBridge is complete we must add a mapping so that our <jButton> tag is defined and handled by the JButtonBridge class. To do this we must create a tag mapping file with the appropriate entry, then add that file to our list of tag mapping files in the client configuration. The tag mapping file itself is simply a list of tag names with the name of the handling class as the text. We will call this file customTagMappings.xml:

<tag-mappings>
    <jButton>JButtonBridge</jButton>
</tag-mappings>

Once this file is created we must add an entry to nexaweb-client.xml:

<client-tag-mapping>
    <tag-map-file>customTagMappings.xml</tag-map-file>
</client-tag-mapping>

Plug-in Architecture Class Survey

The following table lists and provides brief descriptions of the plug-in related classes:

ClassDescription

AbstractTagImpl

The base class for all plug-ins.

ContainerBridgeThe base class for UI component plug-ins. This class handles common attributes that apply to all java.awt.Components.

SwingBridge

A subclass of ContainerBridge for Swing-based plug-ins

LayoutBridgeA subclass of AbstractTagImpl for creating custom LayoutManager tags.
AttributeConverterA utility class for converting attribute values into data types and reporting errors in a uniform manner.

 

Plug-in Class Lifecycle

When a new XAL block is appended to the existing UI DOM:

  1. The name of the class associated with the root element is looked up.
  2. The class is loaded if not loaded already.
  3. A new instance of the handler class is created.
  4. setSession() is called on the handler with the current ClientSession.
  5. The handler is added to the XAL element as a StructureChangeListener and AttributeChangeListener.
  6. setElement() is called on the handler with the XAL element.
  7. init() is called on the handler.
  8. The "onCreate" event is fired on the handler.

This process is performed only once for the entire XAL block. The root element of that block may have children and/or initial attributes set on it. AbstractTagImpl provides two methods which can be called in init() to handle these situations. The "parseInitialChildren()" method will repeat this parsing process for all children of the initial element. The "parseInitialAttributes()" method will feed the attributes on the initial element through onAttributeSet() as if they were being set after the element was created. In addition, ContainerBridge will call these methods by default in its init() method.

When an XAL block is removed from the UI DOM:

  1. unload() is called on the handler for the root element.
  2. The handler is removed from the XAL element as a StructureChangeListener and AttributeChangeListener
  3. The handler calls afterChildRemoved(), which recursively repeats the process for each child element of the root Element being removed.

If there is a handler class override onChildRemoved(), it must be sure to call afterChildRemoved() to make sure the recursive clean-up takes place.

Packaging Plug-Ins

Plug-ins consists of three sets of resources: tag-mapping files, the tag-handler classes and the peer classes. In our example the JButton peer class is included in any Swing-compatible JVM, but often peer classes will come in separate JAR files.

Tag-handling classes and peer classes follow the same deployment rules as MCOs, and tag-mapping files are considered standard files. Some developers may find it easier to combine their tag-mapping files and classes into a JAR file. For example, our JButtonBridge class and customTagMappings.xml may be in a JAR filed called "jButtonPlugin.jar." In that case we would have to change the nexweb-client.xml classpath to include that jar, and change the tag-mapping location to use the "classpath://" specifier. See the documentation on nexaweb-client.xml for more on adjusting the client classpath.

<!-- classpath changed to include our plug-in jar file -->
<client-classpath>
    <pre-loaded-in-applet-def>
    <archive name="jButtonPlugin.jar" 
       path="/WEB-INF/client/lib/jButtonPlugin.jar"/>
    </pre-loaded-in-applet-def>
</client-classpath>

<!-- load the tag mapping file from the classpath -->
<client-tag-mapping>
    <tag-map-file>classpath://customTagMappings.xml</tag-map-file>
</client-tag-mapping>

One final packaging option is to make plug-in classes load on startup, rather than when the tag corresponding to that plug-in is first encountered. Classes loaded at startup load during the Nexaweb loading animation, whereas large plug-ins loaded on the fly can cause the Nexaweb application to freeze for a few seconds. If an application makes consistent and frequent use of a large plug-in, loading it at startup is often the better choice. This can be done by modifying the contents of the tag-mapping file as below:

<tag-mappings>
    <jButton loadPolicy="onStartup">JButtonBridge</jButton>
</tag-mappings>

Defining Plugin Namespaces

Namespaces can be defined for a set of tags.  An example is the Nexaweb Data Framework, which was implemented using the plugin architecture.  The XML that defines this plug-in is:

<tag-mappings namespace="http://openxal.org/core/data" 
    document="bindings">
    <mapping class="com.nexaweb.plugin.data.bridge.BindingBridge" 
        name="binding"/>
</tag-mappings> 

This declares the tag "binding" in the openxal.org/core/data namespace.
 

Overriding Built-in Components

Plugins can be used to replace the behavior of built in XAL tags.  For instance if a plugin was defined as <button> it would replace the built in behavior of button.

Packaging and Distributing Nexaweb Application Plug-ins

In Nexaweb 4.2.x, to package and distribute a plug-in for your Nexaweb application, you need to:

  • Specify information about the plug-in in a plug-in manifest file
  • Place the plug-in JAR file in your application's WEB-INF/client/plugins directory

Adding Plug-in Information to the Plug-in Manifest File

You must add information about each plug-in that you create for your application to the plug-in manifest file, plugin.xml. Nexaweb uses the plug-in manifest to determine the contents of plug-ins.

You add the following sections to the plugin.xml file for each plug-in: 

SectionDescription

<plugin>  </plugin>

Identifies a unique plug-in to include with this application.

The opening tag can include an optional class attribute that specifies a lifecycle manager or some abstract MCO for the plug-in.

<info> </info>Specifies general information about the plug-in including location of the plug-in schema and stylesheet.
<tag-mappings>  </tag-mappings>

Defines tags that the plug-in adds to your application, specifies location of definition file for composite tags, and policy for when the application loads the plug-in.

 The following shows a sample plug-in manifest file:

<plugin class="MyPlugins">

 <info> 
  <provider-name>Nexaweb Technologies</provider-name>
  <author>James</author>
  <description>
     This plug-in automates automatic automation.
  </description>
  <version>10.3.4</version>
  <schema-location>schema/plugin.xsd</schema-location>
  <stylesheet-location>
    stylesheet/stylesheet.xss
  </stylesheet-location>
 </info>

 <tag-mappings namespace="http://nexaweb.com/charts" 
     document="nxml">
  <mapping name="barChart" class="com.nexaweb.plugins.BarChart"
     icon="barChart.gif" loadPolicy="onStartup"/>
  <mapping name="loginDialog" 
     class="com.nexaweb.plugins.LoginDialog" 
     icon="loginDialog.gif" definitionFile="ui/loginDialog.nxml"/>
 </tag-mappings>
</plugin>


The following table provides descriptions of the tags contained in the <info> section:

info TagDescription
<provider-name> </provider-name>Specifies information about who created the plug-in.

<author> </author>

Specifies information about who created the plug-in.

<description> </description>Provides information about the purpose or use of the plug-in.
<version> </version>Specifies plug-in version number.
<schema-location>
</schema-location>
Identifies location of this plug-in's schema file.
<stylesheet-location>
</stylesheet-location>
Identifies location of this plug-in's stylesheet. Default location for distribution is  /stylesheet in the JAR file.

The following table provides descriptions of the tags attributes contained in the <tag-mapping> section:

tag-mapping Tag\AttributeDescription

namespace

Specifies the namespace under which these tag mappings exist.


mapping name

Specifies a tag name used by the plug-in and that this plug-in adds to the application.

Mapping Name Attributes

AttributeDescription
classSpecifies the class to which this tag belongs. The class may exist in the lib or classes folder.

icon

Specifies any image associated with this tag. Images are relative to the images folder.

loadpolicyUse this attribute to specify that Nexaweb starts this plug-in when Nexaweb starts. The only valid value for this attribute is onStartup. If this attributes does not exist in the tag-mapping, then the plug-in starts when Nexaweb first encounters this tag.
definitionFileSpecifies location of file that defines the multiple functions included in a composite tag.

Plug-in Directory Structure

Nexaweb Studio organizes plug-ins using unique directory structures for build and distribution.

Nexaweb Studio uses the directory structure described in the following table for building a plug-in: 

Build DirectoryContains

/source

Java source code.

/dependenciesDependencies that Studio does NOT package with the plugin, as these ought to exist at runtime.
/dependencies/lib

JARs (for example, jaxp.jar).

/dependencies/pluginsOther plugins this plugin is dependent (for example, data-interfaces-plugin.jar)
/PluginContentSimilar to WebContent in a web application.  Nexaweb puts the contents of this directory into the root of the plugin.jar structure.
/build Build staging area, that Nexaweb may delete at the end of the build process.
/dist The plugin JAR that results from the build.

 Nexaweb Studio uses the directory structure described in the following table for distributing a plug-in:

Build DirectoryContains

/lib 

JAR files that the plugins rely on. (Classes are bound for the client, these must be publicly servable).

/imagesImages.

/PLUGIN-INF/docs

Location of plug-in documentation.

/PLUGIN-INF/schema/plugin.xsd   Plug-in schema file.
/stylesheet/stylesheet.xssPlug-in stylesheet.
/plugin.xml   Plug-in manifest file.