Barracuda BConfig Tutorial
barracuda.gif (11456 bytes) BConfig - This application serves a twofold purpose. You can actually use it to configure a running Barracuda system (nice!), but it also provides a great example of how to combine components, events, forms, and localization all in one app (even better!).  This tutorial will try to explain all the major aspects of the application.

Special thanks to Richard Kunze (German), Jonas Thurfors (Swedish), Esteban Gonzalez (Spanish), Roger C. Soares (Portuguese) and Timo Sillanpää (Finnish) for providing the various translations!!!


Introduction

Let's start by talking briefly about the basic structure of the application. First, the purpose of the app is to allow the user to configure static variables within the Barracuda framework. Since there are more settings than will readily fit on one page, we decided to go with a tabbed look and feel. In general, an app like this would be developed by

  • both designer and developer working together to decide what data needs to be displayed in the application
  • designer decides how things are laid out by building shtml mockups, referencing the appropriate data elements
  • the developer creates models responsible for providing the necessary data elements when requested

It's a little more complex than that, but that should be enough to get us going.


Defining an Application Gateway

Since we want to use the Barracuda event model to handle flow control within the application, we need to start with an ApplicationGateway servlet. This particular class will handle all HTTP Requests ending in *.event and convert them to actual event objects so they can be delivered to the listeners that handle them.

In our case here, we do not need to do anything special other than map the event extension to the default ApplicationGateway. This is done in the Barracuda web.xml file, and looks something like this:

<servlet-mapping>
    <servlet-name>ApplicationGateway</servlet-name>
    <url-pattern>*.event</url-pattern>
</servlet-mapping>

key.gif (1065 bytes) The ApplicationGateway routes *.event URLs to the Barracuda Event model.


Defining our Event Gateways

Specifying an ApplicationGateway ensures that event requests get converted to event objects. In order to actually deliver those events, however, the ApplicationGateway needs to know who is listening for them.  To make this happen, every class that handles events needs to implement the EventGateway interface.

In our case, we have a number of event gateways, assembled hierarchically:

  • BarracudaConfig (this is the master event gateway)
    • CompConfig
    • DataConfig
    • DomConfig
    • EventConfig
    • FormsConfig
    • UtilConfig
    • ViewConfig
    • LanguageConfig

The primary purpose of the EventGateways is to allow event listener factories to register with the ApplicationGateway's event broker.

In this application, the root event gateway is registered with the application gateway by using the Event model's automated ApplicationAssembler (see ex4.xml). The specific line looks like this:

        <event-gateway class="org.enhydra.barracuda.config.BarracudaConfig" />

This adds our BConfig app into the ApplicationGateway's own event hierarchy. When the ApplicationGateway is initialized, BarracudaConfig will be instantiated and initialized as well (as will all the sub-event-gateways in the hierarchy). This gives all the specific event handler factories an opportunity to register with the EventBroker.

key.gif (1065 bytes) EventGateways receive events and contain other EventGateways.


Who Listens for What?

In this case there are only three events defined for the entire app:

  • GetBConfig - this control event means "hey, we want the BConfig page"
  • GetNextBConfig - this control event indicates we want to switch to a different tab
  • RenderBConfig - this view event tells the app that we actually want to render the appropriate view for the currently selected tab

BarracudaConfig is actually the only class that registers handlers for these specific events. All the other event gateways will use components that simply listen for generic ActionEvents (defined in the Barracuda Component model).

key.gif (1065 bytes) Only BarracudaConfig handles custom events;
the other event gateways handle generic ActionEvents.


Deciding on the Model API

Ok, first of all, the developer and designer must meet to decide what data needs to be made available. These elements can be thought of as the "Data Model API". They basically represent the data that the server knows how to provide. The designer is free to use as much or as little of this API as is necessary. In the case of the Dom screen, we only have 3 pieces of data:

  1. DefaultDOMLoader debug level (int, values = 0-5)
  2. DefaultDOMWriter debug level (int, values = 0-5)
  3. DefaultDOMWriter pretty printing (boolean)

Because we have a finite set of multiple values, an HTML select box element will be a good choice to represent the debug level fields. In the case of the pretty printing flag, a simple boolean value can be best represented by an HTML checkbox element.  Of course we will also need to support an Update function, along with error and success messages.

Given these needs, the developer gives the designer a list of directives that may be used to load the specified chunks of data into the web page:

  • Dir::Get_Data.Dom.DefaultDOMLoader_DebugLevel
  • Dir::Get_Data.Dom.DefaultDOMWriter_DebugLevel
  • Dir::Get_Data.Dom.DefaultDOMWriter_DefaultPrintPretty
  • Dir::Get_Data.Dom.ErrorMessage
  • Dir::Get_Data.Dom.SuccessMessage
  • Dir::Get_Data.Dom.UpdateButton

The designer could then embed these directives in the template markup.  Or (and this is how we actually implemented our example), the developer could simply provide a directives file, and the designer can reference the various directives by element id. The Dom related items in this file might look like this:

  • DefaultDOMLoader_DebugLevel = Dir::Get_Data.Dom.DefaultDOMLoader_DebugLevel
  • DefaultDOMWriter_DebugLevel = Dir::Get_Data.Dom.DefaultDOMWriter_DebugLevel
  • DefaultDOMWriter_DefaultPrintPretty = Dir::Get_Data.Dom.DefaultDOMWriter_DefaultPrintPretty
  • DomErrors = Dir::Get_Data.Dom.ErrorMessage
  • DomSuccess = Dir::Get_Data.Dom.SuccessMessage
  • DomUpdate = Dir::Get_Data.Dom.UpdateButton

See Config.directives for a complete list.

It is the programmers responsibilities to make sure the server "model" supports these elements.

key.gif (1065 bytes) A "Model" represents an informal contract between Developer and Designer.


Building the Initial Mockups

Before we look at the specific steps the developer must go through to actually implement the "Model API", let's look first at how the designer builds the templates.

In our example here, we're assuming the designer is primarily interested in design, not development. In other words, our designer knows he is really good at layout, and so that's what he wants to focus on. To facilitate this task, he will do most of his design work using a static mockup. Now, obviously, the app will not actually be deployed as static pages, so we really need a process here that supports both the static design phase and the dynamic deploy phase.

The solution is for the designer to use server side includes in conjunction with a local web server. Here's what it looks like in practice. Our designer creates a series of static mockups (look in the xmlc source directory for files with a .shtml extension). This extension tells the web-server to parse these files looking for include statements. Most web server's support server side includes.

In the case of the DOM screen, we're going to create a file called Mockup_Config_Dom.shtml that looks something like this:

<html>
    <head>
        <link HREF="style.css" REL="styleSheet" TYPE="text/css">
        <meta name="GENERATOR" content="Microsoft FrontPage 3.0">
        <title>Barracuda Core Configuration Screen</title>
    </head>
    <body>
        <p>&nbsp; </p>
       
<!--#include file="Dom.ssi"-->
    </body>
</html>

Now, as you can see, there's not much here. The file simply references a style sheet and then includes a file called Dom.ssi. This is the file that contains the bulk of the screen markup. By putting it into its own ssi file, we can easily use the DOM screen markup in both the mockup and the XMLC template too.

During the design process, the designer will view his mockups through a web browser (using a URL somthing like this: http://localhost/barracuda_config/Mockup_Config_Dom.shtml)

key.gif (1065 bytes) Designers will do most of their work using static mockups
and server-side includes.


Referencing Data From Within the Template

As we mentioned above, Dom.ssi is the file that contains the "guts" of the markup for the Dom screen. Let's take a look at some sample markup and see how the designer would reference a data element from within it:

<tr>
    <td>
        &nbsp;
        <img src="images/bullet.gif" alt="" border="0"
         width="9" height="9">&nbsp;
        <strong>DefaultDOMLoader</strong>
    </td>
    <td align="left">
        <select name="D1" size="1" id="
DefaultDOMLoader_DebugLevel">
           
<!--#include file="SelectOptions.ssi"-->
        </select>                                    
    </td>
</tr>

As you can see, we have an HTML table row that contains 2 data cells: one for the title ("DefaultDOMLoader") and the other for the actual <select> element. What we want to do is tell the page "Hey, when you render, we want the data for DebugLevel component placed here". We could do this by directly specifying the directive in the markup:

<select ... class="Dir::Get_Data.Dom.DefaultDOMLoader_DebugLevel">

or (and this way is more elegant), by simply assigning an id value to the element that corresponds to the appropriate directive in the Config.directives file:

<select ... id="DefaultDOMLoader_DebugLevel">

When the screen is rendered, the model will provide a component for this data element. This component will be responsible for rendering itself into the <select> element. In this example here, the designer also references som sample select items by referring to SelectOptions.ssi. This data will display in the mockup, but when the app is actually deployed, these lements will be replaced with real data from the component that gets bound to this <select> element.

The way we reference the the checkbox component is similar:

<td colspan="2">
    <img src="images/spacer.gif" alt="" border="0" width="18" height="9">
    <img src="images/bullet.gif" alt="" border="0" width="9" height="9">&nbsp;
    <input type="checkbox" name="C1" value="ON"
     id="
DefaultDOMWriter_DefaultPrintPretty">
    <span>Default Pretty Printing</span>
</td>

key.gif (1065 bytes) Within your templates, ids link elements to directives
that are contained in a separate file.


Building the XMLC Templates

At this point we've seen how we reference data from within the HTML mockup code. But how does our mockup get included into the actual XMLC template? In this example, there is actually only one template: Config.html. The actual source looks something like this:

<html>
    <head>
        <link HREF="style.css" REL="styleSheet" TYPE="text/css">
        <title>Barracuda Configuration Screen</title>
    </head>
    <body>
        <p>&nbsp;</p>
        <div id="ConfigTabs">
           
<!--#include file="Comp_Comp.ssi"-->
            <!--#include file="Comp_Model.ssi"-->
            <!--#include file="Comp_View.ssi"-->
            <!--#include file="Comp_Rend.ssi"-->
            <!--#include file="Data.ssi"-->
            <!--#include file="Dom.ssi"-->
            <!--#include file="Event.ssi"-->
            <!--#include file="Forms.ssi"-->
            <!--#include file="Util.ssi"-->
            <!--#include file="View.ssi"-->
            <!--#include file="About.ssi"-->

        </div>
    </body>
</html>

Hmm! Now this is interesting. It appears that we are including all of the screens into one big file. This assessment is correct! All the individual screens get compiled using XMLC into a single file called Config.class, which can be loaded and rendered by our BConfig application.

At this point you might well wonder, "How is it that the actual application only displays one screen at a time?" This is a very good question, and to understand it we must turn our attention back to the developer's role in the process and look at how she is handling the the notion of "tabs".

key.gif (1065 bytes) The master XMLC template is one file that includes all the tab screens.


Handling Tabs Within the App

To understand how tabs are handled, let's start by looking at a sample request. As we saw above, there are only 2 control events for the entire app:

  • GetBConfig
  • GetNextBConfig

Let's look at the second one first. When the second event is generated, it will generally be accompanied by a URL parameter that indicates which tab should be selected. The URL might look something like this:

BarracudaConfig/GetNextBConfig.event?CurrentTab=DomTab

This event gets handled by the GetNextConfigHandler in BarracudaConfig.java. The code looks something like this:

class GetNextConfigHandler extends DefaultBaseEventListener {
    public void handleControlEvent(ControlEventContext context)
        throws EventException, ServletException, IOException {
       
       
//get the curtab parameter
        String curtab = context.getRequest().getParameter(TabsModel.CUR_TAB);

       
//place it in the session (so it'll be available when we handle
        //the get event request)

        HttpSession session = SessionServices.getSession(context.getRequest());
        session.setAttribute(TabsModel.CUR_TAB, curtab);

       
//now redirect to the main get event
        throw new ClientSideRedirectException(new GetBConfig());
    }
}

As you can see, it simply takes the 'CurrentTab' parameter and places it in the users session. Then it redirects the client to fire a GetBConfig event.

Ok, that was pretty simple, so what does the GetConfigHandler do? Well, if we look take a look at the code (which is also in BarracudaConfig.java), we see that it too is pretty straightforward:

class GetConfigHandler extends DefaultBaseEventListener {
    public void handleControlEvent(ControlEventContext context)
        throws EventException, ServletException, IOException {
       
       
//get the appropriate screen for the desired context and locale
        Locale locale = context.getViewCapabilities().getClientLocale();
        MasterScreen screen = new MasterScreenFactory().                getInstance(BarracudaConfig.this, context, locale);

       
//make sure the proper tab is selected (get curtab from session)
        HttpSession session = SessionServices.getSession(context.getRequest());
        String curtab = (String) session.getAttribute(TabsModel.CUR_TAB);
        screen.tabsModel.setCurrentTab(curtab);

       
//place the screen in the context (so the renderer can access it)
        context.put(MASTER_SCREEN, screen);

       
//forward control to the renderer
        context.getQueue().addEvent(new RenderBConfig());
    }
}

We're really only doing a couple of things here:

  1. we get the target locale and load the MasterScreen
  2. we set the current tab based on the value in the session
  3. we place the MasterScreen into the context so that the renderer can access it
  4. we fire a RenderBConfig view event to cause the page to be rendered

That's not too complex, but there is obviously something going on behind the scenes here. Let's take a closer look at the MasterScreen class to see what's really happening.

key.gif (1065 bytes) When a specific tab is selected, we simply find the TabsModel
and set the current tab.


Understanding the MasterScreen Class

MasterScreen.java is one of the key pieces in the BConfig app. Basically, it works like this. We access the class by using MasterScreenFactory.java. This factory class looks in the user's Session to obtain a cached instance of the component hierarchy. If it's not there, it will be created. If it is there it will be returned.

It is important to note that this pattern would not scale very well except for the fact that the factory creates the MasterScreen object using a SoftReference, allowing it to be garbage collected if the system is running low on resources (ie. a high-load environment). This enables to maintain a component hierarchy for each individual user without worrying about the causing excessive overhead.

Let's look more closely at what happens when the MasterScreen is instantiated.

First, we save a copy of the target locale and then use it to load the target DOM template:

dom = DefaultDOMLoader.getGlobalInstance().getDOM(ConfigHTML.class, locale);

Next we create a root component and add a template component to it to process the tabs.

//create the root component
bcRoot = new BComponent();
//create the tab template
BTemplate btTabs = new BTemplate();
bcRoot.addChild(btTabs);
//...create the views (we're storing directives in separate file)
try {
    Properties props = new Properties();
    props.load(this.getClass().getResourceAsStream("Config.directives"));
    Node node = dom.getElementById("ConfigTabs");
    TemplateView tv =
        new DefaultTemplateView(node, "id", new MapStateMap(props));       
    btTabs.addView(tv);
} catch (IOException e) {
    System.out.println ("Fatal err loading properties file:"+e);
}

This essentially creates a very shallow component hierarchy that looks like this:

  • bcRoot (BComponent)
    • btTabs (BTemplate)

The tabs component has a single view associated with the "ConfigTabs" id in the div  element within Config.html. This means that when the component hierarchy is rendered, the btTabs template component will parse the entire template looking for directives that tell it what to do. Notice that our developer (sharp wiz that she is) has provided for the directives to be stored in a separate file called Config.directives.

Of course, a template component needs models in order to respond to requests for data. Sure enough, our developer has not forgotten this, and she adds those next. First, the tabs model:

//......Tabs model
tabsModel = new TabsModel(btTabs);
btTabs.addModel(tabsModel);

Next, she adds the individual models for each of the screens. The code that register the model for the Dom screen looks like this:

//......Dom screen model
DomConfig domConfig = (DomConfig) SimpleServiceFinder.findInstance(
    DomConfig.class, egRoot, SimpleServiceFinder.DOWNSTREAM);
if (domConfig!=null) {
    domModel = domConfig.getModel();
    btTabs.addModel(domModel);
}

It will be worth our while to look at each of these a little more closely.

key.gif (1065 bytes) The MasterScreen is responsible for assembling a component hierarchy for each specific user. This pattern scales because we use SoftReferences.


Understanding the Tabs Model

The tabs model does not actually supply data. Instead, it only handles custom directives. For instance, you will recall from up above that the Config.html template uses server-side includes. The resulting code looks something like this (with large chunks excised for clarity):

<html>
    <head>...</head>
    <body>
        <p>&nbsp;</p>
        <div id="ConfigTabs">
            <div id="CompTab_Comp">...</div>
            <div id="CompTab_Model">...</div>
            <div id="CompTab_View">...</div>
            <div id="CompTab_Rend">...</div>
            ...
            <div id="CompTab_Dom">...</div>
            ...
        </div>
    </body>
</html>

As the btTabs component processes the template, it encounters the id's specified in the <div> tags. These ids are matched to the directives in Config.directives. So for an id of CompTab_Comp, we get a directive called Dir::Is_Visible.Tabs.CompTab_Comp. As you might have guessed, these will be handled by the tabs model (defined in TabsModel.java). Let's take a look at some specifics...

The tabs model class defines an array of valid tab names:

public final static String COMP_TAB_COMP = "CompTab_Comp";
public final static String COMP_TAB_MODEL = "CompTab_Model";
public final static String COMP_TAB_VIEW = "CompTab_View";
public final static String COMP_TAB_REND = "CompTab_Renderer";
public final static String DATA_TAB = "DataTab";
public final static String DOM_TAB = "DomTab";
public final static String EVENT_TAB = "EventTab";
public final static String FORMS_TAB = "FormsTab";
public final static String UTIL_TAB = "UtilTab";
public final static String VIEW_TAB = "ViewTab";
public final static String ABOUT_TAB = "AboutTab";

It implements the getName() method of the TemplateModel interface. This will be used so the component can match the directive to the proper model by name.

//register the model by name
public String getName() {
    return "Tabs";
}

It also implements the processDirective() method of the TemplateModel interface.

//process directives
public boolean processDirective(TemplateDirective td) {
    if (td.getCommand().equals("Is_Visible")) {
        String tab = td.getKeyName();
        if (curTab==null) setCurrentTab(tab);
        if (!tab.equals(curTab)) return false;     //skip this tab
    }
    return true;    //by default, allow all directives
}

Basically what happens here is that for every "Is_Visible" directive the model returns false unless the tab name matches the currently selected tab. Returning false here effectively tells the template component to skip this entire portion of the template.

Oh, I get it! This means that if the Dom tab was selected, all the other <div> nodes what get skipped by the template component, and the resulting HTML would look something like this when it gets rendered:

<html>
    <head>...</head>
    <body>
        <p>&nbsp;</p>
        <div id="ConfigTabs">
            <div id="CompTab_Dom">...</div>
        </div>
    </body>
</html>

Exactly! All the other directives return false and only the selected tab (in this case for the Dom tab) gets returned and rendered. Hey, that's kind of cool! All we need now is a way to set which tab is selected. Yep, and the TabsModel provides that too with its setCurrentTab(String tabName) method. And what do you know, this was the method we called up above in the GetNextBConfig handler. Ah-hah! The pieces are starting to fit together...

key.gif (1065 bytes) The tabs model only processes directives for the tab that is visible;
this means only the "selected" tab will actually be rendered.


Locating the DomConfig Screen

Ok, now that we understand how the tabs model works, let's take another look at how we created the model for the DOM screen.

//......Dom screen model
DomConfig domConfig = (DomConfig) SimpleServiceFinder.findInstance(
    DomConfig.class, egRoot, SimpleServiceFinder.DOWNSTREAM);
if (domConfig!=null) {
    domModel = domConfig.getModel();
    btTabs.addModel(domModel);
}

The first thing you will notice is that we don't actually create the model directly. Instead, we use the SimpleServiceFinder utility to search the Event Gateway hierarchy for an instance of the DomConfig class.

We do this because we can't just instantiate the Dom Model directly; it is implemented as an inner class within DomConfig.java, which means we have to access it through a getModel() method that DomConfig provides.   Since the DomConfig class has already been created when we defined our event hierarchy, all we need to do is get a reference to it. The SimpleServiceFinder works great for this, searching down the hierarchy of Event Gateways until it finds a match. Once we have access to an instance of DomConfig, we can simply request a reference to the model and add it to the master template component.

key.gif (1065 bytes) To create a model for a specific screen, we must search for the config
class in the event gateway hierarchy.


Implementing the DomConfig Model

Once we have a reference to the DomConfig class, we can easily get the necessary template model. But how is this model actually implemented? Well, let's take a look. First and foremost, we can see that DomConfig.java defines a number of useful constants:

//model name
public static final String MODEL_NAME = "Dom";

//model elements (these are the data items supported)
public static final String DEFAULT_DOM_LOADER_DL =
    "DefaultDOMLoader_DebugLevel";
public static final String DEFAULT_DOM_WRITER_DL =
    "DefaultDOMWriter_DebugLevel";
public static final String DEFAULT_DOM_WRITER_DPP =
    "DefaultDOMWriter_DefaultPrintPretty";
public static final String ERROR_MESSAGE = "ErrorMessage";
public static final String SUCCESS_MESSAGE = "SuccessMessage";
public static final String UPDATE_BUTTON = "UpdateButton";

You'll probably notice that the MODEL_NAME corresponds to the Model Name portion of the directive, in this case "Dom". You'll also notice that the other constants correspond to the actual directive key names, like "DefaultDOMLoader_DebugLevel". So, when the template component encounters a directive like:

Dir::Get_Data.Dom.DefaultDOMLoader_DebugLevel

we're not surprised when the DomModel is called upon to process the request. The model itself is implemented as an inner class that looks something like this:

class DomModel extends AbstractTemplateModel {

   
//register the model by name
    public String getName() {return MODEL_NAME;}

   
//provide items by key
    public Object getItem(String key, ViewContext vc) {
        if (showDebug>0) Debug.println (this, 0, "Asking for key:"+key);   

       
//Handle requests for data
        if (key.equals(DEFAULT_DOM_LOADER_DL)) {
            return ScreenUtil.getDebugLevelComp(vc, key,
                DefaultDOMLoader.showDebug);
        } else if (key.equals(DEFAULT_DOM_WRITER_DL)) {
            return ScreenUtil.getDebugLevelComp(vc, key,
                DefaultDOMWriter.showDebug);
        } else if (key.equals(DEFAULT_DOM_WRITER_DPP)) {
            return ScreenUtil.getToggleButton(vc, DEFAULT_DOM_WRITER_DPP,
               "true", DefaultDOMWriter.defaultPrintPretty);
        } else if (key.equals(ERROR_MESSAGE)) {
            return ScreenUtil.getErrMsg(vc, FORM, ERROR_MESSAGE);
        } else if (key.equals(SUCCESS_MESSAGE)) {
            return ScreenUtil.getSuccessMsg(vc, FORM, SUCCESS_MESSAGE);
        } else if (key.equals(UPDATE_BUTTON)) {
            return ScreenUtil.getUpdateButtonComp(vc, updateConfigFactory);
        } else return super.getItem(key, vc);
    }
}

When the template encounters the "DefaultDOMLoader_DebugLevel" directive, it asks the model to provide the necessary data. Recall that a Barracuda model can return a String, a Node, or another BComponent. This last option is what we're doing here.

Basically, we use the ScreenUtil.java utility class to generate the proper component. In this case, we call the getDebugLevelComp() method. The actual function basically:

  1. clones the target node and creates a view on it
  2. creates a list model with the various debug level settings
  3. creates a BSelect component using the view/list model
  4. sets the name of the component to DEFAULT_DOM_LOADER_DL

This component gets returned to the template model, which then adds it into the master component hierarchy as a temporary child. When the component hierarchy is rendered the HTML will look something like this:

<select name="DefaultDOMLoader_DebugLevel" size="1">
    <option selected="selected" value="0">Debug Off</option>
    <option value="1">Debug = 1</option>
    <option value="2">Debug = 2</option>
    <option value="3">Debug = 3</option>
    <option value="4">Debug = 4</option>
    <option value="5">Debug = 5</option>
</select>

key.gif (1065 bytes) In response to template directives, the Dom Model returns
Barracuda components to render specific portions of the DOM.


Handling DomConfig Updates

Whew! Still with us? Great, we're almost done now! The next question is naturally, what happens when the user presses the Update button on the client? Well, we could have created an event (ie. UpdateBConfig.event), registered listeners for this event, and then just statically embedded the command into the form url for the page. We wanted an easier way though, and Barracuda provides it.

If we look at the DomConfig model again, we see that it also handles the Dir::Get_Data.Dom.UpdateButton directive, in this case by providing a BAction component created through the ScreenUtil ScreenUtil.getUpdateButtonComp(vc, updateConfigFactory); method. This function creates a BAction component in much the same way that we did earlier with the BSelect. However, it goes one step further and adds a private event listener called UpdateConfigFactory that is also defined in DomConfig.java. When the BAction component renders, it sees that it has a specific listener associated with the component, and so it renders the HTML to fire a generic ActionEvent that will be routed and delivered just to this specific listener when the button is pressed. In other words, our sever side code will be invoked when the a client-side action occurs. Notice that the entire process is transparent to the developer; she just adds an event listener to the component, just like you would in Swing.

The actual UpdateConfigFactory handler is also quite straightforward:

//create the login form
ValidationException ve = null;
FormMap fm = new DomForm();
try {
   
//map/validate the form
    fm.map(context.getRequest()).validate();
    ...
} catch (ValidationException e) {
    ve = e;
}

Here we create a FormMap to map the HttpRequest values to a Barracuda FormMap object. The FormMap object is also defined as an inner class, and looks something like this:

class DomForm extends DefaultFormMap {
    public DomForm() {
        //define the elements (note: these don't need any validators).
        this.defineElement(new DefaultFormElement(DEFAULT_DOM_LOADER_DL,
             FormType.INTEGER, new Integer(DefaultDOMLoader.showDebug), null,
             false));
        this.defineElement(new DefaultFormElement(DEFAULT_DOM_WRITER_DL,
             FormType.INTEGER, new Integer(DefaultDOMWriter.showDebug), null,
             false));
        this.defineElement(new DefaultFormElement(DEFAULT_DOM_WRITER_DPP,
             FormType.BOOLEAN, new Boolean(false), null, false));   
    }
}

Once we have defined our form elements, we can validate the form (in this case there are no specific validators, so it will always validate correctly), and then if there were no errors, we update the data on the server and then indicate our success.

    //If there were no errors, update the data.
    DefaultDOMLoader.showDebug =
        fm.getIntegerVal(DEFAULT_DOM_LOADER_DL).intValue();
    DefaultDOMWriter.showDebug =
        fm.getIntegerVal(DEFAULT_DOM_WRITER_DL).intValue();
    DefaultDOMWriter.defaultPrintPretty =
        fm.getBooleanVal(DEFAULT_DOM_WRITER_DPP).booleanValue();

    //remember our success
    fm.put(SUCCESS_MESSAGE, new Boolean(true));

If there was an error, we will note that in similar fashion (this information will be used by the model when the template processes the success/error message directives):

//store the error message in the form and then put the form in the
//the event context
fm.put(ERROR_MESSAGE, ve);
context.put(FORM, fm);

Now all we need to do is tell the components that the data in the model changed (again, very much like Swing):

//fire an update so that the screen will redraw
((DomModel) screen.domModel).fireTemplateChanged(new TemplateModelEvent(this));

Finally, we redirect the client back to the main config screen:

//redirect to the get screen again
throw new ClientSideRedirectException(new GetBConfig());

Here the currently selected tab will be rendered, and the updated data will be displayed since the model has invalidated the component that uses it. The DOM is written and the page is redisplayed to the user, where the process can start all over again.

key.gif (1065 bytes) To handle client-side updates, we simply add BAction listeners to <input> elements;
when the action occurs, the server is notified so validation/update processing can occur.


Final Comments

While this tutorial is certainly not comprehensive, it does provide an awful lot of detail which may at first seem complex and overwhelming. Rest assured, however, that most of the actual development process is amazingly easy once you have the basic insfrastructure set up.

By using the Barracuda template approach, the designer will spend most of his time doing just one thing: designing the pages and making sure they look just the way they should. Getting data into those pages is just a matter of associating HTML elements with their proper directives.

The developer also spends most of her time doing just one thing: implementing models. The process is surprisingly straightforward--write an inner class to handle model requests and return text, Nodes, or components. And then writing an inner class to handle updates--usually by using a Barracuda FormMap to parse the request into object data, which can then be validated and from there used to update the underlying data model. Gone are the days of mucking with the DOM.

Once you understand how Barracuda works and have defined the basic structure and flow for your app, it really is a very easy to develop with. In addition, the flexibility provided by the template approach is really quite significant: the designer is free to use as much or as little of the model api as is necessary, and the screens can be pretty dramatically rearranged without any developer intervention.

So, while things may seem complex at first glance, it's probably more an issue of learning curve than anything else. Most folks who take the time to learn the Barracuda approach have found it very useful, a great new way to approach web apps!

key.gif (1065 bytes) Barracuda provides a new level of flexibility for building webapps:
Designers can drastically redesign pages without changes to the server code;
Developers focus on creating Models and don't have to deal with the DOM

Click here to return to the Barracuda Component Model Tutorial page.

For all the latest information on Barracuda, please refer to http://barracudamvc.org
Questions, comments, feedback? Let us know...