Copyright © 2008
New Technology / enterprise Ltd.
Objective
To understand how the use of helper objects, not considered part of the object tree, can be constructed and used during generation, particularly the use of JeeWiz controls to handle large numbers of smaller variations in the build while keeping templates and patterns readable.
Helper Objects
Helper objects can be generated for use at run time or at generate time, and in this section we'll be looking at helper objects used at generate time. These objects can be used during the pattern phases or the template build phase.
We looked at several ways to access and create helper methods throughout this tutorial, and this is a major feature of generation in JeeWiz. If everything is piled into the meta-model class or the pattern/template script, things soon start to get out of hand and both become unmanageable.
We've looked at globally available helper methods, accessible from any object via inheritance form the BaseBuildComponent. The list of hundreds of methods available this way can be extended with user defined helper methods by placing them in an ExtraHelperMethods class, somewhere in the path.
We've also used component methods available on tree objects of a particular type and to objects higher up in the template hierarchy. We've also seen session methods available to all objects, provided the stack level where the method is defined is included in the build. There are similar structures in place for Velocity macros, and although these are mainly deprecated in favour of methods, they still work and you will see them used in some of the standard stacks pending their eventual rewrite.
But helper methods and macros can only be used in a static manner, that is they don't hold state. That is not necessarily the case with helper objects which can hold state. We've seen properties that can change value during the generation being held locally in the currently running script, #set ($myVar = $myVar + 30), or on tree objects #set ($this.myVar = "green"). We'll now see how to create generate time helper objects to do the same sort of thing, thereby simplifying what needs to go on a tree object or into a script.
One example of the use of an external object is getNewClassInstance. This creates a java object of the required class and makes it available as a JeeWiz variable. We can then access any of the java methods available to the class. We also saw some helper methods giving us short forms for getting hold of commonly used java data structure classes such as, $this.newTreeMap and $this.newArrayList.
The biggest difference in tree objects and external objects is that tree objects are walked in the pattern and template build phases, while external objects aren't. It's possible to create an external object as a helper object and attach it to a tree object, but in doing so it doesn't become a child in the tree, even if it extends BaseBuildComponent. So setting $this.myList = $this.newArrayList creates an ArrayList object and assigns it to a property called myList on the current tree object. This ArrayList object is accessible throughout subsequent phases of the generation.
If we wanted specialist helper objects, it would be possible to define java objects, compile them and make them available to the java classpath at compile time. We could then use $this.getNewClassInstance("MyHelperClass") to access them. JeeWiz provides an easier way in the form of controls.
JeeWiz Controls
Often helper files are used to render snippets of code, script or markup, and to write java code to render more code can be confusing. We end up with the same problem we had when writing servlets to handle HTML output, and the solution is the same. Controls use JeeWiz methods to handle code output, which as we've already seen can output directly without first creating a java String which would subsequently be sent to an output stream.
So a JeeWiz control uses the same velocity-style scripting language used elsewhere in JeeWiz to create methods and properties directly on the generate time helper object.
Controls are defined in .control files placed in the template directories of the stack levels, and consist primarily of a list of properties and methods declared in much the same way as component or session properties and methods. Controls become generate-time java objects, instantiated when needed, and unlike meta-model classes do not need to be pre-built or pre-compiled. The start of the filename (before the .control part) forms the name of the control.
There are similarities and differences between meta-model objects used to create the tree, and controls used as helper objects. Rather than having different classes extending BaseBuildComponent, controls all use a Jwcontrol class directly, which itself is based on a HashMap. The variability between control types isn't in the class, it's in the methods and properties attached to the class. Because Jwcontrol doesn't extend BaseBuildComponent the global helper methods can't be called directly on a control, but need to be called on a meta-model object such as $this.
Control types are pseudo-classes and can "inherit" from each other in just the same way as component methods and properties, following a control hierarchy akin to the template hierarchy. Methods on a control are aggregated, or flattened, in the same way as component methods, working down the stack levels, but instead of going from one template subdirectory to another following a template goto directive, controls can specify an extends directive which points to the next control in the hierarchy.
Take the case of a uiString control on which we define how to display a string attribute on the screen. This will have something in common with how we display a number on the screen, and some differences too. We can put the common code in the uiBase control, and have uiString extending it. We might want to broaden out the idea, so strings could be displayed differently for drop-downs or radio buttons, where we have a limited choice, textboxes, where we expect a small string, and a textarea, multi-line input box. All these could be defined as different control types extending uiString.
If there is more than one control of the same name in the stack, the methods are aggregated down the stack, always picking the first one in the most specific level that fulfils the method signature. Properties likewise, but rather than just pick the first extends directive, there is a constraint that says only one extends directive can be defined for a particular control type in the stack. Ideally the extends should be put at the lowest relevant layer of the stack, but it doesn't have to.
So we might just have one uiTextArea.control file at the jsf level with an extends pointing to uiString. Even so the engine will check the whole stack for control files of the same name to aggregate. We may have two uiString.control files, one at jsf level and one at screen level, with the latter extending uiBase. And there may be one uiBase.control file at the jboss level with no extension. In this case when searching for a method on a uiText control, the engine will look in the uiText first, if it can't find it, it will look in the aggregated uiString, and if it's not there it will check in the uiBase.
Defining a JeeWiz Control
A .control file can contain any of property definition lines with the same syntax as in component.properties files, #methods, the same as in component.methods files, abstract methods and up to one #extends directive (conventionally, placed before any property or method definitions). Any other lines are treated as comment (using the standard ## syntax for comments avoids confusion).
#methods work as with component methods. These can either output directly into patterns and templates or can return values that can be used explicitly.
Abstract methods must be overridden or the whole control is deemed abstract and can't be instantiated. The syntax is similar to #method definitions
#abstractMethod( methodName $param1 $param2)
Here's an example of a control -
##
## uiTextBox. Renders an attribute value held in a bean as a textbox
## Supports hidden, readonly, disabled and editable text.
##
#extends ( UiBase )
width=32
#method( renderComponentOnPage $attr $beanName )
#if ($self.hidden)
<td>
<input type='hidden' name='${attr.name}'
#elseif ($self.disabled)
<td class="data disabled">
<input type='text' name='${attr.name}'
size='${self.width}'
disabled='disabled'
#elseif ($self.readonly)
<td class="data readonly">
<input type='text' name='${attr.name}'
size='${self.width}'
readonly='readonly'
#else
<td class="data">
<input type='text' name='${attr.name}'
size='${self.width}'
#end
value='$self.valueToRender(${attr.nameCapitalised})'
/>
</td>
#end
#method( valueToRender $name $beanName )
#return ( "<%=(${beanName}.get${name}()==null)?"":${beanName}.get${name}()%>" )
#end
#method ( clear )
#set ($self.width=32)
$super.clear()
#end
The first non-comment line is an extends directive to tell JeeWiz to look for values and methods in UiBase if they can't be found here. We might expect to find default values for hidden, disabled and readonly in the uiBase control.
Next note the use of $self to refer to the control object. $this still refers to the current meta-model object in the tree. If we could be sure the control would only be used from templates and patterns in attribute, we might decide to use $this rather than pass in $attr, but screen level templates would not be able to use the method. $self can be used to access properties such as hidden, or other methods in the control such as valueToRender.
We pass in the beanName rather than the bean itself as we can't actually access bean values at generate time. The control is writing the jsp code, HTML and scriptlet, that will be run later, when the bean is available.
In order to use the control we can set properties such as readonly to make the field display readonly, or width (which might be better written as a style but serves as an example).
The final method is clear, which resets the properties to their default. The only property initialised at this level is width, other properties have to be cleared at the base level, so we call $super.clear.
Instantiating and Using a JeeWiz Control
The easiest way to get a new control is to use the helper method getNewControl. This takes the name of the control as a parameter, so
#set ($ctl = $this.getNewControl("uiTextBox"))
will get a new uiTextBox control and assign it to the $ctl variable.
We can now set any properties on it we want.
#set ($ctl.hidden=$booleanTrue) $ctl.renderComponentOnPage($attr, $bean.name)
Note the commas separating the parameters when calling the method, unlike when defining it.
It's also possible to get a control from a control of the same type so
#set ( $ctl2 = $ctl.new() ) #set ( $ctl2 = $ctl.clone() )
The first gets a new copy, the second also sets the property and List values to be the same as the original (a shallow clone).
One Control Instance or Many
Attributes are an obvious candidate for controls. It's easy enough to see that if we only want to deal with half a dozen logical types of data, for example, Numbers, Booleans, Text, Date, Image, File and each had only two or three different ways of displaying, each perhaps needing to support editing readonly and hidden, that mounts up to a huge number of #if statements if placed in the template directly.
Better is to write methods in the attributes component.method and store the state on the attribute meta-model object itself, but this would clutter up the component.methods file with a large number of methods covering discrete areas of processing, when we can take them out and place them in controls, which also gives us the ability to take advantage of inheritance without creating attribute sub-object directories. So we go for controls. But there are several ways to implement controls. Which do we choose?
Let's break up the requirements into three areas and have a set of controls for each: ui controls, which deal with the front end presentation of the attribute; data type controls, which deal with the native format of the type held on the bean (or more generally in the application code); and database controls that deal with the persistence aspects of the attribute, conversion to and from the database column's format.
We could choose to create the control objects in the pattern stage and attach them to the attributes. We can set up the type of control needed in a component.method and then use code similar to
#set ($uic = $this.getNewControl("$this.myUiControlName()"))
#set ($dtc = $this.getNewControl("$this.myDataTypeControlName()"))
#set ($dbc = $this.getNewControl("$this.myDatabaseControlName()"))
Then each attribute is going to be using at least three controls. In a large application there may be hundreds of attributes, so if we instantiated three new controls per attribute that way we'd need hundreds of control instantiations. Worse, at different places in the application we might want to display the same data item different ways so a Date might appear as a calendar on one page and readonly text on another. So there might be even more than three per attribute.
If we don't need to hold the state of the controls between uses, we can use the same control object on different attributes, setting up different properties of the control at each use in the same way as set hidden property we saw earlier. By creating a global instantiation of the control, we can attach that same object to many different attributes. JeeWiz doesn't let you set project level global objects, but we can attach it to the top level object in the tree, site.
So let's say we create a single uiTextBox control, and put it in a HashMap that we attach to the top level of the tree. Then by accessing the HashMap we can attach the object to all the attributes that need a uiTextBox control. We could do this when needed, that is create a uiTextBox in the HashMap if it doesn't exist when we want to attach it to the attribute, or we can initialise the HashMap with all the controls that can be reused this way.
The beginning of the pattern prephase starts with the preIncludeSpec being run on the top level object as the first step in the top-down tree walk, and so we could do the creation of the HashMap and any initialisation there. But initialisation can be useful for several techniques, not just controls, and there's a mechanism built in to JeeWiz for that.
Prior to the pattern phases, JeeWiz looks for script files in the template directories called start.vm. It runs these scripts working up the stack, from the most general to the most specific. That means that more specific code can override the effects of the general code. The context object of start.vm is the top level object, so creating a HashMap using #set ($this.masterControls = $newHashMap) will attach the HashMap to the site. You can then instantiate a control and add it to the HashMap using another short-form syntax for HashMaps
#set ($masterControls.uiTextBox = $this.getNewControl("uiTextBox"))
Because properties can be found anywhere up the object tree hierarchy, you can refer to $masterControls anywhere and can access objects in the HashMap. So if you don't need to keep the state of the control between uses, you can assign
#set ($attr.uic = $masterControls.uiTextBox"))
If you do need to keep the state you can use
#set ($attr.uic = $this.getNewControl("uiTextBox"))
Developing The Controls Example
Controls are used for several areas in the standard stack including navigation, logging and utilities, but by far the largest use is in handling attribute variations. So as an example, I'll develop a small set of controls to give you a feel for how it's done. The controls in the standard stack are more complicated and are used on different architectural elements, but they use similar techniques.
The first step is to decide what parts of our application will vary with different attributes. We can follow through the main things we do with attributes at the moment and what we might like to do. Breaking it down into two main processes, we have the storage of information in the database, and the search for information. Deletion won't result in variation different from searching (the key type to specify a row for deletion might vary). The syntax for updating rows and creating rows should also use the same variations.
There's also the default SQL schema generation, which will depend on the attribute type.
So looking at storing information:
- We need to display something on the screen capable of accepting values, and we might want that something to vary in form.
- We want to take the information from the screen and store it locally in a pre-determined form. We might also want to validate that the information can be converted to the form we are expecting, a date, a number and so on, otherwise we could keep storing everything as a String.
- We want to take the information in the internally held form and turn it into a form acceptable for use in SQL insert (and update) statements. This will vary depending both on the internal type held and on the SQL column type. (The table might already exist, so we can't dictate that we will always store a particular internal type as the default column type.)
Now looking at retrieving information:
- We want to be able to display something on the screen capable of accepting search criteria.
- We want to take the information on the screen and hold it in an internal format (not necessarily the same format as for update). This might also depend on internal type.
- We need to convert this internal form to SQL where clauses, depending on the column type.
Finally the schema creation
- We use default column types depending on the internal type. It would also depend on the database, if we supported anything other than MySQL and size constraints, if we were supporting those.
So if we decide to stick with UI controls, internal type controls and database type controls what's the best way to split the seven bits up?
Displaying on the screen (1 and 4) will go into a ui control.
Converting the information from the screen (2 and 5) will need a combination of the ui control and the data type control. Converting internal format to SQL format (3 and 6) will require a combination of the data-type control and the database control. We could just do schema creation (7) as a default using the data-type control, or we could allow some variety in the specification and use the database control too.
Perhaps the easiest way to do combinations is to have all the major method calling done on ui and database controls and have them call the data-type control for internal variation.
Next we need to decide what types we intend to support. For the example, I'm going to use a very limited set. The model should specify a logical data type (text, integer, float, boolean, date) and possibly a maximum size or length, and we can then select the right data type and database controls.
data type: dtString, dtByte, dtInteger, dtFloat, dtDouble, dtBoolean, dtDate.
ui: uiTextbox - supports all types.
uiTextarea - only supports text
uiDmy - a triple dropdown, only supporting dates
uiRadio - supports a single specified choice of text items
uiCheckboxBoolean - supports Boolean (required).
database: dbText - supports all data types as varchar
dbByte - supports dtByte
dbInteger - supports dtByte and dtInteger
dbLong - supports dtLong, dtByte and dtInteger
dbBoolean - supports dtBoolean as boolean (tinyint/bit)
dbFloat - supports dtInteger and dtFloat
dbDouble - supports dtInteger, dtDouble and dtFloat
dbDate - supports dtDate
In the real world we'd want to support images, files, times, datetimes, etc, and a whole host of ui widgets, too.
We haven't sorted out the methods explicitly, but they become obvious by going through the seven process parts we need to handle and actually writing them.
With the five tier stack I asked you to split down in the last set of exercises, there's no need to have multiple-level aggregations, but there will be some commonality we abstract out and extend, for example we could put label display in a uiBase, and it might be worth putting some abstract methods in the base controls too. Finally if all methods are implemented or declared abstract at the base level, it's also a good place to document them. (Check out uiBase, dtBase and dbBase.) We'll put ui controls at the jsp level, the data-type controls at the services level and the database controls down at the mysql level. If we had a language level for java, the data type controls would go there, as for example that's where we decide that the logical type text is handled internally as a String.
We'll can establish the data type and database controls for each attribute in the preIncludeSpec, as these won't change. We'll pick up the relevant uiControl for input or searching as necessary.
Now we know what we want to do, the easiest way to get the hang is to just look at the example. Remember the .control files need to go in the template directory.
Standard Stack Controls
The controls in the standard stack are used to handle more complicated cases than are implemented in the tutorial demonstration, but use the same techniques. Some carry state and have to be instantiated separately for each attribute that needs them, others are taken from a master control HashMap, set up using start.vm, as in the tutorial example. Instead of using $masterControls, the base HashMap is called $C and to allow access in multiple ways it has other HashMaps loaded on it, for example all database controls are placed in the DB HashMap, so the syntax for access might be
#set ($dbc = $C.DB.get("mySql"))
Examples of using the different types of controls are shown in the jeewiz/tests directory, and documentation on how to access and create new jsf/screen controls are found in the jeewiz/howto/jsf directory.
Summary
- Jeewiz controls are special helper objects that can be attached to the tree meta-model objects and can have generate time properties set on them and methods run.
- They are created in files, the first part of the filename being the name of the control, the suffix being ".control". The files are placed in the template directories of the various levels.
- Controls can contain properties, methods and abstract methods. They can also extend other controls.
- Methods and properties override up the stack and inheritance is using the extends mechanism as a control hierarchy in a similar way to component methods following the template hierarchy.
- Controls are best used to take complexity out of the standard patterns and templates when there are a considerable number of choices to be made in a discrete area.
- Controls can be quite complicated to set up, so if you only want to handle a very small number of choices in your transform, using system methods or component methods might be a better option.
Exercises
The tutorial example will be found in %jwhome%\examples\tutorial\06webapp. The results of the tutorial will be found in %jwhome%\examples\tutorial\04webapp. If you have an older release of JeeWiz without the tutorial, you may have to download it separately from here.
- Look through the tutorial and compare the level split with how you did it in the last set of exercises. There are no right answers, as some of the levels are almost independent and can come in different orders.
- Check out the start.vm calls in the example, remembering they are run from the most general layer up to the most specific. Check out the controls and see if you can figure what is likely to be called from where. Where are the controls attached to the attribute meta-model objects?
- Build your own ui control. By default, text-based choices in the standard stack are shown as a dropdown (HTML select). Create a uiDropdown control, that like uiRadio uses choices, and make it the default instead of uiRadio.
- I've made the search controls always use uiTextbox and always return a java String value to the search bean. If the first character in the search string is "=", ">" or "<", change the way the search method constructs the SQL query for all types apart from Boolean. If an equals is specified, text should be found exactly rather using LIKE.
- This is difficult, but if you can do this one you've mastered controls! I've deliberately left out the handling of dates. Create the dbDate, dtDate, and uiDmy controls. For uiDmy you'll have to create three dropdowns on the screen and take all three values to set the internal java Date. Similarly you'll have to select the relevant parts of the date in the dropdowns when the date is set in the data bean.
