Declarative Data Binding

Data presentation and manipulation are essential to any application. In the modern enterprise this has extended to the need to handle data from disparate data sources—XML, Plain Old Java Objects (POJOs), web services, databases, and so forth—making their integration an even more important aspect of development. Over the years, many platforms and frameworks have been created to make integrating data into applications easier and more repeatable (JSP, JSTL, Struts, XSTL, MVC, and so forth). The Nexaweb architecture makes it possible to use any of these and many more.

The goal of any data framework is to allow easy separation of presentation, data and behavior logic. This article explains how the Nexaweb architecture achieves these goals.

Declarative Data Integration

Nexaweb 4.2 adds a declarative framework that provides developers with an easy way to populate UI with data. You might ask, "Why create one more way to integrate data, you just listed six?" The difference between Nexaweb's data framework and the others is that it is evaluated on the client while the page is rendered. The data is handled asynchronously, allowing the UI to be presented to the user quickly and then pulling in the data once it has been retrieved. Ajax works this way too, which is one reason for its popularity.

Because the evaluation of the data framework happens on the client, the framework does not supply functionality to retrieve data from a back-end data service, other than through request / response. This means that you need to handle retrieval from the data layer and then supply it to the data framework as either an XML document or as POJOs. 

 

Helpful Tip

Code to retrieve the data should be very well abstracted in order to handle any retrieval request. The code below demonstrates this by creating a JSP page that makes a request to a salesforce web service using AXIS. I then apply an object serializer to convert the object into an XML stream that populates the UI from the data framework.

Code View source file

If you use Hibernate, you can create a transparent data layer with a simple servlet that uses an ObjectOutputStream to serialize the objects returned from Session.createQuery(<HQL statement>) calls to the client. 

Code View source file

 

The breakdown

There are four basic components of the Nexaweb data framework:

Data SourcesResponsible for supplying data to the framework when asked.
Bindings Defines the location for both ends of the binding between the UI and the data location.
Iterators Replicates the contents of the iterator for each data object that is found matching the select statement.
Formatters Changes the data from a raw value into a value useful to the presentation layer.

The data framework is built in such a way that the bindings, iterators and formatters are data source agnostic, allowing for any type of data source to be used by any binding, iterator or formatter.

 

Helpful Tip

Data sources and formatters are fully extensible to allow you to add new functionality. You can define your own dataSource and formatter extension classes.

<formatter class="com.acme.formatters.PhoneNumberFormatter" id="digitFormatter"/>
<dataSource class="com.acme.datasources.CsvFile" id="cvsDS"/>

In addition, you can package extensions as plug-ins, allowing you to declare alternative tag names.

Custom formatter and dataSource objects are discussed below. Let's first make note of what any formatter and dataSource does.

 

Data Sources

A dataSource's main responsibilities include:

  • Supplying the data framework with the results obtained from a given query on the data operation
  • Listening for changes, when desired, and notifying any interested bindings of them
  • Retrieving data from the server, as is the case of the two dataSources supplied out-of-the-box
help See the developer guide for more - Data Sources

The out-of-box data framework provides the following dataSources:

Data SourcesDescription
objectDataSourceProvides functionality to bind to POJOs or other Java objects. If specified as part of a binding, the objectDataSource listens to property changes using PropertyChangeListeners. 
documentDataSourceProvides functionality for binding XML documents to the UI. If specified as part of a binding the objectDataSource listens to DOM changes.

Both the objectDataSource and the documentDataSource can request a resource from the server based on an HTTP URL specified in the source attribute. Data sources are responsible for handling the syntax used by bindings in their SELECT's attributes. For instance, the documentDataSource object uses XPath, while the objectDataSource object uses OGNL, a standard way to traverse Object graphs. When implementing a custom data source, the select statement can be one of the two mentioned previously or can be created from scratch to meet the requirements of the data source.

help See the developer guide for more - Data source Tags

 

Helpful Tip

By supplying a Managed Client Object (MCO) method as the source attribute, you can cache data by retrieving and storing it in a data cache (Hashtable), then returning the existing data rather than making another request to the server for the same data. The method below will cache an XML document from the salesforce.jsp page mentioned in the previous Helpful Tip.

 /**
  * Get the result from the server
  * @return
  */
  public Document getResults(String query, boolean bRefresh){
     Document  document = null;
  
     try {
        //Check to see if the query has already been cached, if it has
        //and the a refresh is not desired return the cached document.

        if (bRefresh == false){
           document = (Document)_documentQueryHashTable.get(query);
           if (document != null) return document;
        }
   
        HttpResponse response = getSession().getNetService().retrieve("query.jsp?query="+URLEncoder.encode(query));   
        document = ParserFactory.getParser().parseXml(new String(response.getContent()));
   _    documentQueryHashTable.put(query, document);
     } catch (NetServiceException netServiceException) {
        netServiceException.printStackTrace();
     } catch (ParserException parserException) {
        parserException.printStackTrace();
     }
     return document;
}

A documentDataSource can then call this method through:

 <data:documentDataSource
          id="resultSet"
        
          source="mco://salesForceMco.getResults('select id, firstname, lastname, rating, company, email from lead', false)"
          xmlns:data="http://openxal.org/core/data"/>

Studio Tip

Using Nexaweb Studio, you can easily create data sources by dragging and dropping XML files into its visual editor. Once data sources are supplied they will be accessible from wizards and from outline views of other XML UI files.

 

Bindings

Bindings allow developers and screen designers to map data to the UI and apply formatting rules.  Depending on the usage pattern for a particular binding requirement, bindings can be defined as a binding tag, allowing re-use through out the application, or anonymously.  To define a binding tag, you need to specify two required attributes:

Required AttributesDescription
selectA string that represents the data to be bound.  Depending on the data source, either an XPath statement (documentDataSource), an OGNL statement (objectDataSource), or other if using a custom data source.
dataSourceThe ID of the data source supplying the data.

Notice that you do not define the end point for the UI. This allows the binding tag to be re-used for multiple UI elements.  The location where you place the reference to the binding tag using "{}" markers defines a binding's UI end point.  When the client sees a "{" followed by a "}" it will look to replace this syntax.  Take for example the following code snippet:

<label text="{bind(binding://myBinding)}"/>

When the client parses the tag, it evaluates the text attributes to see if a replacement is necessary.  When the text attributes starts with a "{" character and ends with a "}" character, the client evaluates the contents for what should be the final text attribute.  In the above example, the client asks the binding for the value to place into the attribute. 

 

Helpful Tip

The following table contains a list of items that you can place between the "{}" characters and a description of what is done for each.

Replacement ActionsDescription
bind(binding://myBinding)The client uses the binding with the id of myBinding for
the value.
bind('dataSource=myDataSource; select=Select statement; ...')The client creates an anonymous one time binding for the
data located in myDataSource and apply any other
defined binding attribute (formatters, type)
*('myDataSource', 'Select Statement')The client creates an anonymous one time binding for the
data located in myDataSource.
*('Select statement')The client creates an anonymous one time binding for the
currently iterated node. This syntax can only be used
inside of an iterator construct.
mco://myMco.getValue()The client calls the getValue method on myMco and
places the return value into the attribute.

 

Bindings allow developers to configure "type" parameters to notify the client when a binding should be re-evaluated.  The following table provides descriptions of the two type parameters currently allowed:

Type ParametersDescription
ONE_WAYNever re-evaluate the binding.
ONE_TIMERe-evaluate the binding when the data frame work
determines that data has changed.

Helpful Tip

When the data framework evaluates a ONE_WAY binding, it configures a listener on the data source to determine when the data has changed.  Each data source is allowed to implement its own listening method on the data it has.  documentDataSource creates structure and attribute change listeners on the XML document.  objectDataSource attempts to add a PropertyChangedListener on the objects it contains.  Once the data source detects a change, it notifies the data framework, which re-evaluates the binding.

PropertChangeListeners Help
StructureChangeListeners Help
AttributeChangeListeners Help

To apply formatting to a binding, add the formatter attribute to the binding tag or the formatter= parameter to the anonymous binding.  The value of the formatter can be either a formatter or formatter chain id.

help See the developer guide for more - Bindings

Iterators

Iterators provide functionality for creating complex UI structures such as tables, lists, and so forth.  By nesting iterators, you can create even more complex structures such as trees and tree tables.  An Iterator replicates its children for each item in a set of data.  The dataSource and select attributes of the iterator tag define the set of data. The following example shows an iterator creating a complex UI structure of panels and buttons within a scrollPane.

<xal>
 <!-- Load the data.xml file into the rss data source -->
 <data:documentDataSource xmlns:data="http://openxal.org/core/data" 
    id="rssDS" source="rss.xml"/>
 <rootPane>
   <borderPane/>
     <scrollPane borderPosition="center">
       <panel>
         <verticalBoxPane boxPaneAlign="stretch" />
           <!-- for each item in the data source (select="//item") -->
           <!-- create the contents of the iterator and place into -->
           <!-- the parent of the iterator -->
           <data:iterator xmlns:data="http://openxal.org/core/data" 
              dataSource="rssDS" type="ONE_WAY" name="rssIterator" 
              select="//item">
              <panel height="100" width="100">
                <borderPane/>
                <!-- bind the text attribute to the "title" element -->
                <!-- for the currently iterator node -->
                <label height="20" text="{*('title')}" width="100" 
                   borderPosition="north"/>
                <panel height="100" width="100" borderPosition="center">
                <borderPane/>
                <button height="25" text="View Story" width="100" 
                        borderPosition="east"/>
                <!-- bind the text attribute to the "description" -->
                <!-- element for the currently iterator node -->
                <label height="20" text="{*('description')}" width="100"
                        borderPosition="center"/>
                </panel>
              </panel>
           </data:iterator>
         </panel>
       </scrollPane>
    </rootPane>
</xal>

Studio Tip

Studio provides two features for creating complex structures with iterators. 

  1. In the Data Mapping perspective, you can drag and drop either a:
    - class object from the Package Explorer, or
    - an XML node from Eclipse's built in XML editor
    onto a table. Nexaweb Studio opens a Wizard that aids you in populating the table with the data.  When you finish populating a table, it is completely ready to display information with in the data source.
  2. You can use Nexaweb Studio to create complex UI structures as in the code snippet above. Once created, you can select the UI structure and select the Wrap with Iterator menu from the UI objects context menu.  Using Nexaweb Studio to create the UI before placing it in the iterator allows you to visualize results before running the application, thus saving you time debugging the presentation.

help See the developer guide for more - Iterators

Formatters

The data framework supplies the following formatters, which wrap the functionality supplied by an underlying Java formatting object.

<messageFormatter/>Wraps the java.text.MessageFormat objectGo to online JavaDocs
<decimalFormatter/>Wraps the java.text.DecimalFormat objectGo to online JavaDocs
<dateFormatter/>Wraps the java.text.DateFormat objectGo to online JavaDocs
<currencyFormatter/>Wraps the java.text.NumberFormat objectGo to online JavaDocs
help See the developer guide for more - Formatters

These four formatters can take care of most application formatting needs, but not all. For example, you will need to create a custom formatter to display a properly formatted phone number, since Nexaweb doesn't supply a formatter for this. There are two ways to make your own:

  1. Creating a plug-in.
  2. Using a generic formatter tag with a custom class and an XML UI file.

If you are creating a single project and not looking to re-use a formatting tag's functionality, use the generic tag as the simplest solution.

You chain formatters together using the formatterChain tag.

help See the developer guide for more - Formatter Tags

Custom code

First, create a class that extends the AbstractJavaFormatter class. This class implements the Formatter interface and handles initialization. Then, implement the class' formatObject method; this method performs the actual formatting of the supplied object into a new one.

Below is a link to the Java code for the PhoneNumberFormatter. The formatObject method takes the incoming phone number object and converts it to a conventionally printed phone number string. 

Code View source file

Declaring the formatter

Now in any XML UI you can use the formatter by adding the following line:

<data:formatter xmlns:data="http://openxal.org/core/data" id="phoneNumberFormatter" class="com.acme.formatters.PhoneNumberFormatter" dotNotation="true">/>

You can supply additional attributes or child tags of the formatter. You can use this extra information to initialize your formatter differently and to supply users with options to customize the functionality. In the example code above includes an additional dotNotation attribute that selects whether the formatObject method will format the phone number as xxx.xxx.xxxx or as (xxx) xxx-xxxx. I have supplied XML that uses the phone number formatter to display a traditional number as well as a dot notation phone number. 

To try this code, create an XML file called data.xml that has <data phoneNumber="1234567890"/> as its content.

 

Helpful Tip

Since the incoming object can be of many possible types, you will need to use the type conversion service supplied to change the incoming object of the formatObject method into a type with which your code can easily work. The interface TypeConversionService encapsulates functionality for changing types.

Convert the incoming object instance by calling ClientSession.getTypeConversionService.convertToString(incomingObject).

 

Putting it all together

Using Nexaweb data framework along with Nexaweb Studio's functionality can greatly reduce the amount of code needed to integrate your application with any back end data sources. Look for ways to effectively use one way data binding within applications. For example, you can implement one way data binding of shared XML documents. You can modify shared XML documents from the server, thus automatically synchronizing changes and triggering UI updates without the need to write any client side code.

Nexaweb

By Bob Buffone