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.
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!
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>
<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.
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>
<SPAN>Cabbage</SPAN>
</LI>
<LI>
<SPAN>1 bag</SPAN>
<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?
So, let's take a quick look at the complete list of directives:
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! :-)