UIX Developer's Guide
Go to Table of Contents
Contents
Go to previous page
Previous
Go to next page
Next

4. Data Binding

This chapter tells how to create pages that generate dynamic content for each user, even though the node tree itself remains unchanged, by using uiXML's data binding attributes. You will learn how UIX supports JavaBeans and Maps for grouping data, and how UIX iterates through data with arrays and Lists. You'll also learn how to build inline data directly into your uiXML for prototyping. Next, Java developers can learn about the DataObject, BoundValue, DataProvider, and DataObjectList interfaces that support this functionality, and how you can use these Java APIs to provide more customized implementations of data binding. Finally, you'll learn the details about the UIX support for JavaBeans.

This chapter contains the following sections:

Data binding from uiXML

The next several sections will introduce UIX data binding from the perspective of a uiXML developer. If you're only developing UIX with the Java API, you might skip ahead to Data binding from the Java API.

Dynamic Attributes in UIX

Let's start with the simplest possible example, the very first example from the last chapter:

<text xmlns="http://xmlns.oracle.com/uix/ui"
      text="hello, world"/>

Now, let's add support for data binding "hello, world" to something more interesting. In particular, let's give ourselves a very simple JavaBean that can display the current date and time:


 package yourpackage;
 import java.util.Date;

  public class CurrentDateBean
  {
    public CurrentDateBean() { }

    public String getTime()
    {
      return (new Date()).toString();
    }
  }

Now, we want to change our page so that it uses getTime(). We'll need to do three things:

  1. Tell UIX to data bind the "text" attribute.
  2. Add a <dataScope> to the page to provide data to the content.
  3. Write a small "data provider" in Java that can access the bean.

First, we'll data bind "text":

<text xmlns="http://xmlns.oracle.com/uix/ui"
      xmlns:data="http://xmlns.oracle.com/uix/ui"
      data:text="time@currentDate"/>

We've made three small changes to the example:

If you tried running this example, you'd see absolutely nothing. That's because we haven't given "currentDate" to the page, so the databinding failed, and the "text" is left to null. We do this by adding <dataScope> to the page:

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
                    xmlns:data="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="currentDate">
      <method class="yourpackage.DataDemo" method="getCurrentDate"/>
    </data>
  </provider>
  <contents>
    <text data:text="time@currentDate"/>
  </contents>
</dataScope>

This looks like a much greater change. Let's walk through what's happened:

If you try typing in this page and running it, you'll get an error when UIX can't find the "yourpackage.DataDemo" class. We still need to write that class:


  package yourpackage;
  import oracle.cabo.ui.RenderingContext;

  public class DataDemo
  {
    static public Object getCurrentDate(
      RenderingContext context, String namespace, String name)
    {
      return new CurrentDateBean();
    }
  }

This is another simple class. Java data providers you write that are referenced by the uiXML <method> element all must have the same signature you see here. We don't need any of these three parameters in this example - we just need to return our JavaBean. You can, if you wish, move this method into the CurrentDateBean class, but we generally recommend against that coding style. Your JavaBeans should be designed to be reusable in any architecture, and as free as possible of dependencies on UIX, Servlets, or any other technology.

Before we continue, let's consider two questions:

  1. When will DataDemo.getCurrentDate() be called?

    DataDemo.getCurrentDate() will be called once each time the page renders. Even if CurrentDateBean had multiple properties, and we used all of them, the bean will get loaded once. But getCurrentDate() will get called every time the page renders. If you have to display the page 100 times, this method will be called 100 times.

  2. When will CurrentDateBean.getTime() be called?

    CurrentDateBean.getTime() will be called at least once. UIX can't guarantee that it will ask for each property only once; it may be necessary to ask repeatedly. Since getTime always creates a new Date object, if you use "time" repeatedly on a page, you may see different times in the page. If this is a problem, you could create the Date in the CurrentDateBean constructor and reuse the same object on each call to getTime().

Using Maps in Data Binding

JavaBeans are an elegant way to encapsulate data, but it can be tedious writing a new JavaBean class for every bit of state you want to hand to UIX, and tedious to add new "get" methods every time a new property arises. And sometimes, it's impossible to know the full list of property names in advance - you'll only discover the names at run-time. UIX supports the Java Map interface for all these scenarios.

Here's a data provider that uses a map instead of a JavaBean:


  package yourpackage;
  import oracle.cabo.ui.RenderingContext;
  import java.util.HashMap;

  public class DataDemo
  {
    static public Object getURLAndText(
      RenderingContext context, String namespace, String name)
    {
      HashMap map = new HashMap();
      map.put("text", "Oracle Corporation");
      map.put("url", "http://www.oracle.com");
      return map;
    }
  }

And, here's the UIX that consumes it:

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
                    xmlns:data="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="link">
      <method class="yourpackage.DataDemo" method="getURLAndText"/>
    </data>
  </provider>
  <contents>
    <text data:text="text@link"
           data:destination="url@link"/>
  </contents>
</dataScope>

It's very simple to develop this way, but not quite as elegant as using JavaBeans. It's a matter of personal taste which to use at which times, and most developers will want to use both techniques.

The <boundAttribute> element

Let's extend our first example a bit further. Instead of simply displaying the time, let's show "The time is: " followed by the time. We could just add another <text> element containing the static text, but we'll take a different approach. When you use "data:", you can get a value off of a JavaBean, but you can't manipulate that value. The <boundAttribute> element lets you go further. You can perform some very complicated expressions; we'll use a simple concatentation rule:

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
                    xmlns:data="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="currentDate">
      <method class="yourpackage.DataDemo" method="getCurrentDate"/>
    </data>
  </provider>
  <contents>
    <text>
      <boundAttribute name="text">
        <concat>
          The time is:
          <dataObject select="time" source="currentDate"/>
        </concat>
      </boundAttribute>
    </text>
  </contents>
</dataScope>

The only part of our example that's changed is the <text> element. We're no longer databinding "text" using "data:text"; instead, we're using <boundAttribute>. It contains a <concat> element that joins together the "time" from our bean (grabbed with a <dataObject> element) with a static string.

There are dozens of elements that can be used inside <boundAttribute>, and we're not going to go into all of them here. You can look at these elements using Code Insight in the uiXML editor in JDeveloper, or by browsing the UIX Element Reference. These elements are called BoundValue elements after the Java interface to which they correspond. Some of the other useful BoundValue elements include:

Many of these will be described in detail in later chapters, but see the Element Reference for the full list.

Iterating Through Data

By now, you can set up pages with dynamic data. However, the interfaces you've seen only support unordered bags of properties. It's a very inconvenient interface if you need to display tabular data, or any other data that looks like a Java array. UIX supports Lists and Java arrays for iteration. It also supports a UIX-specific API, DataObjectList, as you'll see later.

Now, how to get the data back out of these arrays and Lists and into your pages? For this, you'll need something UIX calls the "current DataObject." Any data framework has to support iterating through data. For Java, it's a "for" loop. For a database, it's a cursor. For UIX Components, it's the "current DataObject." To get data out of the current DataObject, you omit the source altogether:

<styledText>
 <boundAttribute name="text">
   <dataObject select="textKey"/>
 </boundAttribute>
</styledText>

<!-- OR -->

<!-- Note that there's no "@" sign here -->
<styledText data:text="textKey"/>

You can explicitly set the current DataObject using the "currentData" attribute of a <dataScope>:

<dataScope data:currentData="currentDate">
 <!-- Now we have a current data object -->
 ...
</dataScope>

... but this is not the usual way it's used. More commonly, you use a component that will automatically step through your List or array for you and automatically set teh current DataObject. The UIX <table> is one such component. We're not going to discuss <table> in a lot of detail here - it has enough features to merit its own chapter - but we'll describe it just enough to understand how the current DataObject works.

Let's start by building up a List for our table. Here, we'll use an ArrayList of Maps. We could just as easily use an array of Maps, or an array of JavaBeans, etc.


package yourpackage;

//...
import java.util.List;
import java.util.ArrayList;

public class DataDemo
{
  // ...

  static public Object getTableData(
     RenderingContext context, String namespace, String name)
  {
    List list = new ArrayList();
    for (int i = 0; i < 5; i++)
    {
      HashMap row = new HashMap();
      row.put("text", "Row " + i);
      row.put("text2", "Column 2, row " + i);
      list.add(row);
    }

    return list;
  }
}

Now, we'll put this data into a <table>. Each column of a <table> is represented by a single child inside <contents>. If you have three such children, the table will have three columns.

The trick lies in getting each child to render differently in each row. And this happens with the "current DataObject". When the table is rendering its first row, the current DataObject will point at the first Map in our List. When the second row is rendering, the current DataObject will point at the second Map in our List. And so forth.

So, let's add two columns to our table, first a normal text column, then a column in red. (In this example, we're using the built-in "OraErrorText" CSS style - one of the built-in UIX styles.):


<dataScope>
  <provider>
    <data name="someTableData">
      <method class="yourpackage.DataDemo" method="getTableData"/>
    </data>
  </provider>
  <contents>
    <table data:tableData=".@someTableData">
      <contents>
        <styledText data:text="text"/>
        <styledText styleClass="OraErrorText" data:text="text2"/>
      </contents>
    </table>
  </contents>
</dataScope>

And done. Not much code, but there's a lot fine points.

First, the <table> element gets its data, using a data bound "tableData" attribute. The oddest thing here is that period (".") in our data binding expression. If you remember how we wrote getTableData(), we returned the ArrayList. For the table's data, we don't want a property of the ArrayList - we want the ArrayList itself. This is a magic syntax that accomplishes that.

Next, we've added two children. The first is a <styledText> that gets its text from the "text" key. Note the absence of any at-sign ("@") here - that means "get 'text' from the current DataObject". The second is another <styledText>, with its text bound to a different key. We can add columns, remove columns, and reorder columns directly in the UI, without changing our data model at all.

This is a trivial example: we haven't added column or row headers, or any of roughly one zillion other things the table can do. And we've only used ordinary, uneditable text. But all of these features work with the same technique. Any time you need something to render differently from one row to the next in a table, there must be some piece of information in the "current DataObject" that describes that difference.

For example, say you want a link in each row, but it goes to a different location. Simple! Use a <link> element, databind the "destination" attribute to the current DataObject, and update your data to include a destination in each row. In UIX, everything can be data bound - so every row can be different.

Complex Data Binding in UIX

This section describes how you can nest keys and data objects to produce more complex data bindings. First of all, use the following form to produce a nested key data binding:

data:attributeName="keyN@...@key2@key1@name"

In this case, when the value of this attribute needs to be computed, key1 is used with the DataObject referenced by name to produce another DataObject. key2 is used with this second DataObject to produce a third DataObject. This process continues until keyN is used to produce the final value of this attribute. Note that all the DataObject values must themselves be DataObjects; only the value of the last key, keyN, need not be a DataObject (it must be whatever type is required by the attribute). The following is an example of nested key binding:

<dataScope>
 <provider>
   <data name="families">
     <inline>
       <Smith members="4">
         <address number="2255" street="37th Ave" city="San Francisco"
            state="CA" zip="94116" />
       </Smith>
       <Jones members="3">
         <address number="500" street="Oracle Parkway" city="Redwood Shores"
           state="CA" zip="94065" />
       </Jones>
     </inline>
   </data>
 </provider>
 <contents>
   <messageTextInput prompt="The Smiths live in"
        data:text="city@address@Smith@families" /> <!--San Francisco-->
   <messageTextInput prompt="The Jones's live in"
        data:text="city@address@Jones@families" /> <!--Redwood Shores-->
 </contents>
</dataScope>

In the above example, the first messageTextInput produces The Smiths live in San Francisco, while the second produces The Jones's live in Redwood Shores.

Parentheses may be used to produce nested data object bindings. Consider the following example:

data:attributeName="(key1@name1)@name2"

In the above example key1 is used with (the DataObject referenced by) name1 to produce the key that will be used with name2 to produce the final value of this attribute. This is an example of the key itself being data bound. The data object part can also be data bound; the following two examples are equivalent:-

<!-- These two are equivalent -->
data:attributeName="key2@(key1@name)"
data:attributeName="key2@key1@name"

The following is a parenthesis example using the families data structure created in the previous example; it produces The Jones family has 3 members.

<dataScope>
 <provider>
   <data name="families">
      ... as above ...
   </data>
   <data name="selection">
     <inline current="Jones" />
   </data>
 </provider>
 <contents>
    The<link data:text="current@selection"/> family
    has<link data:text="members@(current@selection)@families" />
    members.
 </contents>
</dataScope>

Parentheses may be nested as in the following example:

data:attributeName="((key1@name1)@name2)@name3"

In the above example, key1 is used with name1 to produce the key that will be used with name2 to produce the key for name3 that would compute the final value.

Text before and after the @ symbol is optional. If the key is left out, then the DataObject itself is returned, as in the following example:

<!-- The following attribute is bound to the data object itself -->
data:attributeName="@name"

And if the data-object-name part is left out, the key will be used with the current data object (this is the default case when no @ symbol is provided), as in the following examples:

<!-- These keys are used with the current data object  -->
data:attributeName="key@"
data:attributeName="key"

If no @ symbol is provided an implicit one is assumed (as in the second binding in the above example). The following example illustrates this further:

<!-- These are different bindings  -->
data:attributeName="key@name"
data:attributeName="(key@name)"

The two bindings in the above example are different; the latter binding has an implicit @ and could have been written as data:attributeName="(key@name)@". In the latter case, the value returned by applying key to the data object name is used as a key into the current data object.

There are certain restrictions on keys and data object names; for example, you can't have the symbol @ as part of a key's text. A future version of UIX will allow you to escape such characters and permit their usage in UIX.

The DataObject API

The DataObject Java interface is the generic data source for UIX Components and uiXML pages. When you hand JavaBeans or Maps to UIX, we convert those objects into DataObjects with a lightweight adapter class. We'll discuss that adapter later; here, we'll talk about the Java API for DataObject itself, and why you might care. DataObject is a very simple interface, with only a single method:

public interface oracle.cabo.ui.data.DataObject
{
  /**
   * Select a single value out of the DataObject.
   * @param context the current RenderingContext
   * @param select  a select key
   * @return the selected value
   */
  public Object selectValue(RenderingContext context, Object select);
}

It's such a small interface that it places almost no requirements on the developer or the structure of the data. This makes it easy to adapt any form of data, like:

UIX includes built-in bindings to BC4J (see Business Components for Java Integration) and built-in bindings to JavaBeans and Maps, which we've already shown.

When developers first see the "select" parameter, most naturally think of Hashtable keys, but a DataObject doesn't need to work like that at all. The "select" parameter could be:

Most of the time you're developing with UIX, there's no reason to code against the DataObject API. But there are a few good reasons to consider using DataObject instead of Maps and JavaBeans. DataObjects are the actual abstraction UIX uses for data. So, when you're using our Java API to create our beans, you'll have to use DataObjects, since that's what the methods take. (You can use BeanAdapterUtils to easily convert JavaBeans and Maps into DataObjects, as we'll see later.)

In addition, our built-in support for JavaBeans doesn't quite come for free. There are performance costs. First, we need to create adapter objects that implement DataObject. These are fairly lightweight, but object creation is always worth avoiding if possible. Second, introspection - dynamically discovering and calling methods at runtime - is inherently slow. We do a good job at caching the most expensive part of this process - finding the Method objects - but calling into the Method objects is still slower than a pre-compiled method call in a hand-coded DataObject.

Still, remember the well-known axiom of programming: "Premature optimization is the root of all evil." Just because hand-coded DataObjects are faster doesn't mean you should spend time writing DataObjects. When used in moderation, there's no reason why using JavaBeans should seriously impact performance. And for JavaBeans that are inherently slow - because they need to access the database, for example - the small additional overhead of the adapter is largely irrelevant. But if you perform profiling and discover that the overhead of adapting one of your beans is significantly affecting performance, you can attack the dual problems of object creation and introspection overhead. Later, you'll see some easier ways to tackle the performance problem.

Finally - and most usefully - DataObjects receive a RenderingContext. A RenderingContext has a lot of useful values. Here, let's use the Locale to properly format our date string:


 package yourpackage;
 import oracle.cabo.ui.RenderingContext;
 import oracle.cabo.ui.data.DataObject;
 import java.text.DateFormat;
 import java.util.Date;
 import java.util.Locale;

  public class CurrentDateObject implements DataObject
  {
    public Object selectValue(RenderingContext context, Object select)
    {
      if (!"time".equals(select))
        return null;
      Locale locale = context.getLocaleContext().getLocale();
      DateFormat formatter =  DateFormat.getDateTimeInstance(
        DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
      
      return formatter.format(new Date());
    }
  }

If you use this class instead of CurrentDateBean, the date will automatically be formatted correctly for the current Locale.

Building data and lists of data in UIX

It's possible to define both data and lists of data right in your UIX. Often, this is useful because it lets you dummy up data before your page is attached to a real data source.

First, let's introduce a new DataProvider element: <inline>.

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
           xmlns:demo="http://www.example.org/">
 <provider>
   <data name="demo:linkData">
     <inline>
        .... data object is in here ...
     </inline>
   </data>
 </provider>

 <contents>
    ...
 </contents>
</dataScope>
It behaves much like the <method> element we saw before, but doesn't call out to Java code. You'll define the data right inside your UIX.

Any attributes of the <inline> define key/value pairs in the data it creates. For instance,

<inline url="http://example" text="Inline text">
</inline>
will define data with two values, one at key "url", the other at key "text". Simple enough.

But you can also add data and lists of data as subvalues, using child elements. The name of the element is the key where that child data or list of data will be stored. If the same element name appears more than once, the elements together define a list of data. If it appears once, it actually defines both a single bag of data and a single-element list of data! An example should help clarify:

<inline foo="Inline text">
 <stuff random="Stuff text"/>

 <row text="First row"/>
 <row text="Second row"/>
</inline>
This example gives you data that contains three things: Pretty simple. Now, let's go back and repeat that table example, only using inline data:
<dataScope>
 <provider>
   <data name="inlineData">
     <inline>
       <tableKey text="Row 0" text2="Column 2, Row 0"/>
       <tableKey text"Row 1" text2="Column 2, Row 1"/>
        ... etc ...
       <tableKey text="Row 4" text2="Column 2, Row 4"/>
     </inline>
   </data>
 </provider>

 <contents>
   <table data:tableData="tableKey@inlineData">
     <contents>
       <styledText data:text="text"/>
       <styledText styleClass="OraErrorText" data:text="text2"/>
     </contents>
   </table>
 </contents>

</dataScope>

Data binding from the Java API

The next several sections will discuss many details specific to the Java API. Even if you're developing entirely from uiXML, this may be of interest if you want to know how your uiXML works or you want to extend the framework.

Dynamic Attributes: BoundValue

The BoundValue interface is the key to dynamic pages in UIX Components. It's simply a single method - a RenderingContext goes in, and any Java object comes out:

public interface oracle.cabo.ui.data.BoundValue
{
  public Object getValue(RenderingContext context);
}

What makes this simple interface useful is that it can be used in place of any attribute value in any UIX Components bean. If the value of an attribute is set to a BoundValue, then getting the value of that attribute won't return the BoundValue. Instead, UIX Components will call through BoundValue.getValue(), and return the result instead. For example:

  // Remember the code to set the text of a StyledTextBean...
  styledTextBean.setText("Some text");
  // ... is really a cover for:
  styledTextBean.setAttributeValue(UIConstants.TEXT_ATTR,
                                   "Some text");
                                   
    // So you can also set a BoundValue:
  BoundValue boundValue = ...;
  styledTextBean.setAttributeValue(UIConstants.TEXT_ATTR,
                                   boundValue);

Then, at display time, if we ask the StyledTextBean for its text:

  text = styledTextBean.getAttributeValue(renderingContext,
                                          UIConstants.TEXT_ATTR)
  // .. is equivalent to writing:

  BoundValue boundValue = ...;
  text = boundValue.getValue(renderingContext);

Keep in mind: you can use setAttributeValue() with a BoundValue and data bind any attribute. It doesn't matter if that bean has convenience methods for data binding that attribute, or if anyone else has ever thought of data binding that attribute before, or what type of Java Object that attribute requires. Every attribute can be data bound.

Many of our beans do offer convenience methods for binding their attributes. In fact, StyledTextBean's "text" attribute is one of these. So, we can write:

  BoundValue boundValue = ...;
  styledTextBean.setTextBinding(boundValue);  

But this is only a convenience. Any attribute can be bound on any bean, whether or not we've added a convenience method.

Example: the Current Date

While we provide many implementations of BoundValue, let's start with a custom implementation. The following code will always return the current date:

  public class CurrentDate implements BoundValue
  {
    public Object getValue(RenderingContext context)
    {
      return new Date();
    }
  }

Now, let's use this with a DateFieldBean:

  DateFieldBean dateField = new DateFieldBean();
  dateField.setValueBinding(new CurrentDate());

The date field this produces will always be initialized with the current date. This might seem pretty similar to:

  DateFieldBean dateField = new DateFieldBean();
  dateField.setValue(new Date());

But there's a big difference. In this second example, the date gets set once. That's fine if you're using the bean once, then throwing it away. But UIX Components beans can be reused, over and over again. With the BoundValue you used in the first example, UIX Components will ask for the date every time it has to render the page - so the date will still be correct, days or months after the DateFieldBean was created!

Next, we'll show how to write your own DataObject class.

Example: BundleDataObject

This class turns a Java ResourceBundle into a DataObject. This class is so useful that it's part of UIX Components, but walking through it shows the basic ideas in implementing a DataObject. This example also shows one way to handle exceptions in DataObjects (see the Handling Errors chapter for other approaches.)

  import java.util.MissingResourceException;
  import java.util.ResourceBundle;

  import oracle.cabo.ui.data.DataObject;

  public class BundleDataObject implements DataObject
  {
    public BundleDataObject(ResourceBundle bundle)
    {
      _bundle = bundle;
    }

    public Object selectValue(RenderingContext context, Object select)
    {
      try
      {
        return _bundle.getObject(select.toString());
      }
      catch (MissingResourceException e)
      {
        context.getErrorLog().logError(e);
        return null;
      }
    }

    private ResourceBundle _bundle;
  }

Things to note:

  1. By wrapping up the ResourceBundle, we only allocate one object. If we'd created a Map and pulled all the data out of the ResourceBundle, we would have had to create many, many more objects. Likewise, we'll only get the keys actually used at runtime.
  2. DataObjects should avoid throwing exceptions. Here, we detect any MissingResourceExceptions, and log them on the standard UIX ErrorLog before returning a safe value - null.

You should feel very comfortable writing your own implementations of DataObject. Custom implementations can help keep your application efficient and performant.

DataObjects and the RenderingContext

Once you've created DataObjects, you have to make them available to your UIX Components page by attaching them to the RenderingContext interface. (As a reminder, this interface gives UIX Components beans and uiXML pages the context they need to render.) We're interested here in one particular method of RenderingContext:

  public DataObject getDataObject(String namespaceURI,
                                  String localName)

This method gives a UIX Components bean or BoundValue access to any number of DataObjects. As usual in UIX, we've partitioned these DataObjects by two separate strings. You can pick one or more namespaces based on the URL of your web site. Then, pick any local names without conflicts with other code.

Binding to DataObjects: DataBoundValue

For a very little while, we'll leave aside the question of how DataObjects get into a RenderingContext, and show how you get data out of a DataObject and into your UINodes or UIX. This is where the DataBoundValue class comes in. In UIX Components, the DataBoundValue class lets you get at the DataObjects on the RenderingContext.

  LinkBean bean = new LinkBean();
  DataBoundValue destinationBinding = 
    new DataBoundValue(_EXAMPLE_NAMESPACE,
                       "dataObjName",
                       "url");
  bean.setAttributeValue(UIConstants.DESTINATION_ATTR,
                         destinationBinding);

  // ...

  static private final String _EXAMPLE_NAMESPACE =
     "http://www.example.org";

This is equivalent to executing the following Java code while rendering:

  // ...
  DataObject dataObject = context.getDataObject("http://www.example.org",
                                                "dataObjName");
  Object destination = dataObject.selectValue(context, "url");

  //  ...

Because DataBoundValue is so useful, our beans have convenience methods that will create the DataBoundValue for you:

  LinkBean bean = new LinkBean();
  bean.setDestinationBinding(_EXAMPLE_NAMESPACE
                             "dataObjName",
                             "url");

Registering DataObjects: DataProviders

By now, you can write your own DataObjects, you can get them from a RenderingContext, and you can bind them to attributes. What's missing from the picture is a way to get the DataObjects into the RenderingContext in the first place. This happens with (you guessed it, another interface) the DataProvider interface. This is another small interface:

  public interface DataProvider
  {
    public DataObject getDataObject(
      RenderingContext context,
      String           namespace,
      String           name);

    // ... methods omitted
  }

There's some methods we've left out, but getDataObject is the important one: it's the method RenderingContext will call to get at DataObjects. And since it gets passed the namespace and name, each DataProvider can serve up many different DataObjects. Only one thing's left: a way to attach a DataProvider to a RenderingContext - for that, we only need to introduce one more method:

  public interface RenderingContext
  {
    // ...

    public void addDataProvider(DataProvider provider); 

    // ...
  }

You can call this method as many times as you want to hook your DataProviders up to a RenderingContext. (In practice, you'd be better off putting all your DataProviders inside a TableDataProvider, then putting that inside a CachingDataProvider). And we're there! Now, let's recap before we go to some examples of the whole stack in action. See Figure 4-1.

Figure 4-1: Relationships among DataObjects, DataProviders, RenderingContexts, and UINodes

Logical representation of relationships among DataObjects, DataProviders, RenderingContexts, and UINodes

  1. A DataProvider is a collection of DataObjects.
  2. A DataObject is a collection of arbitrary data.
  3. A RenderingContext gets DataObjects from DataProviders.
  4. A DataBoundValue object (or a UIX dataObject element) connects everything to a UINode attribute.

DataProvider Example: UIX Components

Let's create a very simple page. It will have only one link, and databind its contents to a single DataObject. And we'll display it all in a JSP. First, we'll define some constants:

public class DataDemo
{
  // ...
  private static final String _DEMO_NAMESPACE   = "http://www.example.org/";
  private static final String _DATA_OBJECT_NAME = "TextData";

  private static final Object _TEXT_KEY = "textKey";
  private static final Object _DESTINATION_KEY = "urlKey";
}

Now, let's write the function that creates the UIX Components nodes:

public class DataDemo
{
  static public UINode getPage()
  {
    // An ultra-simple example - we'll create a single link:
    LinkBean link = new LinkBean();
    link.setTextBinding(
      new DataBoundValue(_DEMO_NAMESPACE,
                         _DATA_OBJECT_NAME,
                         _TEXT_KEY)
                       );

    // Or, we can use the built-in DataBoundValue convenience methods
    link.setDestinationBinding(_DEMO_NAMESPACE,
                               _DATA_OBJECT_NAME,
                               _DESTINATION_KEY);
 
    // And put the link inside of a "BodyBean" (see below)
    BodyBean body = new BodyBean();
    body.addIndexedChild(link);
    return body;
  }

  // ...
}

This is a lot like the code we wrote earlier. We create a link bean, and bind two attributes to a data object. The only new piece is the BodyBean. This isn't part of data binding - it's the UIX Components bean that should always be used to create the HTML <body> tag.

And now, we'll write the function that sets up the DataProvider.

Note: This is still a primitive example - the data is hardcoded, and we're using the rudimentary DictionaryData class to store it. But we could pass in the JSP PageContext - you could use that context to get whatever state you need here. What's more, the RenderingContext is passed in to both the DataProvider and the DataObject when they are asked for data. And, since you can get all the usual Servlet objects from a RenderingContext - ServletRequest, HttpSession, etc., there's no reason to load all the data up front. Instead, you can wait until the moment the data is actually requested.

public class DataDemo
{
  // ...

  static public DataProvider getDataProvider()
  {
    DataObject data = _getData();

    // And put it in a DataProvider
    TableDataProvider provider = new TableDataProvider();
    provider.put(_DEMO_NAMESPACE, _DATA_OBJECT_NAME, data);

    return provider;
  }

  static private DataObject _getData()
  {
    // Build up a DataObject
    DictionaryData data = new DictionaryData();
    data.put(_TEXT_KEY, "Shameless promotion!");
    data.put(_DESTINATION_KEY, "http://www.oracle.com");

    return data;
  }


  // ...
}

Note the TableDataProvider we've used here. This class handles partitioning up DataObjects or other DataProviders by namespace and name.

Now, at last, let's write the JSP that renders this:

<%@ page contentType="text/html" %>
<%@ page import='oracle.cabo.ui.ServletRenderingContext'%>
<%@ page import='oracle.cabo.ui.beans.StyleSheetBean'%>
<%@ page import='oracle.cabo.ui.data.DataProvider'%>
<%@ page import='yourpackage.DataDemo'%>
<html>
<head>
<%
  // Create a rendering context
  ServletRenderingContext rContext =
    new ServletRenderingContext(pageContext);

  // Include the stylesheet that UIX Components needs
  StyleSheetBean.sharedInstance().render(rContext);
%>
</head>
<%
  // Get the data provider, and attach it to the
  // rendering context
  DataProvider provider = DataDemo.getDataProvider();
  rContext.addDataProvider(provider);

  DataDemo.getPage().render(rContext);
%>
</html>

If you run this example, you'll see the words "Shameless promotion!" linked to the Oracle web site.

For this example, we've also introduced another bean - StyleSheetBean. This bean automatically adds the stylesheet that all UIX Components pages need. Later chapters discuss the power and customizability this bean gives you with a single line of code. For now, know that it belongs in every <head> HTML.

That's all you need to do. Admittedly, this is overkill for rendering one little link. But with a generic DataProvider that can talk to a database or provide up localized text, or any other source of localized data.

Iterating Through Data

By now, you can use BoundValue and DataObject to set up pages with dynamic data. However, the DataObject interface just supports a bag of properties. It's a very inconvenient interface if you need to display tabular data, or any other data that looks like a Java array. This is where the DataObjectList interface comes in.

DataObjectLists

The last new interface introduced in this chapter is a simple one:

  public interface DataObjectList
  {
    public int getLength();
    public DataObject getItem(int index);
  }

A DataObjectList is at heart a DataObject array. (By not actually using an array, we can keep DataObjectLists immutable - there's no methods to "set" anything - and we let you lazily create DataObjects or reuse DataObject instances.)

The ArrayDataSet class is a conveniently simple implementation of DataObjectList. Like DictionaryData, it's simple to use, but forces you to create all the DataObjects ahead of time:

  DataObject[] array = new DataObject[10];
  for (int i = 0; i < 10; i++)
  {
    array[i] = ...; 
  }

  ArrayDataSet list = new ArrayDataSet(array);

So remember: you should expect to write your own implementations of DataObjectList.

Binding to DataObjectLists: Current DataObject

Now, how to get the data back out of a DataObjectList and into your pages? For this, you'll need the "current DataObject."

Any data framework has to support iterating through data. For Java, it's a "for" loop. For a database, it's a cursor. For UIX Components, it's the "current DataObject."

You'll remember the RenderingContext.getDataObject method introduced earlier in this chapter. There's another method for getting a DataObject that we hadn't mentioned before now:

public interface RenderingContext
{
  // ..

  /**
   * Get a DataObject by namespace and name.
   */
  public DataObject getDataObject(String namespaceURI,
                                  String localName)

  /**
   * Get the "current" DataObject.
   */
  public DataObject getCurrentDataObject();

  // ..
}

This new method, getCurrentDataObject(), returns this "current DataObject" - the UIX Components equivalent of a database cursor, and what you'll use to bind to a DataObjectList.

To get data out of the "current DataObject" in Java, you can use a special one-argument constructor of DataBoundValue, or a one-argument convenience method in our beans:

  StyledTextBean textBean = new StyledTextBean();
  textBean.setAttributeValue(UIConstants.TEXT_ATTR,
                             new DataBoundValue(TEXT_KEY));

  // ... OR, EASIER ...
  
  textBean.setTextBinding(TEXT_KEY);

To put this all together, let's start building up a DataObjectList for a TableBean:

public class DataDemo
{
  // ...

  static private DataObjectList _getDataForTable()
  {
    DataObject[] rows = new DataObject[5];
    for (int i = 0; i < rows.length; i++)
    {
      DictionaryData row = new DictionaryData();
      row.put(_TEXT_KEY, "Row " + i);
      row.put(_TEXT_2_KEY, "Column 2, row " + i);
      rows[i] = row;

    }

    return new ArrayDataSet(rows);
  }

  // ...

  private static final Object _TEXT_2_KEY = "text2Key";
}

Again, for the sake of keeping the example simple, we've used hardcoded data, but your application can do much more. Now, we have to get this DataObjectList into our table. Now, we could call:

  TableBean table = new TableBean();
  table.setTableData(_getDataForTable());

but data set that way wouldn't be dynamic, so we couldn't reuse the TableBean from one request to the next. To make the data dynamic, we'll add it into the same DataObject we were using in our old demo. We'll also need an new key, and we'll change our code to add the TableBean to our page:

public class DataDemo
{
  // ...
  static public UINode getPage()
  {
    // ... old code ...
  
    // And now, let's create the table
    TableBean table = new TableBean();
    table.setTableDataBinding(_DEMO_NAMESPACE,
                              _DATA_OBJECT_NAME,
                              _TABLE_DATA_KEY);
    body.addIndexedChild(table);

    return body;
  }


  static private DataObject _getData()
  {
    // Build up a DataObject
    DictionaryData data = new DictionaryData();

    // ... old code ...

    // And now, add the DataObjectList
    data.put(_TABLE_DATA_KEY, _getDataForTable());
    return data;
  }

  
  // ...

  static private final Object _TABLE_DATA_KEY = "tableKey";
}

As before, the DataObject will be added to the RenderingContext using a DataProvider. That code doesn't have to change at all. Also, note that we've used the same old code for binding the table data - "tableData" is simply another attribute, and no different from any other element.

There are a lot of objects here. Let's review what's been created:

  1. A DataProvider that serves up one DataObject.
  2. That DataObject contains two pieces of text and one DataObjectList.
  3. The DataObjectList contains five more DataObjects.
  4. Each of those DataObjects has two pieces of text.

We haven't yet seen the "current DataObject" in action yet, but we'll need to use it once we have added columns to the table. Each column of a TableBean is represented by an indexed child. If you call addIndexedChild() three times, the table will have three columns. Each of these columns renders by using its child element as a stamp once for each row.

The trick lies in getting each element to render differently in each row. And this happens with the "current DataObject". When the table is rendering its first row, RenderingContext.getCurrentDataObject() will return the first DataObject in the "tableData" DataObjectList. When the second row is rendering, getCurrentDataObject() returns the second DataObject. And so forth.

So, let's add two columns to our table, first a normal text column, then a column in red. (In this example, we're using the built-in "OraErrorText" CSS style - one of the styles supplied by the StyleSheetBean.)

public class DataDemo
{
  // ...
  static public UINode getPage()
  {
    // ... old code ...
  
    // And now, let's create the table
    TableBean table = new TableBean();
    table.setTableDataBinding(...);

    // Create the first column
    StyledTextBean firstColumn = new StyledTextBean();
    // Use _TEXT_KEY for the data of the first column
    firstColumn.setTextBinding(_TEXT_KEY);
    // Add it to the table
    table.addIndexedChild(firstColumn);

    // Create the second column, using _TEXT_2_KEY
    StyledTextBean secondColumn = new StyledTextBean();
    secondColumn.setStyleClass("OraErrorText");
    secondColumn.setTextBinding(_TEXT_2_KEY);
    table.addIndexedChild(secondColumn);
  
    // ...
  }
 
  // ...
}

And done. Let's talk about how this renders. The table does a lot of the work, putting in borders and setting backgrounds for you. Then, the first cell renders.

For row 0, column 0, the table uses the first StyledTextBean. And RenderingContext.getCurrentDataObject() will return the first DataObject in the DataObjectList: this has "Row 0" and "Column 2, row 0" as entries. The StyledTextBean asks for its text, which is a DataBoundValue. The "current DataObject" is retrieved and asked for _TEXT_KEY - it returns "Row 0", which renders in that cell.

Then, on to row 0, column 1. The same "current DataObject" is still in effect - but the other StyledTextBean is rendering. It asks for _TEXT_2_KEY, and so it renders "Column 2, row 0".

Now, row 1. We're back to the first StyledTextBean, but now we've moved to the next DataObject in the DataObjectList. So, RenderingContext.getCurrentDataObject() will return a different DataObject. So this row gets filled in with "Row 1" in the first column, and "Column 2, row 1" in the second column.

So things continue throughout the entire table. This is a trivial example: we haven't added column or row headers, or any of roughly one zillion other things the table can do. And we've only used ordinary, uneditable text. But all of these features work with the same technique. Any time you need something to render differently from one row to the next in a table, there must be some piece of information in the "current DataObject" that describes that difference.

For example, say you want a link in each row, but it goes to a different location. Simple! Use a LinkBean databind the "destination" attribute to the current DataObject, and update your DataObjectList to include a destination in each row. In UIX Components, everything can be data bound - so every row can be different.

Adapting JavaBeans, Maps, and Lists: BeanAdapterUtils

For most developers, the mechanisms by which JavaBeans, Maps, Lists, and arrays turn into DataObjects or DataObjectLists can remain magic. However, some will find it useful to know more of the details. In particular, there are some APIs - especially on our Java Beans - that explicitly take DataObjects or DataObjectLists. Second, you may want to customize the adapters to add additional functionality or improve performance. In either case, you'll want to use oracle.cabo.ui.data.bean.BeanAdapterUtils.

To convert an arbitrary object - be it a bean, a Map, or even a DataObject - into a DataObject, use one of the BeanAdapterUtils.getAdapter() methods; most often, you'll want to use:

  public static DataObject getAdapter(RenderingContext context,
                                      java.lang.Object instance)

To convert an array or List into a DataObjectList, use:

  public static DataObjectList getAdapterList(RenderingContext context,
                                              java.lang.Object listInstance)

Improving the performance of the JavaBean adapter

Before trying to improve the performance, make sure that you actually have a problem. The adapter layer is lightweight and performant enough that most applications will not need to bother with optimizing this portion of your code. Until you've run a

First, you can avoid creating the adapter objects each time they are needed. Instead of letting UIX automatically create the adapters, create them yourselves and cache the result. For example:

public class DataDemo
{
  static public Object getLinkBean(
    RenderingContext context,
    String           namespace,
    String           name)
  {
    return _sAdapter;
  }

  static private LinkDataBean _sInstance = 
       new LinkDataBean("http://www.oracle.com",
                        "Shameless bean promotion!");

  // Create a static adapter:
  static private DataObject _sAdapter;
  static
  {
    try
    {
       _sAdapter = BeanAdapterUtils.getAdapter(_sInstance);
    }
    catch (InstantiationException ie) { }
    catch (IllegalAccessException iae) { }
  }
}

This is only helpful if the bean is "long-lived". If the bean is only used in one request, then caching the adapter may not be of much help, but if it's a session- or application-level bean, this can be a small but very simple performance win.

Second, you can attack the performance problems inherent in introspection by writing your own DataObject adapter. Or, even better - let us write the adapter for you! UIX includes a Java-based tool called BuildBeanDOAdapter that takes compiled JavaBeans and automatically writes DataObject adapters. Just type on the command line:

  java oracle.cabo.ui.tools.BuildBeanDOAdapter yourpackage.LinkDataBean

We won't show you all the code this produces, but here's the interesting piece: the selectValue() code produced for our example LinkDataBean:

  public Object selectValue(RenderingContext context, Object select)
  {
    LinkDataBean instance = _instance;
    if (instance == null)
      return null;

    try
    {
      if ("url".equals(select))
        return instance.getUrl();
      if ("text".equals(select))
        return instance.getText();
    }
    catch (Exception e)
    {
      context.getErrorLog().logError(e);
    }
    return null;
  }

This is a lot faster than introspection. In a recent benchmark (Java 1.3.0 on Windows 2000), it was twenty to thirty times faster. However, for very, very large JavaBeans with hundreds of properties, these calls to String.equals() become a serious problem and can actually make this "optimization" hurt performance! For these cases, the adapter-building tool supports a "-fast" command-line option. The resulting class uses some trickery with String hashcodes to greatly increase the speed of selectValue() to once again be faster than basic introspection. Whichever way you build your adapter class, these adapters are also much smarter than introspection about converting boolean and int return values into Boolean and Integer objects.

Once you've created these adapter classes, you could change your data binding code to explicitly create these adapters:

public class DataDemo
{
  static public Object getLinkBean(
    RenderingContext context,
    String           namespace,
    String           name)
  {
    LinkDataBean  bean = ...;
    return new LinkDataBeanDataObject(bean);
  }
}

...but it'll be tedious (and error-prone) to make sure you've caught all the cases. Adapters can instead be directly registered with BeanAdapterUtils with their static registerAdapter() call. If you add this call in your initialization code, the rest of your code can continue returning your bean instance as it always did:

public class DataDemo
{
  static public Object getLinkBean(
    RenderingContext context,
    String           namespace,
    String           name)
  {
    // Under the covers, UIX will use LinkDataBeanDataObject to
    // adapt this bean, but we don't to know that here! 
    return new LinkDataBean(...);
  }

  // Register the adapter once
  static
  {
    LinkDataBeanDataObject.registerAdapter();
  }
}

Support for Psuedo-JavaBeans

UIX don't require your data to be fully comply with all aspects of the JavaBeans specification, though we happily accept classes that are. We'll automatically handle all of the following situations:

  1. Classes don't need to have no-argument constructors, or even any public constructor at all.
  2. Classes don't need to be serializable.
  3. Classes don't have to have "set" methods - we only care about "get" (for data binding, at least.)
  4. We support accessing public fields, not just methods, so a LinkDataBean could be written as:
    public class LinkDataBean
    {
      public LinkDataBean(String url, String text)
      {
        this.url = url;
        this.text = text;
      }
    
      public String url;
      public String text;
    }

    This isn't generally considered good design for your own JavaBeans, but it does mean that we can support legacy classes like java.awt.Point without any special adapters.

  5. JavaBeans requires that your class itself be public, not just the methods. This is an annoying limitation - good design principles sometimes recommend against making these classes public. UIX introspection supports the following alternatives: