In the previous chapters the various elements that can appear in a builder script - bean declarations, form elements, actions, windows, etc. - have been discussed. There have also been plenty examples demonstrating the usage of the tags provided by the JGUIraffe tag libraries. However, these have only been short code fragments focusing on the element(s) just under discussion. What is missing so far is a full example of a builder script. Such an example is the subject of this chapter.
Having a full example of a builder script is indeed useful because there are certain requirements regarding the order in which elements are declared. The script is processed in a single pass from its beginning to its end. So as a rule of thumb, elements must be declared before they can be referenced by other elements. There are some exceptions from this rule, however. Bean declarations for instance do not create the corresponding objects directly. The creation happens on first access through a BeanContext, and then all dependencies are resolved automatically. As long as there are no cyclic dependencies between beans (refer to the section Cyclic dependencies), dependent beans can be placed in any order.
In the following sub sections a full builder script defining a simple dialog window will be developed. We first present single blocks of the script that deal with a specific topic. At the end of the chapter the script is again printed as a whole. The order of the single blocks can be adapted as a common pattern. It takes logic dependencies between the different elements into account and complies to the ordering requirements mentioned above.
The example script discussed in this chapter has already been partly presented in other chapters of this guide. It defines a dialog window whose purpose it is to create a new text file. The dialog provides the following features:
The following screenshot shows the example dialog window in action:
The builder script starts with the definition of some beans that are used by UI components as helper classes. Such beans do not have references to graphical elements defined in this script. Therefore they can be put at the very top. First the bean acting as the form's model and the command object to be triggered when the user clicks the OK button are defined:
<!-- The form bean. This object acts as the model of the dialog.--> <di:bean name="createFileModel" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.CreateFileData"/> <!-- The command to be executed when the OK button is pressed. --> <di:bean name="okCommand" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.CreateFileCommand"> <di:constructor> <di:param refName="jguiraffe.applicationContext"/> <di:param refName="createFileModel"/> <di:param refName="action:editRefreshAction"/> </di:constructor> </di:bean>
The bean named createFileModel is a simple Java bean defining get and set methods corresponding to the input fields of the form. It basically has two properties of type String for the two text fields in the dialog - there is no additional logic. For database-centric applications an entity class could be used, too. Declaring the form model as a bean has the advantage that it is automatically created and is available to all components that need access to it. Note that it is a singleton bean, so only a single instance is created, and all referencing components share the same instance.
Note: What happens if the builder script is executed multiple times, e.g. if the user wants to create a number of files? Will there still be only a single instance of the model bean? The answer is no. Everytime the dialog window is closed all singleton beans in the bean context are freed. The next time the builder script is processed again new instances are created. This is what we want in this case: when the dialog opens it starts with empty input fields. If a dialog's purpose is to edit an existing data object, the model has to be created and populated before the builder script is executed. It can then be passed to the builder as a property of the ApplicationBuilderData object.
After that another bean is defined which serves as the command to be executed when the user clicks the OK button. In this example a custom command implementation, CreateFileCommand is used. We do not show this class here, it is not too complicated. It creates the file as specified by the input fields of the dialog. The directory in which the file is created is obtained from the global ApplicationContext object passed to the constructor - storing global state of the application is a typical use case of the ApplicationContext class. Two points are worth a remark:
Next there are some bean definitions for validators that will later be used by input components:
<!-- A validator for required input fields.--> <di:bean name="requiredValidator" beanClass="net.sf.jguiraffe.transform.RequiredValidator"> </di:bean> <!-- A regular expression validator for validating a file name. Certain characters are not allowed in file names. --> <di:bean name="fileNameValidator" beanClass="net.sf.jguiraffe.transform.RegexValidator"> <di:setProperty property="regex" value="[^\*\\/~\|<>]*"/> </di:bean>
Validators are normal beans which can be created using the tags of the dependency injection framework. Alternatively, they can be created by <f:validator> tags directly. Using the <di:bean> tag provides more features. Also, because most validators can be shared between multiple components, it makes sense to define them globally and reference them from the corresponding input elements. If an application uses some specialized custom validators, they can be declared in the top-level builder script and are then available in all other builder scripts through the parent bean context.
For this example we define an instance of RequiredValidator which checks that an input field is not empty. Then a specialized validator for checking the file name input field is declared. It ensures that the field does not contain any of the invalid characters. We will see below how these validators are associated with input fields.
The next block in the builder script is concerned with action definitions. Because the window under construction is just a simple dialog box there is neither a main menu nor a tool bar. However, the text area input element has a popup menu which is defined using actions.
<!-- Action for clearing the content text field. --> <a:action name="clearContentAction" textres="newfile_act_clear_text" mnemonicres="newfile_act_clear_mnemo" taskBean="clearTask"/> <!-- Action for adding lorem ipsum to the content text field. --> <a:action name="addContentLoremIpsumAction" textres="newfile_act_loremipsum_text" mnemonicres="newfile_act_loremipsum_mnemo" taskBean="addLoremIpsumTask"/> <!-- Action for adding XML to the content text field. --> <a:action name="addContentXMLAction" textres="newfile_act_xml_text" mnemonicres="newfile_act_xml_mnemo" taskBean="addXMLTask"/> <!-- Action for adding HTML to the content text field. --> <a:action name="addContentHTMLAction" textres="newfile_act_html_text" mnemonicres="newfile_act_html_mnemo" taskBean="addHTMLTask"/> <!-- Action data definition for the append sub menu in the popup menu --> <a:actionData textres="newfile_men_append_text" mnemonicres="newfile_men_append_mnemo" var="menuAppend"/>
The actions defined here are related to clearing the text area for the new file's content and for inserting specific text fragments. The <a:action> tags define the typical visual properties of their actions, e.g. a name, a text label, and a mnemonic. Icons are not used here, but would also be allowed.
An interesting point are the references to the actions' tasks specified by the taskBean attributes. The beans referenced here have not yet been declared in the builder script. Actually, action task definitions belong to the exceptional elements in builder scripts that support forward references. There is a good reason for this: Beans used as tasks for actions frequently require access to graphical elements defined in the same builder script - a typical use case for an action is to manipulate parts of the UI. So to create the task beans, references to those elements have to be injected. In such a constellation it is easy to construct cyclic dependencies: Actions reference task beans, task beans reference UI elements, UI elements reference actions. Therefore it is possible to define action tasks at an arbitrary position of the builder script. We will meet them again at the very bottom of the script.
In the following block the actions are used to construct a popup menu:
<!-- Popup menu handler for the append sub menu --> <di:bean name="appendMenu" beanClass="net.sf.jguiraffe.gui.builder.action.SimplePopupMenuHandler"> <di:constructor> <di:param> <di:list> <di:element refName="action:addContentLoremIpsumAction"/> <di:element refName="action:addContentXMLAction"/> <di:element refName="action:addContentHTMLAction"/> </di:list> </di:param> </di:constructor> <di:setProperty property="actionData" refName="menuAppend"/> </di:bean> <!-- Popup menu handler for the text area's content menu --> <di:bean name="fileContentPopupHandler" beanClass="net.sf.jguiraffe.gui.builder.action.SimplePopupMenuHandler"> <di:constructor> <di:param> <di:list> <di:element refName="appendMenu"/> <di:element refName="action:clearContentAction"/> </di:list> </di:param> </di:constructor> </di:bean>
Here two beans of type SimplePopupMenuHandler are defined. The one with the name fileContentPopupHandler represents the actual popup menu. It consists of a menu item for clearing the text area and a sub menu. The sub menu is defined by the other SimplePopupMenuHandler bean. Here the actions for inserting text fragments are contained. This is all this simple dialog box does with its actions. If you were defining a frame window, you could create a main menu as well. A tool bar would be created as part of the window definition.
The form controller is a regular bean definition. For this example no specific, UI-related logic is needed; therefore we can go with the default form controller implementation. The whole declaration looks as follows:
<!-- The bean for the form controller.--> <di:bean name="controller" beanClass="net.sf.jguiraffe.gui.builder.window.ctrl.FormController"> <di:setProperty property="btnOkName" value="btnOk"/> <di:setProperty property="btnCancelName" value="btnCancel"/> <di:setProperty property="okCommand" refName="okCommand"/> </di:bean>
Here some important properties of the controller are set: the names of the OK and the Cancel buttons, and the command to be executed when the OK button is clicked. We have already seen the declaration of the command bean. The buttons are defined together with the whole UI of the window in the next block.
As we will see in a moment, the controller bean is associated with the dialog window as part of the window declaration. Because of that controller beans are typically defined before the window. In most cases this is not a problem. The FormController class has access to the current ComponentBuilderData object, from which all elements defined by the builder script can be obtained. If a custom controller implementation actually required injection of some UI elements, it is possible to declare the controller bean in the body of the tag defining the window - after the UI-related tags, and before the tag that connects the controller to the window. At this position all UI elements can be referenced.
The whole dialog window is defined by a <w:dialog> tag. In the body of this tag tags of the form builder tag library are placed to create the graphical elements comprising the UI. This block looks as follows:
<w:dialog titleres="newfile_title" center="true" resizable="true"> <f:borderlayout canShrink="true"/> <!-- The main panel --> <f:panel> <f:borderconstr name="CENTER"/> <f:percentlayout columns="4dlu start/preferred 3dlu full/preferred(6cm)/100 4dlu" rows="4dlu preferred 3dlu preferred full/preferred(5cm)/100 4dlu" canShrink="true"/> <f:label textres="newfile_lab_name" mnemonicres="newfile_lab_name_mnemo" componentref="fileName"> <f:percentconstr col="1" row="1"/> </f:label> <f:textfield name="fileName" displayNameres="newfile_disp_name" maxlength="200" tooltipres="newfile_txt_name_tip"> <f:percentconstr col="3" row="1"/> <f:validators phase="syntax"> <f:validator beanName="requiredValidator"/> <f:validator beanName="fileNameValidator"> <f:properties> <f:property property="ERR_PATTERN"> <f:localized resid="ERR_FILENAME_PATTERN"/> </f:property> </f:properties> </f:validator> <f:validator class="net.sf.jguiraffe.examples.tutorial.createfile.UniqueFileNameValidator"/> </f:validators> </f:textfield> <f:label textres="newfile_lab_content" mnemonicres="newfile_lab_content_mnemo" componentref="fileContent"> <f:percentconstr col="1" row="3"/> </f:label> <f:textarea name="fileContent" displayNameres="newfile_disp_content" rows="10" columns="50"> <f:percentconstr col="1" row="4" spanx="3" targetCol="3"/> <f:font name="Monospaced" size="13"/> <f:validator phase="syntax" beanName="requiredValidator"> <f:properties> <f:property property="errorLevel" value="WARNING"/> </f:properties> </f:validator> <a:popup beanName="fileContentPopupHandler"/> </f:textarea> </f:panel> <!-- The button bar --> <f:panel> <f:borderconstr name="SOUTH"/> <f:buttonlayout/> <f:button name="btnOk" textres="newfile_btn_create" mnemonicres="newfile_btn_create_mnemo" command="create"/> <f:button name="btnCancel" textres="newfile_btn_cancel" mnemonicres="newfile_btn_cancel_mnemo"/> </f:panel> <!-- Connect the form controller --> <w:formController beanName="controller" formBeanName="createFileModel"> <!-- Register the field markers --> <w:formControllerListener beanName="jguiraffe.fieldMarker"/> </w:formController> </w:dialog>
This fragment is a bit longer, but there is not that much to say about it. Most of the elements occuring in this script should be familiar after reading the chapter Building user interfaces. The script specifies the layout to use - a border layout with a main panel in the center area and a button panel in the south area; the main panel uses a percent layout - and adds the corresponding UI elements to it. Here are a few remarks:
After the definition of the window the most important declarations of the builder script have been discussed. What remains are elements which reference other graphical elements defined so far. Under this category fall event listener declarations and the tasks of actions. Remember that we already mentioned in the sub section Actions and menus that action tags can reference task beans that may be defined later in the script. Here the action tasks manipulate the text area with the content of the new file. Thus a reference to this input element needs to be injected. The following listing shows the definitions for the task beans for clearing the text area and for inserting text fragments:
<di:bean name="clearTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.ClearTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> </di:constructor> </di:bean> <di:bean name="addLoremIpsumTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.AppendTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> <di:param> <di:value> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum quis mauris in quam mattis dignissim. Integer non nibh purus, vel cursus tortor. Donec quam orci, tincidunt vitae fringilla vitae, suscipit eu nibh. Etiam fermentum, turpis ut gravida tempor, nisl lorem malesuada ante, at sodales libero nibh non libero. Duis vel erat hendrerit neque condimentum imperdiet sit amet id diam. Phasellus in congue urna. Sed lectus orci, hendrerit eget vehicula sit amet, tempus ac dui. Aenean nec dolor diam, fermentum venenatis justo. Nunc ac lorem arcu, ut convallis dui. Nam pretium feugiat felis sit amet consequat. Morbi id nunc sodales diam pulvinar commodo id ac magna. Donec accumsan rhoncus arcu ut porta. Donec blandit porta suscipit. Cras sit amet nibh non dui faucibus blandit. Curabitur tincidunt sagittis neque, ut sagittis magna tempor ut. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus egestas iaculis eleifend. Pellentesque dui lacus, fringilla vulputate tempor eu, pretium non eros. Ut id eros nibh. </di:value> </di:param> </di:constructor> </di:bean> <di:bean name="addXMLTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.AppendTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> <di:param> <di:value><![CDATA[ <?xml version="1.0" encoding="ISO-8859-1"?> <root> </root> ]] ></di:value> </di:param> </di:constructor> </di:bean> <di:bean name="addHTMLTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.AppendTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> <di:param> <di:value><![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> </body> </html> ]] ></di:value> </di:param> </di:constructor> </di:bean>
In this example special action task implementation classes are used. The classes are not shown here, but they are pretty simple. ClearTextActionTask is initialized with a reference to a text component handler in its constructor. It simply sets the data of the handler to null which clears the content of the text field.
The AppendTextActionTask class is used to append a configurable text fragment to the text area. A reference to the component handler of the text area and the text fragment to be appended are passed to the constructor. When triggered AppendTextActionTask reads the current data of the text component handler, appends the text fragment to it, and writes the resulting String back into the handler. The fact that the text fragment to be appended can be configured in the builder script makes this class pretty flexible. However, the declarations tend to become verbose, especially if XML fragments are involved which have to be enclosed in CDATA sections. In this example three instances of the AppendTextActionTask task class are created and initialized with different text fragments: one with the famous lorem ipsum text, one with a skeleton for an XML document, and one with a skeleton for an HTML file. Also note how the references to the text component handlers are passed to the constructor: by using the special prefix comp: followed by the name of the affected input component.
We are now complete with our tour through the different sections that can occur in a builder script. You should have gained an understanding about the order in which elements are best placed in such a script. To give the reader a better overview we present the script again as a whole. The different sections are marked with comments.
<j:jelly xmlns:j="jelly:core" xmlns:di="diBuilder" xmlns:f="formBuilder" xmlns:a="actionBuilder" xmlns:w="windowBuilder"> <!-- The form bean. This object acts as the model of the dialog.--> <di:bean name="createFileModel" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.CreateFileData"/> <!-- The command to be executed when the OK button is pressed. --> <di:bean name="okCommand" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.CreateFileCommand"> <di:constructor> <di:param refName="jguiraffe.applicationContext"/> <di:param refName="createFileModel"/> <di:param refName="action:editRefreshAction"/> </di:constructor> </di:bean> <!-- Validators. As validators can be shared between components, they can be declared globally and then be referenced from the input field declarations. --> <!-- A validator for required input fields.--> <di:bean name="requiredValidator" beanClass="net.sf.jguiraffe.transform.RequiredValidator"> </di:bean> <!-- A regular expression validator for validating a file name. Certain characters are not allowed in file names. --> <di:bean name="fileNameValidator" beanClass="net.sf.jguiraffe.transform.RegexValidator"> <di:setProperty property="regex" value="[^\*\\/~\|<>]*"/> </di:bean> <!-- Action definitions --> <!-- Action for clearing the content text field. --> <a:action name="clearContentAction" textres="newfile_act_clear_text" mnemonicres="newfile_act_clear_mnemo" taskBean="clearTask"/> <!-- Action for adding lorem ipsum to the content text field. --> <a:action name="addContentLoremIpsumAction" textres="newfile_act_loremipsum_text" mnemonicres="newfile_act_loremipsum_mnemo" taskBean="addLoremIpsumTask"/> <!-- Action for adding XML to the content text field. --> <a:action name="addContentXMLAction" textres="newfile_act_xml_text" mnemonicres="newfile_act_xml_mnemo" taskBean="addXMLTask"/> <!-- Action for adding HTML to the content text field. --> <a:action name="addContentHTMLAction" textres="newfile_act_html_text" mnemonicres="newfile_act_html_mnemo" taskBean="addHTMLTask"/> <!-- Action data definition for the append sub menu in the popup menu --> <a:actionData textres="newfile_men_append_text" mnemonicres="newfile_men_append_mnemo" var="menuAppend"/> <!-- Popup menu handler for the append sub menu --> <di:bean name="appendMenu" beanClass="net.sf.jguiraffe.gui.builder.action.SimplePopupMenuHandler"> <di:constructor> <di:param> <di:list> <di:element refName="action:addContentLoremIpsumAction"/> <di:element refName="action:addContentXMLAction"/> <di:element refName="action:addContentHTMLAction"/> </di:list> </di:param> </di:constructor> <di:setProperty property="actionData" refName="menuAppend"/> </di:bean> <!-- Popup menu handler for the text area's content menu --> <di:bean name="fileContentPopupHandler" beanClass="net.sf.jguiraffe.gui.builder.action.SimplePopupMenuHandler"> <di:constructor> <di:param> <di:list> <di:element refName="appendMenu"/> <di:element refName="action:clearContentAction"/> </di:list> </di:param> </di:constructor> </di:bean> <!-- The bean for the form controller.--> <di:bean name="controller" beanClass="net.sf.jguiraffe.gui.builder.window.ctrl.FormController"> <di:setProperty property="btnOkName" value="btnOk"/> <di:setProperty property="btnCancelName" value="btnCancel"/> <di:setProperty property="okCommand" refName="okCommand"/> </di:bean> <!-- The dialog window --> <w:dialog titleres="newfile_title" center="true" resizable="true"> <f:borderlayout canShrink="true"/> <!-- The main panel --> <f:panel> <f:borderconstr name="CENTER"/> <f:percentlayout columns="4dlu start/preferred 3dlu full/preferred(6cm)/100 4dlu" rows="4dlu preferred 3dlu preferred full/preferred(5cm)/100 4dlu" canShrink="true"/> <f:label textres="newfile_lab_name" mnemonicres="newfile_lab_name_mnemo" componentref="fileName"> <f:percentconstr col="1" row="1"/> </f:label> <f:textfield name="fileName" displayNameres="newfile_disp_name" maxlength="200" tooltipres="newfile_txt_name_tip"> <f:percentconstr col="3" row="1"/> <f:validators phase="syntax"> <f:validator beanName="requiredValidator"/> <f:validator beanName="fileNameValidator"> <f:properties> <f:property property="ERR_PATTERN"> <f:localized resid="ERR_FILENAME_PATTERN"/> </f:property> </f:properties> </f:validator> <f:validator class="net.sf.jguiraffe.examples.tutorial.createfile.UniqueFileNameValidator"/> </f:validators> </f:textfield> <f:label textres="newfile_lab_content" mnemonicres="newfile_lab_content_mnemo" componentref="fileContent"> <f:percentconstr col="1" row="3"/> </f:label> <f:textarea name="fileContent" displayNameres="newfile_disp_content" rows="10" columns="50"> <f:percentconstr col="1" row="4" spanx="3" targetCol="3"/> <f:font name="Monospaced" size="13"/> <f:validator phase="syntax" beanName="requiredValidator"> <f:properties> <f:property property="errorLevel" value="WARNING"/> </f:properties> </f:validator> <a:popup beanName="fileContentPopupHandler"/> </f:textarea> </f:panel> <!-- The button bar --> <f:panel> <f:borderconstr name="SOUTH"/> <f:buttonlayout/> <f:button name="btnOk" textres="newfile_btn_create" mnemonicres="newfile_btn_create_mnemo" command="create"/> <f:button name="btnCancel" textres="newfile_btn_cancel" mnemonicres="newfile_btn_cancel_mnemo"/> </f:panel> <!-- Connect the form controller --> <w:formController beanName="controller" formBeanName="createFileModel"> <!-- Register the field markers --> <w:formControllerListener beanName="jguiraffe.fieldMarker"/> <w:formControllerListener beanName="toolTipMarker"/> </w:formController> </w:dialog> <!-- Action task definitions that require references to UI elements --> <di:bean name="clearTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.ClearTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> </di:constructor> </di:bean> <di:bean name="addLoremIpsumTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.AppendTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> <di:param> <di:value> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum quis mauris in quam mattis dignissim. Integer non nibh purus, vel cursus tortor. Donec quam orci, tincidunt vitae fringilla vitae, suscipit eu nibh. Etiam fermentum, turpis ut gravida tempor, nisl lorem malesuada ante, at sodales libero nibh non libero. Duis vel erat hendrerit neque condimentum imperdiet sit amet id diam. Phasellus in congue urna. Sed lectus orci, hendrerit eget vehicula sit amet, tempus ac dui. Aenean nec dolor diam, fermentum venenatis justo. Nunc ac lorem arcu, ut convallis dui. Nam pretium feugiat felis sit amet consequat. Morbi id nunc sodales diam pulvinar commodo id ac magna. Donec accumsan rhoncus arcu ut porta. Donec blandit porta suscipit. Cras sit amet nibh non dui faucibus blandit. Curabitur tincidunt sagittis neque, ut sagittis magna tempor ut. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus egestas iaculis eleifend. Pellentesque dui lacus, fringilla vulputate tempor eu, pretium non eros. Ut id eros nibh. </di:value> </di:param> </di:constructor> </di:bean> <di:bean name="addXMLTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.AppendTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> <di:param> <di:value><![CDATA[ <?xml version="1.0" encoding="ISO-8859-1"?> <root> </root> ]] ></di:value> </di:param> </di:constructor> </di:bean> <di:bean name="addHTMLTask" beanClass="net.sf.jguiraffe.examples.tutorial.createfile.AppendTextActionTask"> <di:constructor> <di:param refName="comp:fileContent"/> <di:param> <di:value><![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> </body> </html> ]] ></di:value> </di:param> </di:constructor> </di:bean> </j:jelly>