struts-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Ray Clough <>
Subject S2 - Ajax Dynamic Double Select Demo - complete code included
Date Wed, 30 May 2007 00:54:56 GMT

A common problem is having two related select controls on a page.  Call them
the 'trigger' and the 'target'.  This is an ideal use case for Ajax, because
you don't want to refresh the entire page to re-populate the 'target'
control when the user selects something in the 'trigger'.  We have produced
a very nice demo of this using the S2 Ajax tags, and I am enclosing the
Action class and a jsp page which give the example.  I hope this is useful
to people.  It was a difficult problem to solve, not helped by the very poor
quality of the Ajax documentation on the S2 site (sorry for beefing, but I
spent lots of extra hours because of it).  The documentation is incomplete,
disorganized, contradictory, and in many cases just plain wrong.

Anyway, here is the example:

The page calling the demo uses a link to
"/greetings/AjaxDynDoubleSelect_setup.action"  Here is the mapping of the
action from struts.xml:

		<action name="AjaxDynDoubleSelect_*" method="{1}" 
				class="greetings.struts2.action.AjaxDDS_DemoAction" >
			<result name="success" type="tiles" > /greetings/AjaxDemo.jspx </result>		


This is the Action class:

package greetings.struts2.action;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

 * @author  	Ray Clough, Michael Matz.
 * Created:		May 29, 2007
 * Project:		GreetingsEarth_S2_S1_Tiles
 * Package:		greetings.struts2.action
 * File:
 * <br/>
 * Description: 
 * <br/>
 * This Struts-2 Action class is used to handle the data actions for the
 * display and function of the Ajax Dynamic Double Select (DDS)
 * The point is to use two 'select' controls, which contain related data.
 * The first select control, referred to here as the 'trigger', reacts to
 * an 'onchange' event to set the contents of the second select control,
 * referred to here as the 'target', to a corresponding list.  The update
 * of the 'target' is done with Ajax, meaning that the entire page does not
 * need to be refreshed.  This is a common, but fairly trick operation.
public class AjaxDDS_DemoAction extends ActionSupport {

	/** Objects implementing 'Serializable' should define 'serialVersionUID' */
    private static final long serialVersionUID = -2394017505624866795L;
	private Map<String,List<String>> dataModel;
	private String triggerVal = null;

	 * This constructor initializes the data set which is displayed in
	 * the two select controls.
	public AjaxDDS_DemoAction() {
		// create the data model
		List<String> grains = new ArrayList<String>();
		List<String> fruits = new ArrayList<String>();
		List<String> veggies = new ArrayList<String>();
		List<String> nuts = new ArrayList<String>();
		List<String> other = new ArrayList<String>();

		grains.add("Wheat");		//$NON-NLS-1$
		grains.add("Barley");		//$NON-NLS-1$
		grains.add("Oats");			//$NON-NLS-1$
		grains.add("Rye");			//$NON-NLS-1$
		grains.add("Corn");			//$NON-NLS-1$

		fruits.add("Apples");		//$NON-NLS-1$
		fruits.add("Oranges");		//$NON-NLS-1$
		fruits.add("Limes");		//$NON-NLS-1$
		fruits.add("Kumquats");		//$NON-NLS-1$
		fruits.add("Dates");		//$NON-NLS-1$
		fruits.add("Persimmons");	//$NON-NLS-1$

		veggies.add("Leeks");		//$NON-NLS-1$
		veggies.add("Onions");		//$NON-NLS-1$
		veggies.add("Beans");		//$NON-NLS-1$
		veggies.add("Peas");		//$NON-NLS-1$
		veggies.add("Carrots");		//$NON-NLS-1$

		nuts.add("Pecans");			//$NON-NLS-1$
		nuts.add("Pistachios");		//$NON-NLS-1$
		nuts.add("Walnuts");		//$NON-NLS-1$
		nuts.add("Filberts");		//$NON-NLS-1$
		nuts.add("Pinole");			//$NON-NLS-1$
		nuts.add("Cashews");		//$NON-NLS-1$

		other.add("Ice Cream");		//$NON-NLS-1$
		other.add("Chocolate");		//$NON-NLS-1$
		other.add("Fudge");			//$NON-NLS-1$
		other.add("Pie");			//$NON-NLS-1$
		other.add("Cake");			//$NON-NLS-1$

		dataModel = new LinkedHashMap<String,List<String>>();
		dataModel.put("Grains", grains);		//$NON-NLS-1$
		dataModel.put("Fruits", fruits);		//$NON-NLS-1$
		dataModel.put("Vegetables", veggies);	//$NON-NLS-1$
		dataModel.put("Nuts", nuts);			//$NON-NLS-1$
		dataModel.put("Other", other);			//$NON-NLS-1$


	 * @return The 'setup()' method sets the initial value for the 'trigger',
	 * which will result in a compatible 'target' list being displayed
	public String setup() {

		// initial value for TargetList
		this.triggerVal = "Grains"; 	//$NON-NLS-1$

		return SUCCESS;

	 * The 'update()' method is called when the user selects a value from
	 * the 'trigger' list.
	 * @return The method writes the option elements to the response
	 * output Writer, and returns 'null'.  Returning 'null' tells the
	 * Struts processor that the processing cycle is complete, and the
	 * page asynchronously displays the returned values.
	public String update() {
		HttpServletRequest request = ServletActionContext.getRequest();
		triggerVal = request.getParameter("triggerValue");

		System.out.println("triggerVal = " + triggerVal);

		HttpServletResponse response = ServletActionContext.getResponse();
		response.setHeader("Cache-Control", "no-cache");

		Collection<String> targetValues = this.getTargetList(triggerVal);
		PrintWriter out = null;

		try {
			out = response.getWriter();
			out.print("<select id='targetSelectionId' >");
			for (String targetVal : targetValues) {
				out.println("<option>" + targetVal + "</option>");
		} catch (IOException e) {
		} finally {
			if (out != null) {

		return null;

	 * This method is called by the 'list' attribute on the trigger
	 * select control.
	 * @return Returns the collection used to populate the "trigger'
	 * select box.
	public Collection<String> getTriggerList() {
		Collection<String> keys = dataModel.keySet();
		return keys;

	 * @return Returns the Collection used to populate the 'target'
	 * select box, which is a function of the selected value in the 
	 * 'trigger' select box.
	private Collection<String> getTargetList(String trigger) {
		Collection<String> target = dataModel.get(trigger);
		if (target == null) {
			return Collections.emptyList();
		return target;

	 * This method is called by the 'trigger' select box, which has
	 * the 'name' attribute value of 'triggerValue'
	 * @param trigVal
	public void setTriggerValue(String trigVal) {
		System.out.println("setTriggerValue() = " + trigVal); //$NON-NLS-1$

		if (trigVal == null || trigVal.length() == 0) return;
		this.triggerVal = trigVal;

	 * This is called by the s:url tag for the 'target' s:div tag, which
	 * uses this value to append the initial target value to the url.
	 * @return Returns the current 'trigger' value.
	public String getTriggerValue() {
		System.out.println("getTriggerValue() returns: "  	//$NON-NLS-1$
				+ this.triggerVal);
		return this.triggerVal;

}             // end class 'AjaxDDS_DemoAction'

And here is the jsp page (it is in 'jspx' format):

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root version="2.0"
		xmlns:tiles=""  >

	<jsp:output omit-xml-declaration="true"
		doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"  />

		String basePath = request.getScheme()
				+ "://"
				+ request.getServerName()
				+ ":"
				+ request.getServerPort()
				+ request.getContextPath();

	< contentType="text/html" />

	<title>Dynamic Double Select with Ajax</title>
	<s:head theme="ajax" />
	<meta name='Author' content='Ray Clough' />
	<meta name='keywords' content='Greetings Earth - Struts_2 Demo'/>
	<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
	<meta http-equiv="pragma" content="no-cache" />
	<meta http-equiv="cache-control" content="no-cache" />

<script type="text/javascript">
	// <![CDATA[

		var basePath = "${BasePath}";

		function updateTarget() {
			//alert('function: updateTarget()' );
			var triggerId = document.getElementById("triggerId");
			var triggerValue = triggerId.value;

			var triggerWidget = dojo.widget.byId("targetDivId");

			var target = document.getElementById("targetDivId");
			var url = target.getAttribute("href");

			var newUrl = basePath 
				+ "/greetings/AjaxDynDoubleSelect_update.action?triggerValue=" 
				+ triggerValue;

			/* this publishes the topic, which is registered to the div tag,
				causing it to refresh itself. */
			dojo.event.topic.publish("updateTargetTopic", "triggerValue:" +

			/* this is an alternative to publish (above) */

	// ]]>


<form id="ddsFormId" >

	<table style="width:100%;">
			<td style="width:25%; text-align:right;" >Trigger: </td>
			<td style="width:*%; text-align:left;" >
				<s:select id="triggerId"  name="triggerValue"
						onchange="javascript: updateTarget(); " />

			<td style="width:25%; text-align:right;" >Target: </td>
			<td style="width:*; text-align:left;" >
				<s:url id="target_url"
						value="${BasePath}/greetings/AjaxDynDoubleSelect_update.action" >
					<s:param name="triggerValue" value="%{triggerValue}" />
				<s:div id="targetDivId"
						theme="ajax" listenTopics="updateTargetTopic"
						href="%{target_url}"  >




View this message in context:
Sent from the Struts - User mailing list archive at

To unsubscribe, e-mail:
For additional commands, e-mail:

View raw message