Barracuda Component Model Tutorial - HelloWorld 2

<<|Preface|HelloWorld 1|1a|1b|2|2a|2b|3|4|>>

barracuda.gif (11456 bytes) Hello World, I Need Some Groceries - this example shows how we can use the BTemplate to generate a list of data.

Taking a look at the Source

Ok, let's start by looking at the source. There really aren't too many differences between HelloWorld 2 and previous examples.

One of the first things we see is that in this example we are registering a second model with the template component. The code looks something like this:

String[] items = new String[] {"Cabbage", "Carrots", "Pickles",
                               "Sugar", "Wheaties", "Flour", "Potatoes"};
String[] qtys = new String[] {"3 heads", "1 bag", "1 jar",
                              "5 lbs", "3 bxs", "50 lbs", "25 lbs"};
templateComp.addModel(new GroceriesModel(items, qtys));

Here, we're basically passing in two arrays of data (items, qtys) which will serve as the underlying data model for our TemplateModel implementation.

Ok, so what does the TemplateModel look like? Well, if we look further down in the code we see it looks somethign like this:

class GroceriesModel extends AbstractTemplateModel implements IterativeModel {

    String[] items = null;
    String[] qtys = null;
    int cntr = -1;

    //constructor
    public GroceriesModel(String[] iitems, String[] iqtys) {
        items = iitems;
        qtys = iqtys;
    }

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

    //provide items by key
    public Object getItem(String key) {
        if (key.equals("Item")) return items[cntr];
        else if (key.equals("Qty")) return qtys[cntr];
        else return super.getItem(key);
    }

    //prepare for iteration
    public void preIterate() {
        cntr = -1;
    }

    //return true if there are more rows in the data model
    public boolean hasNext() {
        return (cntr<(items.length-1));
    }

    //load the next row in the model
    public void loadNext() {
        cntr++;
    }

    //cleanup after iteration
    public void postIterate() {}
}

While this may look like a lot more code, a quick glance reveals that it's actually still very straightforward. The most obvious difference is that here the TemplateModel also implements an interface called IterativeModel. This interface defines 4 key methods that allow the component to iterate through the model's data.

  1. preIterate() - this method is called when the component wishes to begin iterating over data. It gives the model a chance to prepare itself. Here we just reset our position counter to -1.

  2. hasNext() - this method allows the model to tell the component whether or not it has any more data. In this case, we can return true as long as the counter doesn't exceed the number of items in the underlying model.

  3. loadNext() - the component calls this method when it actually wants the model to iterate forward to the next record in the data set. In this case, all we do is increment our counter; in a more realistic example we might load the next record from an iterator or result set.

  4. postIterate() - this method is called when the iteration is complete (after the model returns false for the hasNext() method). It gives the model a chance to perform any cleanup. Here we don't need to do anything special.

By implementing these methods, the component can iterate through the data in the GroceriesModel. Every time it requests a specific key (here "Item" or "Qty") the model just returns the value from the current row of data.

Well now, that wasn't too bad, was it! So now you probably wonder what kind of directives we had to use in the template to make this happen. Good question...read on for the answer!


Taking a look at the Template

The template code is now a little more complicated than in previous examples, but it is still very simple when compared to other templating approaches (and that's the good news...this is as complicated as it gets! So once you understand this part you will have mastered all you need to know to effectively use the the BTemplate). Let's start by taking a look at the actual template here. Most of it is identical to what we've seen in previous examples. The part that we are interested in is this:

<p>Here's our simple shopping list:</p>
<ul>
    <li class="Dir::Iterate_Start.Groceries Dir::Iterate_Next.Groceries">
        <span class="Dir::Get_Data.Groceries.Qty">3 cans</span>&nbsp;
        <span class="Dir::Get_Data.Groceries.Item">Corn</span>
    </li>
    <li class="Dir::Iterate_End.Groceries Dir::Discard">4 bags Peas</li>
    <li class="Dir::Discard">1 bunch Bannanas</li>
    <li class="Dir::Discard">...</li>
</ul>

Here we see a couple of interesting things. First of all, notice that we are dealing with an HTML list structure. The designer has populated the template with a couple of rows to demonstrate how the grocery list would appear. We want to actually iterate through the model and replace the mockup data with the real data from our GroceriesModel.

The way we do this is by using an "iterate" directive. There are actually 3 separate directives, each of which is used to perform a different part of the iteration process.

  1. Dir::Iterate_Start.Groceries - this directive tells the component to find the Groceries model and prepare it for iteration. The model must implement IterativeModel for the component to be able to implement over it.

  2. Dir::Iterate_Next.Groceries - this directive tells the component to notify the model that it needs to locate the next row of data.

  3. Dir::Iterate_End.Groceries - this directive tells the component where the iteration block ends. When the iterator hits the Iterate_End block, it will loop back up to the Iterate_Start block and continue through the template until the model reports that it is out of data.

Once the model is out of data, the template component jumps to the Iterate_End block and continues processing the template from that point on.

Now, during the iteration, every node from Iterate_Start to Iterate_End gets repeatedly processed. As we work through the template, we encounter the familiar Get_Data directives which load Item and Qty values from the model (for whatever the current row is). The resulting HTML looks something like this:

<LI>
    <SPAN>3 heads</SPAN>&nbsp;
    <SPAN>Cabbage</SPAN>
</LI>
<LI>
    <SPAN>1 bag</SPAN>&nbsp;
    <SPAN>Carrots</SPAN>
</LI>
...

Cool! That seems to be doing exactly what we wanted.

Now, let's take a look at the directive that immediately follows Iterate_End. It's very simple: Dir::Iterate_Discard. This directive does just what it sounds like: it tells the template component that it may simply discard this particular node because it's not actually part of the template. Note that in the specific case here this directive never gets processed until after the iteration loop is complete. This is because as soon as the component hits the Iterate_End directive, it immediately loops back to the start block.

That about covers it! Pretty simple, huh?


Understanding Directives

So, let's take a quick look at the complete list of directives:

  • Dir::Get_Data.<model>.<key> - get data from the specified model based on key and place it into the document
  • Dir::Set_Attr.<model>.<key>.<attr> - similar to the previous command, except that this directive locates the data for the specified key, converts it to a String and places it in the named attribute setting. Useful when you need to set an attribute (like the alt attribute in an image tag).
  • Dir::Iterate_Start.<model> - start an iteration on the specified model
  • Dir::Iterate_Next.<model> - ask the model to load the next record (if it exists)
  • Dir::Iterate_End.<model> - marks the end of an iteration block
  • Dir::Discard - discards the current element so that it is not included in the final DOM output

Yep, that's it! Pretty straightforward.

Now, one of the questions that comes up a lot is "So what other directives are you going to add next?" The answer is "We don't think we will need to add any more". Here's why.

First of all, you'll notice we don't have any conditional directives. This is intentional; it's very easy for a directives based approach to blossom into a full blown scripting language. We want to avoid this because we think it's a bad idea to merge content with code. Now, some people may argue that that's what we've already done, but we think there's a difference. We are embedding "tags" in the markup, but really we are just following a naming convention that allows the component to interpret what the designer had in mind in terms of layout. In other words, we could simply use an id naming convention (which would be illegible to us but meaningful to the component); we've chosen the more legible route because it makes it easier for designers to add these "layout markers" to the markup if they desire. Of course, if you don't like anything in you *ML, you don't have to go this route.

At any rate, the point here is that with the current implementation of directives, we really are not adding "code" to the markup language. So then, what about conditional type operations? What if the designer wants a certain row to be used in one case and a different row to be used in another? To accomodate this scenario, we've allowed for custom directives. A custom directive follows the same directive format we're already familiar with:  (Dir::<command>.<model>.<key>.<attr>). All commands are passed into the TemplateModels processDirective() method, which allows the model developer to tell the designer "look, here's a custom directive you can use for this model".

As an example, the developer could create a Veto_If_Null directive. When the designer specifies this directive, the model could check to make sure that the value for the key specified is not null. If it is, the model returns false (meaning, "No, don't process this directive", which causes the model to discard the current node and continue processing. This type of behaviour can be used quite well to perform simple conditional tasks, like dropping out blank rows in a report. We have intentionally not added this type of things as a standard directive in order to keep the number of directives very small.

If for some reason the custom directive approach is too limiting, we'd recommend using an alternate component rather than trying to modify the directives. Remember, simple, simple, simple. Simple is a good thing! :-)

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...