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>
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.
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).
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:
- DefaultDOMLoader debug level (int, values = 0-5)
- DefaultDOMWriter debug level (int, values = 0-5)
- 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.
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> </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)
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>
<img src="images/bullet.gif"
alt="" border="0"
width="9"
height="9">
<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">
<input type="checkbox" name="C1"
value="ON"
id="DefaultDOMWriter_DefaultPrintPretty">
<span>Default Pretty Printing</span>
</td>
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> </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".
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:
- we get the target locale and load the MasterScreen
- we set the current tab based on the value in the session
- we place the MasterScreen into the context so that the renderer can access it
- 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.
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:
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.
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> </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> </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...
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.
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:
- clones the target node and creates a view on it
- creates a list model with the various debug level settings
- creates a BSelect component using the view/list model
- 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>
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.
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!
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
|