$URL: svn+ssh://alci@svn.forge.objectweb.org/svnroot/barracudamvc/Barracuda2/tags/2.1/WEB-INF/src_docs/architecture/comp/overview.html $ - $Revision: 158 $

High Level Overview

This document attempts to provide a very high level overview of the Barracuda Component Model strategy by answering some of the basic questions. We start with the obvious...

  1. What is the purpose of this package?
  2. What is a Component?
  3. What is a View?
  4. What is an ElementFactory?
  5. What are ViewCapabilities?
  6. What is a ViewContext?
  7. What is a Model?
  8. What kinds of data can a Model return?
  9. How does all this stuff fit together?
  10. Can I see some UML?
  11. How does Barracuda compare with Swing?
  12. What components does Barracuda provide?
    1. How does the BText work?
    2. How does the BList work?
    3. How does the BTable work?
    4. How does the BTemplate work?
    5. How does the BAction work?
    6. How does the BLink work?
    7. How does the BSelect work?
    8. How does the BInput work?
    9. How does the BToggleButton work?
  13. How do components support multiple markup languages?
  14. Where should I look for the gory details?
  15. Can I create custom components?
  16. Can I create non-visual components?
  17. How about some working examples?

Q: What is the purpose of this package?

The purpose of the Component Model package is to make it easier to manipulate the DOM structures (generated by tools like XMLC) that back HTML, WML, and even XML pages.

We accomplish this by using components with strongly typed Model interfaces; rather than directly modifying data through the low-level DOM interfaces, you create a component, bind it to a portion of the DOM, and then render the component. The component is smart enough to figure out how to take data out of the model and insert it into the DOM. This makes it significantly easier to manipulate complex HTML structures (like lists and tables) programatically.

Q: What is a Component?

A component is really just an object that has three basic characteristics:

  1. First, a component must support the notion of a View. A view is simply the object a component should render itself in. In the case of the Barracuda component model, there can actually be more than one view per component.
  2. Second, it must implement the BContainer interface, which defines the methods necessary for a component to contain child components; after a component renders itself, it should also ask all of its children to render as well.
  3. Third, it must extend from the AbstractBComponent class, which defines a render() method (similar to paint() in GUI component models); when the render method is invoked the component should "render" its data in all of its views.

Some components may support additional characteristics as well. For instance, they may require the user to manipulate data through a Model interface (like in Swing). Or, the components may provide support for server side event handlers that are notified when an action occurs on the client (like traditional client-server apps).

This very simple arrangement makes it possible to assemble hierarchies of components that are bound to various points of a DOM page. Render the root component and all of the components will update their portions of the page as well.

Q: What is a View?

A View has a 1:1 relationship with a node in a DOM. The view is used to "bind" a component to a particular point in the DOM. When the component renders, it is responsible for mapping data from its model into the part of the DOM the view is bound to.

Most components just use a DefaultView. There are, however, several special kinds of views: a TableView is used to identify table structures; a TemplateView, is used to indicate that a portion of the DOM will processed by the BTemplate component.

Q: What is an ElementFactory?

Whenever a view is bound to a node, the view creates a structure called an ElementFactory. An element factory basically catalogues all the children beneath a given node, allowing them to be retrieved later (by the model) to be used as templates for new nodes.  The default implementation of ElementFactory catalogs elements based on their class name, interface name, and id attributes. As an example, consider a section of HTML that looks like this:

<ul>
    <li id="CreditRow">...</li>
    <li id="DebitRow">...</li>
</ul>

would allow you to retrieve template rows from the element factory like this:

Element creditRowTemplate = elementFactory.getElement("CreditRow");

Q: What are ViewCapabilities?

The ViewCapabilities object is used to describe the client view capabilities. It specifies the client type (what kind of browser), the format type (HTML, WML, etc), and the scripting type (None, Javascript, etc). This information is available for the component to consider when rendering. It also returns the target client Locale, which is very useful for the developer who wishes to load a localized DOM template.

Q: What is a ViewContext?

A ViewContext structure acts as a simple container for the ElementFactory and ViewCapability objects; it is passed into each component during the render process. The model implementation also receives a copy of this structure.

Q: What is a Model?

Now is a good time to discuss the role the Model plays. Every component has a model-- in the case of a BText, the model is simply the underlying text String; in the case of other components, the model is an actual interface (ie. ListModel, TableModel, TemplateModel).  That this is very comparable to Swing.

When the component needs to render itself in a View, it takes the data from the model and loads it into the DOM structure. List and table components will usually clear any prexistant data from the DOM structure; in these cases the model is really driving the structure and content of the list. In the case of the BTemplate component however, the DOM template itself drives the structure and content -- the component parses the template looking for keys and as they are found it queries the model to replace the data based on key name. This demonstrates the different methods components can use to actually update the DOM.

As in Swing, Model implementations will generally be implemented as inner classes.

Q: What kinds of data can a Model return?

In Swing, model implementations can return data of type Object as opposed to just String. The Swing component examines the returning data type; if it implements JComponent, then the new component will be added directly to the parent component. Otherwise, the data is converted to a String and displayed. This is very powerful because it allows the nesting of components: a JList can contain simple String data, or it can contain complex components.

This is actually very similar to HTML and other *ML languages, where a list or table structure can contain other complex blocks of markup.

Barracuda follows Swing's example here. In the Barracuda component model, models can generally return one of three types of data:

  1. DOM Nodes - if the returning data implements Node, it will be added directly into the parent structure. This is useful where you would like to return HTML fragments, like headers, footers, etc.
     
  2. BComponents - if the returning data type implements BComponent, it is automatically added in to the parent component as a temporary child (causing it to get rendered too) and then removed once the rendering proces is complete. This is very powerful because it allows models to return complex components. For instance, you might want to return a BLink component to represent a URL link. Or, you might want to return a BList (to create lists within lists!). There are many similar examples.
     
  3. Objects (Strings) - if the data returned does not implement Node or BComponent, it is converted to a String and added to the underlying DOM structure by delegating to a BText.

One specific component (BSelect) can also support models that return ItemMap objects. This interface is basically a convenience mechanism to pass back multiple data elements like a key and a value in one wrapper object--useful in rendering Select lists.

All of this is highly transparent to the developer, just like it is in Swing. All you have to know is that you can return these types of data and the component model will handle the rest.

Q: How does all this stuff fit together?

Let's see if we can construct a cohesive picture of how this all fits together. Generally the process of rendering a component hierarchy will go something like this:

  1. load DOM object
  2. construct component hierarchy, binding individual components to specific spots in the DOM via Views
  3. render the component hierarchy
  4. render the DOM object

Of course, we could apply caching patterns to steps 1 and 2 here to improve performance by reusing the component tree. The key point to note, however, is that unlike stated GUI components, here we really have a 2-phased render: first we render the component hierarchy (which updates the DOM), then we render the DOM (which actually generates the client view).

This distinction is important because it highlights a key difference between a server side component model and more traditional client-server components. In the traditional model, we can get away with a one-pass render because all the information needed for rendering (both layout and data) is kept in one place: a single component hierarchy.

Here in the web-app paradigm, however, the DOM "template" structure is really defining the layout and structure of the document. Now, we still need the component hierarchy in order to ensure that all components render, but the actual layout specifics are contained in the DOM template the components are bound to. Consequently, we need a 2 phase rendering process because the information needed to complete the render is stored in two distinct places: the component hierarchy (data) and the DOM template (layout).

The following diagram illustrates the concept:

comp_hierarchy1.gif (15811 bytes)

  1. We load the DOM template (via an XMLC object or some other DOM generating mechanism)
  2. We create our component hierarchy and bind it to the DOM template. The root component is not bound to any node; each of the child components is bound to a specific node in the DOM.
  3. We invoke render on the component hierarchy; the root component does not have any views, so it simply invokes render on all the child components.
  4. When the BText component renders, it takes the component's text value, finds the first Text child beneath in its view and sets the text value in it.
  5. When the first BList renders, it starts by removing all DOM items beneath the List1 node. Then it queries the model to determine how many items it contains, and adds them back in as new items to the list.
  6. The second BList does the same thing, updating the portion of the DOM for which it is responsible.
  7. Finally, we render our updated DOM template to generate the client view and return it in response to the original HTTP Request.

As you can see, the actual process is really very straightforward. It may be worthwhile, however, to investigate the actual mechanics of how components handle the data returned by the model. As we mentioned above, a model can return 3 distinct data types: Nodes, BComponents, or Strings.

In the case of List1 above, if the model returns a Node, that value is just added in directly as a child of the <ol> element.

If the item is a BComponent, however, we are dealing with a complex component that is already bound (or should be) to a node in a template. In this case the list component locates that node that backs the list item and adds it to the actual DOM list. Now, the list component adds the list item as a temporary child to itself. This last step is critical: when the list component finishes rendering, it will invoke the render method for all its children as well, including those BComponents which were returned from the model. This will ensure that each BComponent list item also renders itself into the node to which it was bound.

If the list model returns a String, the component actually wraps that value in a BText and adds that to the list (which results in the same processed as described above).

If these details sound complex, don't worry: as a developer all you really to know is that a model can return objects of type Node, BComponent, and String.  The components should take care of the rest.

Q: Can I see some UML?

Sure, here's the complete Component Model Class Diagram.

Q: How does Barracuda compare with Swing?

If you are familiar with Swing you will undoubtedly see a great deal of similarity. Both Swing and the Barracuda Component Model define strongly typed model interfaces that make it very easy for programmer to provide the component with data. In addition, Barracuda also keeps track of component validity; if you invoke render on the component hierarchy again, only components which have been invalidated will actually be re-rendered. This makes sense because as long as the data has not changed in the models, then there is no need for the component to update the DOM again. Again this is very similar to Swing.

There are some differences however. Most notably, Barracuda also defines View interfaces, whereas Swing does not. We primarily do this because some components (like BTable and BTemplate) require additional information in order to render themselves in the view, and we wish to ensure that the component is bound to a View that it will be capable of rendering in.

In addition, Barracuda defines far fewer components than does Swing. This is because the  template approach makes each component much more flexible. For instance, you can bind a BList component to just about any DOM node and it will do its best to render the data as a list structure suitable to the parent node. As an example, if you bind the list to a <ul> node, list data will be wrapped in <li> items. If you bind the same list to an <select> node, the list data will be wrapped in <option> elements. Furthermore, the components are smart enough to handle multiple output formats (HTML, WML, etc).

As a final note, it's important to observe that Barracuda does not attempt to match Swing components on a 1:1 basis. Sure, you'll see some that look familiar (BList, BTable, BText), but you'll see many that are modeled after the underlying markup structures (BAction, BLink,  BInput, BToggleButton, BSelect). And some are actually modeled after higher level presentation concepts (BTemplate).

Where there was natural commonality, we tried to keep things as close to Swing as possible. We didn't want to lose sight of the fact that we're dealing with the web-app paradigm, however, and so we also tried to simplify whenever possible.

Q: What components does Barracuda provide?

Barracuda currently provides the following components:

Q: How does the BText work?

Unlike most of the other components, BText does not have a specific model interface. Instead, the component's "model" is really just the internal variable that stores the text value. When the component renders, it retrieves that value, finds the first Text child beneath the Node to which it is bound, and sets the value therein. Its really quite simple.

We should note that this makes the BText component very flexible: you can bind it to just about any node and it will do it's best to set the text in the appropriate place. In all likelihood, developers will probably not actually use this class a lot, although it is used extensively by the other components.

Q: How does the BList work?

The list is actually fairly straightforward and easy to use. In order to use the list component, you must bind it to a node and then provide it with a model that is capable of returning the data for the actual list.

When the list component renders, it starts by removing all children beneath the node to which it is bound. Then it simply iterates through the list model, retrieving items from the model and adding them to the list based on the mechanics described above in How does all this stuff fit together? It should be noted that when adding items to the DOM template, the list does use some simple intelligence to try and ensure that the data you are adding is actually valid markup. For instance, the HTML DTD specifies that <ul> and <ol> tags can only contain <li> elements. If you try adding a <table> element to one of these parents, the component will automatically wrap it in an <li> element first.

Q: How does the BTable work?

The generic table component operates on the same principal as the list component: remove all data from the DOM element and then repopulate it based on the data in the model. There are a couple of noteworthy differences, however.

First, the table component supports both kinds of table structures defined in the HTML 4.0 spec (those with <thead>, <tbody>, and <tfoot> elements, as well as those that just contain <tr> elements). When you create a TableView, it preparses the template in an attempt to locate the header, body and footer elements. If it cannot find them, it just assumes that header, body, and footer elements will all be placed directly under the <table> node.

Second, the table component allows for three distinct table models: one for the header, one for the footer, and one for the body. These are invoked in turn and the data is added into the table accordingly. Of course, you don't have to implement either the header or the footer models if you don't wish to; this approach just adds flexibility and makes it easy to create complex, multilined headers and footers.

(NOTE: In practice, this component is not used all that much; it's usually much easier/better to use BTemplate instead)

Q: How does the BTemplate work?

The BTemplate component takes a different approach than the other Barracuda components. Instead of clearing the template and repopulating based on the contents of the model (which mirrors the Swing approach), the template component parses the template looking for tagged keys and then queries the model for data based on key name. This approach is similar to that taken by template engines,  although because BTemplate is a component, it can be freely intermixed with the other Barracuda components (so it's a lot more flexible than most template engines).

BTemplate is probably the easiest of all the components to use; let's take a closer look at how it works.

First of all, the easiest way to use the template component is to embed simple commands or "directives" inside the actual markup template. In HTML, directives can be specified in an elements' class attribute. In XML, directives can be embedded by using processing instructions. Now some developers want to avoid adding any kind of programming logic to *ML; directives can also be specified programatically via a "directive id map" which allows the template component to lookup directives based on ID. Using this latter approach directives could be stored anywhere (in code, properties file, XML, etc). The examples below will demonstrate directives stored in a properties file. [THIS NEEDS TO BE CHANGED TO THE SIMPLER METHOD]

The key here is that the directives provide a flag to instruct the component: "Hey, go query the model for the piece of data that goes here". The component interprets the directive to determine which model is capable of serving the request, and passes in the directive key. At this point, the model acts just like those for the other Barracuda components: the developer can pass back a Node, BComponent, or a String and the component will ensure it gets added into the template in the proper position.

Let's take a look at a simple template (ie. sample.html):

template_ex1.gif (2949 bytes)

The ids here match values in a corresponding directives file (ie. sample.directives) that might look something like this:

template_ex1_dir.gif (2817 bytes)

As the component parses this template, it finds directives based on id values in the template. It will then query the model based on directive keys. Notice that there are two models referenced here: ReportData and UserData.

The ReportData model might be implemented like this:

class ReportModel extends DefaultPropertiesModel {
    public ReportModel() {
        super("ReportModel");
    }
}

That was it? Wow! Yep. In this case, the model is extending DefaultPropertiesModel, which simply looks for the keyname in an underlying properties file. Pretty easy.

The UserData model is a little more complicated, but not much. It could be implemented like this:

class UserModel extends AbstractTemplateModel implements IterativeModel {

    //data structures
    UsersList users = UsersList.getData();
    UserData ud = null;
    Iterator it = null;

    public String getName() {return "UserData";}

    public Object getItem(String key) {
        if (key.equals("First")) return ud.get(UserData.FIRST);
        else if (key.equals("Last")) return ud.get(UserData.LAST);
        else if (key.equals("Descr")) return ud.get(UserData.DESCR);
        else if (key.equals("Age")) return ud.get(UserData.AGE);
        else if (key.equals("Gender")) return ud.get(UserData.GENDER);
    }

    public void preIterate() {
        it = users.iterator();
    }
   
    public boolean hasNext() {
        return (it!=null && it.hasNext());
    }
   
    public void loadNext() {
        ud = (UserData) it.next();
    }
   
    public void postIterate() {
        it = null;
    }
}

Note that the UserData model implements IterativeModel. This allows the template component to loop through the data set when it encounters the iterate directive. During this process, the model will faithfully return key values for the current record, and then report when it's out of data so the template component can continue.

The net result of all this is that its very easy to use: the designer tells the programmer "this is what data I need"; the developer responds by saying "here are the models and keys you can use to retrieve the data". At this point, the developer can focus on creating the models that provide the data and the designer can focus on making the page look good. Both can work independently of one another.

This clean separation makes it possible for the designer to drastically alter pages without requiring the developer to make further changes to the server. For instance, let's say the designer decides that the list format just doesn't look right, and instead the data should be presented in a table format. The template could be altered to look like this (sample2.html):

template_ex2.gif (3241 bytes)

If we are using XMLC to generate the DOM structures, all the designer has to do is recompile the XMLC pages and the new layout will take effect! The developer is only needed when the designer requires additional information that is not present in the current models.

For many developers, BTemplate will be the easiest way to leverage the power of Barracuda components. For those who would rather not use the directive approach, the more traditional components are still at your disposal.  You should also keep in mind that many times the template model will actually want to return instances of these more specific components, so even if you love templates, you'll still want to read about the rest...

Q: How does the BAction work?

In a nutshell, the BAction component allows you to manipulate client-side markup that is capable of server-side events. Specifically, a BAction component can be bound to <a>, <form> <input>, <button>, and <select> elements. You can use a BAction to specify what kind of "action", or "event" should be generated when the element is activated.

For instance, when you bind a BAction to an <a> element, you are saying "this is what I want the link to be...". BAction allows you to specify the action as a URL or as an Barracuda Event. In the latter case, when the link is pressed it would cause the specified event (which defaults to ActionEvent) to be generated (and handled) on the server. The same thing applies if you bind the component to a <button>; you are saying, "when this button is pressed, generate this particular event".

BAction also makes it easy to catch the events generated by client side components by providing a method called addEventListener(). This method allows you to specify an event handler factory (defined in the Barracuda Event Model) that should be notified when the event occurs.

In short, BAction makes it easy to control a) what events get generated by client-side markup and b) what server-side interests handle those events. In the future, we intend to add support for client-side event handlers (via Javascript) as well.

Q: How does the BLink work?

BLink provides a simple extension of BAction that allows you to specify the text that accompanies an action. The most obvious usage would be for working with anchor elements <a>, where the action corresponds to the href attribute, and the text corresponds to the text that the link contains. BLink can also be bound to <button> and <input> elements (although you'd usually use BInput for this); the text value will be used to render the text associated with those elements.

One of the really convenient features about BLink is that it doesn't actually have to be bound to a node. You can return it as data for a Template model and it will automatically create an anchor element if needed and bind the link to it. This makes it very easy to work with links in a web-application.

Q: How does the BSelect work?

BSelect provides an extension of BList that is specifically tailored to manipulate <select> elements. In a nutshell, the BSelect component will render the list data as <option> elements in the select list. As its name suggest, BSelect also supports the concept of "selection" via a ListSelectionModel very similar to that found in Swing--you can select one or more elements in the list simply by setting the appropriate range in the selection model.

When working with HTML select lists, many times you will want to set both key and value attributes for each option. Since the ListModel interface only allows you to return one item for a given list element, we created a special ItemMap interface that acts a simple wrapper around key/value pairs. The BSelect component is smart enough to recognize ItemMap objects and set the key/value attributes accordingly.

We should also note that BSelect provides a convenience mechanism to add event listeners directly to the component, just like with BAction. This all combines to make it much easier to manipulate select elements from within your Java code.

Q: How does the BInput work?

BInput is another specialized component, in this case aimed at making it easier to manipulate <input> elements. BInput allows you to specify the element type (TEXT, PASSWORD, SUBMIT, RESET, FILE, HIDDEN, IMAGE, BUTTON, RADIO and CHECKBOX) and set the value associated with that element. This is very useful for manipulating textfields, buttons, etc. As with BSelect, BInput also allows you to add event listeners directly to the component.

Q: How does the BToggleButton work?

BToggleButton extends BInput to provide additional functionality in dealing with Radio and Checkbox buttons. In a nutshell, it allows you to mark them as selected/unselected. Again, you can add event listeners if you so desire.

(A brief note here: you may be wondering "Why would I want to add an event listener to a Radio button?" Well, in most cases you wont: usually the button would be part of a form and you'd want to defer processing the button until the form is submitted. In a case like this, you'd never need to add an event listener to the specific Radio button component.

There may however, be occasions where you'd like to be notified immediately as soon as a button is pressed, without waiting for the user to press a separate submit button. In cases like these, adding an event listener makes perfect sense. When the component renders, it sees that it has event listeners and so will use Javascript to ensure that when the component value is clicked/changed, the server side gets notified.

Ultimately, the developer will need to decide whether or not its appropriate; the Barracuda components just aim to make it easy to do when you need to)

Q: How do components support multiple markup languages?

The Barracuda components use custom renderers to handle different kinds of markup. In a nutshell, every component generally has a static initializer that registers specific renderers for various classes of documents. For instance, the BInput component says "whenever you need to render an HTMLElement or HTMLDocument, use the HTMLRendererFactory". Other classes like BTemplate already have support for both HTML and XML documents. When the component renders, it looks at the type of node it is bound to (starting with the more specific interfaces, like org.w3c.dom.html) and looks for a renderer that can handle the component/document combination. If none is available, it will work up the parent interfaces until it finds a suitable match.

The net effect of all this is that if you create a custom component (ie. MyComp.java) you can also create a custom renderer and install it dynamically so that MyComp.java will be rendered by your renderer rather than the default renderer.

While not really that difficult to follow, the details are a little complex. Fortunately, the developer is generally insulated from these details (unless you decide to start writing custom components). The good thing is, if you need it, its there!

Q: Where should I look for the gory details?

If you really want to understand components in all their gory details, the place to start is actually in the org.barracudamvc.core.comp.renderer package. This package contains all of the markup specific renderers used by each components. Most of these renderers are very simple and straightforward. By studying these classes, you will be able to quickly understand exactly how components manipulate the markup.

After that, you should probably study the components themselves, especially BAbstractComponent and BComponent, as these classes are responsible for most of the general component behaviour.

Q: Can I create custom components?

Sure! All you have to do is extend BComponent (or one of the other components) and override the render() method. Or, if you prefer, you can override three methods which are called by the default implementation of render(ViewContext vc):

It should be noted, however, that in most circumstances you'll probably never need to extend the base components. As with Swing, you can often control look and feel of the data simply through your model implementation.

If you do decide to create a custom component, there are really only two key requirements: first, you component should handle multiple output formats as specified by the ViewContext structure; secondly, if your component uses a Model interface, it should handle the standard data types that models can return: Nodes, BComponents, and Strings. Following these guidelines will help you create well behaved components that are useful to the Barracuda community.

Q: Can I create non-visual components?

Yes again. All you would have to do is override the render() or renderView() methods as specified above. There is no requirement for a component to actually have any views, or to actually render anything into those views, so it would be entirely permissable to have components that do not actually manipulate the DOM structure.

It should also be noted that components are free to traverse the component hierarchy, so it would certainly be possible to create a component which interacts with other components above or below it in the hierarchy. Given the generic nature of the component hierarchy, there are really not many limitations to what components can do.

Q: How about some working examples?

A: Sure thing. If you're new to components, start by taking a look at the Component Model Tutorial. Once you're familiar with the basics, check out the BConfig Application for a good example of how it all fits together [NEED A BETTER EXAMPLE HERE].

 


$Date: 2007-02-03 14:24:13 +0100 (sam, 03 fév 2007) $