<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
	<channel>
		<title><![CDATA[Latest posts for the topic "Dynamic/Ajax Combos with mtw:select tag"]]></title>
		<link>http://recipes.mentaframework.org/posts/list/2.page</link>
		<description><![CDATA[Latest messages posted in the topic "Dynamic/Ajax Combos with mtw:select tag"]]></description>
		<generator>JForum - http://www.jforum.net</generator>
			<item>
				<title>Dynamic/Ajax Combos with mtw:select tag</title>
				<description><![CDATA[ One recurrent problem is when you want to display a dynamic list, in other words, a list that depends on other field. One standard example is State and City where each State has its own list of Cities.<br /> <br /> One possible approach to this problem is to add all 50 lists to [i]ListManager[/i], but that would not be a good solution because you would have to have a bunch of IFs in your JSP, like below:<br /> [code]<br /> &lt;mtw:if test="state" value="NY"&gt;<br /> &lt;tr&gt;<br /> &lt;td&gt;Cities&lt;/td&gt;&lt;td&gt;&lt;mtw:select name="city" list="cities_ny" emptyField="true" /&gt;&lt;/td&gt;<br /> &lt;/tr&gt;<br /> &lt;/mtw:if&gt;<br /> &lt;mtw:if test="state" value="CA"&gt;<br /> &lt;tr&gt;<br /> &lt;td&gt;Cities&lt;/td&gt;&lt;td&gt;&lt;mtw:select name="city" list="cities_ca" emptyField="true" /&gt;&lt;/td&gt;<br /> &lt;/tr&gt;<br /> &lt;/mtw:if&gt;<br /> &lt;mtw:if test="state" value="FL"&gt;<br /> &lt;tr&gt;<br /> &lt;td&gt;Cities&lt;/td&gt;&lt;td&gt;&lt;mtw:select name="city" list="cities_fl" emptyField="true" /&gt;&lt;/td&gt;<br /> &lt;/tr&gt;<br /> &lt;/mtw:if&gt;<br /> [/code]<br /> This is awkward and will introduce other problems. For example when we first show the page and no state is selected, we must show an empty list.<br /> <br /> Luckily there is a much better solution by using a Mentawai filter to create a dynamic list [i]on the fly[/i] and place it in the action output. Remember that the [i]mtw:select tag[/i] is smart enough to pick up lists straight from the action output, if it finds one.<br /> <br /> Let's define the problem before we dive into the code. Below are the situations where we need to take action:<br /> <br />     1) Empty form is being displayed to insert a new bean =&gt; List should be empty<br /> <br />     2) Form is being redisplayed probably because of a validation error =&gt; List should be the same one when the used submitted the form, in other words, it should not lose its state when a validation error occurs.<br /> <br />     3) Form is being used to EDIT a bean, in other words, a bean object was placed in the action output and a forward was performed to the JSP with the form =&gt; List should display the appropriate list corresponding to the bean State.<br /> <br />     4) The user selected another State =&gt; List should be changed through Ajax (out of the scope of this recipe)<br /> <br /> Here are the conditions for 1), 2) and 3):<br /> <br />     1) Nothing is in the action input and nothing is in the action output =&gt; Form is being used to insert a bean and an empty list should be displayed.<br /> <br />     2) The current State id is in the action input =&gt; A validation error occurred and you should use the State id to show the appropriate City list.<br /> <br />     3) There is an object in the action output with a State id property =&gt; The object (bean) is being edited, so show the appropriate City list corresponding to this bean State id.<br /> <br /> Below is a simple filter that I coded to address all these scenarios:<br /> [code]<br /> public class CityListFilter implements Filter {<br /> 	<br /> 	private Map&lt;Integer, String&gt; getMap(int state_id) {<br /> 		<br /> 		Map&lt;Integer, String&gt; list = new LinkedHashMap&lt;Integer, String&gt;();<br /> 		<br /> 		List&lt;City&gt; cities = State.getCities(state_id);<br /> 		<br /> 		if (cities != null) {<br /> 			<br /> 			list.put(0, "-- Select a City --");<br /> 			<br /> 			for(City c : cities) {<br /> 				<br /> 				list.put(c.getId(), c.getName());<br /> 			}<br /> 			<br /> 		} else {<br /> 			<br /> 			list.put(0, "-- Select a State --");<br /> 		}<br /> 		<br /> 		return list;<br /> 	}<br /> 	<br /> 	public String filter(InvocationChain chain) throws Exception {<br /> 		<br /> 		Action action = chain.getAction();<br /> 		<br /> 		Input input = action.getInput();<br /> 		<br /> 		Output output = action.getOutput();<br /> 		<br /> 		int state_id = input.getIntValue("state");<br /> 		<br /> 		if (state_id &gt; 0) {<br />             <br />             // 2) VALIDATION ERROR<br /> 			<br /> 			output.setValue("cities", getMap(state_id));<br /> 			<br /> 			return chain.invoke();<br /> 			<br /> 		} else {<br /> 			<br /> 			// 3) EDITING A BEAN<br /> 			<br />              // execute action first, so bean is placed in<br />              // action output for editing<br /> 			String res = chain.invoke();<br /> 			<br /> 			Object obj = output.getValue("user");<br /> 			<br /> 			if (obj instanceof User) {<br /> 				<br /> 				User user = (User) obj;<br /> 				<br /> 				state_id = user.getState();<br /> 				<br /> 				output.setValue("cities", getMap(state_id));<br /> 				<br /> 			} else {<br /> 				<br />                 // 1) EMPTY FORM FOR INSERTING BEAN<br />                 <br /> 				// just show an empty list...<br /> 				output.setValue("cities", getMap(-1));<br /> 			}<br /> 			<br /> 			return res;<br /> 		}<br /> 	}<br />     <br />     public void destroy() { }<br /> }<br /> [/code]<br /> So now you can use a single [i]mtw:select[/i] tag and your list will be filled appropriately according to the situation and the State id.<br /> [code]<br /> &lt;mtw:select name="city" list="cities" /&gt;<br /> [/code]<br /> <br /> One last catch when setting the filter in the application manager: This filter should  be executed *before* the [i]ValidatorFilter[/i] which is a [i]global filter[/i] that performs validation. That's because the [i]ValidatorFilter[/i] aborts the execution chain if it finds an error and as we know global filters are executed before any [i]action specific[/i] filter.<br /> <br /> Luckily there is an easy way to place an action specific filter before all global filters. Just use the [i]addFilterFirst()[/i] or [i]filterFirst()[/i] method from [i]ActionConfig[/i].<br /> [code]<br /> <br /> filter(new ValidatorFilter()); // global filter for validation<br /> <br /> action("/User", UserAction.class)<br />     .filterFirst(new CityListFilter())<br />     .on(SUCCESS, fwd("/show.jsp"))<br />     .on(ERROR, fwd("/error.jsp"));<br /> [/code]<br /> In a web application project, you will probably have to code a filter like that for every dynamic list. To make this job easier, Mentawai 1.14 includes the [i]org.mentawai.filter.DynamicListFilter[/i] that you can use to fast code these filters. See how we could have coded the filter above with this new abstract class:<br /> [code]<br /> public class CityListFilter extends DynamicListFilter {<br /> 	<br /> 	protected Map&lt;Integer, String&gt; getMap(int state_id) {<br /> 		<br /> 		Map&lt;Integer, String&gt; list = new LinkedHashMap&lt;Integer, String&gt;();<br /> 		<br /> 		List&lt;City&gt; cities = State.getCities(state_id);<br /> 		<br /> 		if (cities != null) {<br /> 			<br /> 			list.put(0, "-- Select a City --");<br /> 			<br /> 			for(City c : cities) {<br /> 				<br /> 				list.put(c.getId(), c.getName());<br /> 			}<br /> 			<br /> 		} else {<br /> 			<br /> 			list.put(0, "-- Select a State --");<br /> 		}<br /> 		<br /> 		return list;<br /> 	}<br />     <br />     protected String getParamName() {<br />         return "state";<br />     }<br />     <br />     protected String getListName() {<br />         return "cities";<br />     }<br />     <br />     protected String getBeanName() {<br />         return "user";<br />     }<br />     <br />     // not needed if == getParamName()<br />     protected String getBeanProperty() {<br />         return "state";<br />     }<br /> }<br /> [/code]<br /> ]]></description>
				<guid isPermaLink="true">http://recipes.mentaframework.org/posts/preList/59/62.page</guid>
				<link>http://recipes.mentaframework.org/posts/preList/59/62.page</link>
				<pubDate><![CDATA[Fri, 22 Aug 2008 11:41:41]]> GMT</pubDate>
				<author><![CDATA[ saoj]]></author>
			</item>
	</channel>
</rss>
