Tutorial - Objective Html and Struts Advanced

Author Keith Wong, keithwong@optushome.com.au

Last updated: 23rd Feb 2002

Example Code

These are the files for this example.
customerlist.html  [source code]
customerlist.jsp  [source code]
customer.html  [source code]
customer.jsp  [source code]
CustomerListForm.java  [source code]
CustomerListFormDesign.java  [source code]
CustomerForm.java  [source code]
CustomerFormDesign.java  [source code]
CustomerDomain.java  [source code]
PostCodeDomain.java  [source code]
Customer.java  [source code]
PostCode.java  [source code]
web.xml  [source code]
struts-config.xml  [source code]
ohtml-config.xml  [source code]
ohtml-config1.xml  [source code]

The binaries can be downloaded below (packaged in a war file). In Tomcat, simple place the war file under the "webapps" directory, it will automatically explode when Tomcat is started up. Make sure the objectivehtml-alphaX.jar is in the Tomcat $TOMCAT_HOME/lib directory. You should then be able to view the form using the URL http://localhost:8080/objectivehtml-struts-adv/. Replace the server address and port number if your computer is setup differently.
objectivehtml-struts-adv.war

Introduction

This tutorial requires that you have gone through all the previous tutorials or at least you are familiar with all of the features of the previous tutorials. If you have not gone through the previous tutorials then it is suggested you go back and read them before proceeding.

The tutorial assumes that the reader is familiar with the workings of Struts. If you are not then check out the Struts website it has some good tutorials on how to get started with Struts. This code has been tested with Struts 1.02. If you have difficulty getting this tutorial working with other Struts versions please email me.

In this tutorial we'll provide a detailed guide into how you can integrate Objective Html and Struts. This integration allows you to use the features of both Struts and Objective Html fairly seemlessly. This new integration also allows you to configure the signals, slots and update-order of your forms using xml configuration files.

Once again we have our customer details form. The functionality of the customer details form is the same as the ohtmlc tutorial. However for this tutorial we are going to add a new page and form, this page will list all the customers in the system and will allow the user to select a customer and view the customers details.

Code Walk-Through

customer.html

This file represents our customer form design. We will compile this html file into a Objective Html form using ohtmlc. Lets first clean up the html with HtmlTidy. To make the html code xml well-formed we can run the command:

	htmltidy --output-xml y customer_orig.html > customer.html

This command will generate a well formed xml document and the good thing about this is that the file should be still viewable by most browsers. The file generated will also have all tag names and attribute names in lower case which is another requirement of ohtmlc.

CustomerFormDesign.java

This class will have the code to render our customer form design (customer.html). We will use ohtmlc to generate the CustomerFormDesign class. The command to run is:

	ohtmlc -package mypackage customer.html CustomerFormDesign

The -package option simply indicates the package of the class. The last two arguments are the input html file and the class name of the class to be generated. This command will produce a file with the same name as the class name, i.e. CustomerFormDesign.java.

Something to note is that we generated a number of member variables in our CustomerFormDesign.java file. The ohtmlc will generate a member variable whenever it encounters either an element with an id attribute or a form element. The member variable name is the same as the name given in the id attribute or name attribute (only for form elements).

customerlist.html

This file represents our customer list form design. We will compile this html file into a Objective Html form using ohtmlc. Once again we will clean up the html with HtmlTidy. To make the html code xml well-formed we run the command:

	htmltidy --output-xml y customerlist_orig.html > customerlist.html

CustomerListFormDesign.java

This class will have the code to render our customer list form design (customerlist.html). We will use ohtmlc to generate the CustomerListFormDesign class. The command to run is:

	ohtmlc -package mypackage customerlist.html CustomerListFormDesign

Customer.java

This class is fairly straight forward, its simply a JavaBean that holds all the details of a Customer. It will be the object we will use to pass a Customer details between the front-end and back-end code.

PostCode.java

Like the Customer class this class is simply a JavaBean that holds all the details of a PostCode. It will be the object we will use to pass a PostCode details between the front-end and back-end code.

CustomerDomain.java

This class consists of all the backend functionality for customers. The implementation is only a stub it simply retrieves and saves customers from memory but its purpose is simply to highlight how you would separate your front-end and back-end code when using Objective Html.

PostCodeDomain.java

This class consists of all the backend functionality for post codes. The implementation is only a stub it simply retrieves post codes from memory but its purpose is simply to highlight how you would separate your front-end and back-end code when using Objective Html.

CustomerForm.java

This class contains all the presentation logic for the customer form. The class extends CustomerFormDesign so that it inherits all the visual properties of the form. Most of the code should be fairly straight forward as its pretty much the same code as the previous tutorials. Notice however that we have removed all the code for connecting signals and slots and also the update-order. This will now be done in the configuration files (see the configuration files section for details). One interesting method worth looking at is checkException(HttpServletRequest).

	/**
	 * This method clears any stored exceptions.
	 */
	public void clearException()
	{
		m_objThrowable = null;
	} // end clearException

	/**
	 * This method should be called after a business method (or slot method)
	 * is called to check whether an exception occurred.
	 */
	public void checkException(HttpServletRequest objRequest)
	{
		if (objRequest.getAttribute("ohtml.result") != null)
		{
			EventResult objResult = (EventResult)objRequest.getAttribute("ohtml.result");
			if (objResult.getResult() == null
				|| objResult.getResult().equals("success"))
			{
				if (m_objThrowable != null)
				{
					objResult.setResult("failure");
					objResult.setAttribute("error", m_objThrowable);
				} // end if something failed
				else
				{
					objResult.setResult("success");
				} // end else everything ok
			} // end if not failure

			// lets clear the exception as we've processed this exception
			clearException();

		} // end if result exists
	} // end checkException

The checkException will check to see whether any exceptions were thrown by slot methods. If a slot method threw an exception then it will be set in the m_objThrowable member variable. If there is an exception then we set the result object appropriately. This method is also a slot method and is to be connected to the afterUpdateData signal of the HtmlForm object. This signal will be emitted immediately after all the form objects have been updated with submitted data. The EventResult object is used to determine which page to forward the request to. So for the case where no exception is thrown our request will be forwarded to the page associated with the "success" forward specified in the struts-config.xml file. If an exception is thrown our request will be forwarded to the page associated with the "failure" forward.

CustomerListForm.java

This class contains all the presentation logic for the customer list form. The class extends CustomerListFormDesign so that it inherits all the visual properties of the form.

	/**
	 * Lets populate the form with all the customers.
	 */
	public void getAllCustomers()
	{
		Collection clCustomers = CustomerDomain.retrieveAllCustomers();
		boolean bFirstRow = true;
		for (Iterator it = clCustomers.iterator(); it.hasNext(); )
		{
			Customer objCustomer = (Customer)it.next();
			HtmlTableRow objTableRow = null;
			HtmlRadioButton objRadioButton = null;
			if (bFirstRow == true)
			{
				bFirstRow = false;
				// no need to create new row
				objTableRow = m_tblLayout.getTableRow(m_tblLayout.getRows()-1);
				// lets set the control box
				objRadioButton = new HtmlRadioButton(objTableRow.getTableCell(0), "customerid");
				connect(slot("processCustomerSelection(String)"), objRadioButton.signal("dataSubmitted(String)"));
			} // end if first row
			else
			{
				// lets create a new row with the same properties as the previous
				objTableRow = m_tblLayout.appendTableRow(true);
				// lets set the control box
				objRadioButton = new HtmlRadioButton(objTableRow.getTableCell(0), "customerid");
			} // end else not first row

			objRadioButton.setValue(objCustomer.getCustomerId().toString());
			// lets set the first name
			objTableRow.getTableCell(1).setText(objCustomer.getFirstName());
			// lets set the last name
			objTableRow.getTableCell(2).setText(objCustomer.getLastName());
			// lets set the birth date
			SimpleDateFormat objFormatter = new SimpleDateFormat("yyyy-MM-dd");
			objTableRow.getTableCell(3).setText(objFormatter.format(objCustomer.getBirthDate()));

		} // end for more customers
	} // end getAllCustomers

This method retrieves a list of customers via the CustomerDomain class and then creates a table row entry and a HtmlRadioButton object for each customer. To add a new row to the table m_tblLayout the method appendTableRow(boolean) is used. When called with the parameter value true, this will add a new row with all the same properties (all the attributes of the table row and table cells are copied) as the last one. This method provides a convenient way to add table rows.

	/**
	 * This signal is emitted when a new customer is selected.
	 */
	public Signal customerSelected(long lCustomerId)
	{
		ParameterList objParamList = new ParameterList();
		objParamList.addParam(lCustomerId);
		Signal objSignal = null;
		try
		{
			objSignal = new Signal(signal("customerSelected(long)"), objParamList);
		}
		catch (Exception e)
		{
			e.printStackTrace();	// shouldn't happen
		}
		return objSignal;

	} // end customerSelected

The above method is a signal for this class. This signal is emitted whenever a customer is selected on the form.

This class also has the method checkException(HttpServletRequest) which is used in the same way as in the CustomerForm class.

web.xml

The web.xml file is used to configure the servlet part of this application. Most of the configuration directives are the same as a typical struts application. We'll just quickly highlight the directives that need to be changed for an Objective Html - Struts application.

    <servlet-class>objectivehtml.struts.ObjectiveHtmlServlet</servlet-class>

The servlet-class needs to be the ObjectiveHtmlServlet servlet class. This servlet is required for Objective Html with Struts to work correctly.

    <init-param>
      <param-name>ohtmlconfig</param-name>
      <param-value>/WEB-INF/ohtml-config.xml</param-value>
    </init-param>

This servlet parameter specifies the location of Objective Html configuration file. This parameter does not need to be specified if you are using the default file location /WEB-INF/ohtml-config.xml.

struts-config.xml

In this file we'll configure all our forward pages and actions. If an action is to be processed by Objective Html then it must use the action class objectivehtml.struts.ObjectiveHtmlAction or a subclass of this.

    <!-- loading up the customer list form -->
    <action    path="/customerlistload"
               type="objectivehtml.struts.ObjectiveHtmlAction">
      <forward name="success"              path="/customerlist.jsp"/>
    </action>

    <!-- loading up the customer form -->
    <action    path="/customerload"
               type="objectivehtml.struts.ObjectiveHtmlAction">
      <forward name="success"              path="/customer.jsp"/>
    </action>

    <!-- processing the customer form -->
    <action    path="/customersave"
               type="objectivehtml.struts.ObjectiveHtmlAction">
      <forward name="failure"              path="/customer.jsp"/>
      <forward name="success"              path="/customer.jsp"/>
    </action>

We have 3 actions for our tutorial. Because we want these actions to be processed by Objective Html we specify the action class to be objectivehtml.struts.ObjectiveHtmlAction. Note we also specify all our forwards here.

ohtml-config.xml

In this file we configure all our Objective Html forms and actions. There are two versions of this configuration file provided here (the other one being ohtml-config1.xml). Both differ slightly in there approach but effectively achieve the same functionality.

<!DOCTYPE ohtml-config PUBLIC
       "-//Objective Html//DTD Objective Html Configuration 1.0//EN"
       "http://objectivehtml.sourceforge.net/dtds/ohtml-config_1_0.dtd">

This document type declaration is needed in all Objective Html configuration files.

	<ohtml-result>ohtml.result</ohtml-result>

This directive specifies the request name that EventResult object will be bound with. A new EventResult is created and stored in the request object for each action. This object is inspected after the action is processed to determine which jsp the request is to be forwarded to.

		<ohtml-form>
			<name>CustomerForm</name>
			<type>mypackage.CustomerForm</type>

In the first part of the configuration file we specify our Objective Html forms. In this section we can configure some properties of the form. The name tag contains the name for this form and it is how we will reference this form later on in the file (the name needs to unique for each form). The type tag contains the fully qualified Java class name of the object that we are configurating properties for.

			<update-orders>

				<update-order>
					<name>m_txtPostCode</name>
					<order>10</order>
				</update-order>

				<update-order>
					<name>m_btnSave</name>
					<order>20</order>
				</update-order>

			</update-orders>

Here we specify the update order of the form components. All the member variables of the form that are to be referenced in this file have to be public. With this configuration the form component m_btnSave will be updated last and the form component m_txtPostCode will be updated after all the other form components are updated.

			<connections>

				<connection>
					<slot-obj>this</slot-obj>
					<slot>saveForm()</slot>
					<signal-obj>m_btnSave</signal-obj>
					<signal>clicked()</signal>
				</connection>

				<connection>
					<slot-obj>this</slot-obj>
					<slot>updatePostCodeFields(String)</slot>
					<signal-obj>m_txtPostCode</signal-obj>
					<signal>valueChanged(String,String)</signal>
					<set-error-handler>false</set-error-handler>
				</connection>

				<connection>
					<slot-obj>this</slot-obj>
					<slot>checkException(javax.servlet.http.HttpServletRequest)</slot>
					<signal-obj>this</signal-obj>
					<signal>afterUpdateData(javax.servlet.http.HttpServletRequest)</signal>
				</connection>

			</connections>

Here we configure 3 connections. The first connection connects the clicked() signal of the Save button to the saveForm() slot method of this form. The second connection connects the valueChanged(String,String) signal of the PostCode text field to the updatePostCodeFields(String) slot method of this form. The third connection connects the afterUpdateData(HttpServletRequest) signal of this form to the checkException(HttpServletException) slot method of this form. Note all the member variables that we are connecting to, that is m_btnSave and m_txtPostCode must be public for this to work.

		<ohtml-form>
			<name>CustomerListForm</name>
			<type>mypackage.CustomerListForm</type>

			<connections>

				<connection>
					<slot-obj>this</slot-obj>
					<slot>checkException(javax.servlet.http.HttpServletRequest)</slot>
					<signal-obj>this</signal-obj>
					<signal>afterUpdateData(javax.servlet.http.HttpServletRequest)</signal>
				</connection>

			</connections>

		</ohtml-form>

Here we configure another form CustomerListForm. This class has only one connection.

		<ohtml-action>
			<path>/customerlistload</path>

In the next part of the configuration file we configure all our actions. These Objective Html actions are similar to Struts actions. Each action maps to a request path like Struts. This action directive will be triggered whenever the request path is "customerlistload.do".

			<create-forms>
				<create-form>
					<name>CustomerListForm</name>
					<session-ref>myapp.customerlist</session-ref>
					<condition>always</condition>
				</create-form>
			</create-forms>

Here we specify that a form is to be created when this action is encountered. The form to be created is the CustomerListForm (specified in the name tag). The form created will have all properties specified earlier in the file for the CustomerListForm. The new form is to be stored in the session object and bound with the name "myapp.customerlist". The condition tag indicates under what condition this form is to be created. Here we've used 'always' meaning that whenever this action is encountered this form is always created. The other option you can use is 'notexists'. This means the form will be created as long as no object is already bound to that session name specified in the session-ref tag.

			<invoke-methods>

				<invoke-method>
					<session-ref>myapp.customerlist</session-ref>
					<method-signature>getAllCustomers()</method-signature>
					<method-params>
					</method-params>
				</invoke-method>

				<invoke-method>
					<session-ref>myapp.customerlist</session-ref>
					<method-signature>checkException(javax.servlet.http.HttpServletRequest)</method-signature>
					<method-params>
						<request-obj />
					</method-params>
				</invoke-method>

			</invoke-methods>

In this section we specify the methods we wish to invoke. These methods will be invoked in the order they are specified here. The first method to be invoked is getAllCustomers() on the object bound to the name "myapp.customerlist" in the session object. This method will retrieve all customers from the database and will populate the form with these customer details. The second method to be invoked is checkException(HttpServletRequest) on the same object. If you remember earlier this method is used to check whether any exceptions occurred during a method call. So if getAllCustomers() causes an exception it will be picked up in the checkException(HttpServletRequest) method. This method requires the request object as a parameter, we can pass this object to the method by using the empty tag request-obj.

			<default-forward>success</default-forward>

In this tag we specify the default forward. If no result is set in the EventResult object then this default is used. This is just for convenience if your action always has the same forward.

		<ohtml-action>
			<path>/customerload</path>

In this section we configure another action. This action is triggered whenever the path "customerload.do" is requested.

			<create-forms>
				<create-form>
					<name>CustomerForm</name>
					<session-ref>myapp.customer</session-ref>
					<condition>always</condition>
				</create-form>
			</create-forms>

The form to be created is the CustomerForm form (specified in the name tag). The form created will have all properties specified earlier in the file for the CustomerForm. The new form is to be stored in the session object and bound with the name "myapp.customer". This form will always be created whenever this action is triggered (as specified by the condition tag).

			<invoke-methods>

				<invoke-method>
					<session-ref>myapp.customer</session-ref>
					<method-signature>populateForm(long)</method-signature>
					<method-params>
						<request-param>customerid</request-param>
					</method-params>
				</invoke-method>

				<invoke-method>
					<session-ref>myapp.customer</session-ref>
					<method-signature>checkException(javax.servlet.http.HttpServletRequest)</method-signature>
					<method-params>
						<request-obj />
					</method-params>
				</invoke-method>

			</invoke-methods>

For this action we invoke 2 methods. The first method is populateForm(long) and it will be invoked on the CustomerForm object. This method requires a customerid as a method argument. Here we obtain the customerid from a request parameter. The request parameter bound with the name "customerid" will be passed to the method populateForm(long), the casting from String to long is done automatically.

			<update-form>myapp.customerlist</update-form>

Here we specify that the form object held in the session object and bound to the name "myapp.customerlist" will be updated with the submitted data (i.e. the method updateData(HttpServletRequest) will be called on this object).

		<ohtml-action>
			<path>/customersave</path>

			<update-form>myapp.customer</update-form>

		</ohtml-action>

The third action is triggered when the request path "customersave.do" is encountered. The action will update the form object held in the session object and bound to the name "myapp.customer" with the submitted data.

ohtml-config1.xml

This version of the configuration file has some slightly different directives to ohtml-config.xml but effectively achieve the same functionality. To use this configuration file simply change the init-param "ohtmlconfig" in the web.xml file to be this file. Below we'll highlight the differences in these two configuration files.

					<connections>
						<connection>
							<slot-session-ref>myapp.customer</slot-session-ref>
							<slot>populateForm(long)</slot>
							<signal-session-ref>myapp.customerlist</signal-session-ref>
							<signal>customerSelected(long)</signal>
						</connection>

					</connections>

This connection is made in the customerload action. If you recall in the previous configuration file we invoked the method populateForm(long) using the invoke-method directive. Instead of invoking the method directly we now connect the method to the customerSelected(long) signal of the CustomerListForm. So whenever a customer is selected on the CustomerListForm the CustomerForm will be populated with the customer details.

Conclusion

Using Objective Html alongside Struts gives you the strengths of both frameworks and provides you with a great platform to start developing your web applications. The major benefit for Objective Html is the ability to configure your forms using xml configuration files. Having this feature allows developers to build highly self-contained forms that are loosely coupled with each other and highly reuseable. Forms can be easily adapted to a particular problem domain by configuring the appropriate form properties and actions in the xml files.

SourceForge Logo