001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.web.forms; 007 008import javax.servlet.*; 009import javax.servlet.http.*; 010import java.io.*; 011import java.util.*; 012import java.util.regex.*; 013import java.sql.*; 014 015import fc.jdbc.*; 016import fc.io.*; 017import fc.util.*; 018 019/** 020A dependent HTML DependentSelect field. The values of this field change based 021on the chosen value of some other field in the form. These new values are 022calculated/set on the server side. 023<p> 024Another good (and equally valid) approach is to instead use javascript arrays 025(with different sets of values) and swap out values on the client and/or 026use AJAX on the client side. 027 028@author hursh jain 029**/ 030public final class DependentSelect extends DependentField 031{ 032public static class Data 033 { 034 //---------- Select Data ------------- 035 private LinkedHashMap options = new LinkedHashMap(); 036 //the initially selected options when form was constructed 037 private Map origSelectedMap = new HashMap(); 038 private int size = 0; 039 private boolean multiple; 040 //---------- Client Submit Data ------- 041 //[option1.getValue() -> option1] ... 042 private Map selectedMap = null; //created if there is data 043 //[option1, option2....] 044 private List selectedList = null; //created if there is data 045 //------------------------------------- 046 /** 047 Convienence method for use by the {@link Dependency#getInitialValues} 048 method. 049 */ 050 public void addOption(Select.Option item) { 051 String itemval = item.getValue(); 052 options.put(itemval, item); 053 if (item.isOrigSelected()) { 054 origSelectedMap.put(itemval, item); 055 } 056 } 057 058 public void clearSubmittedData() { 059 if (selectedMap != null) 060 selectedMap.clear(); 061 if (selectedList != null) 062 selectedList.clear(); 063 } 064 065 public String toString() { 066 return new ToString(this, ToString.Style.VisibleLevel.PRIVATE) 067 .reflect().render(); 068 } 069 } 070 071/** 072Creates a new select element with no initial values. Multiple 073selections are not initially allowed. 074 075@param name the field name 076**/ 077public DependentSelect(String name) 078 { 079 super(name); 080 } 081 082public Field.Type getType() { 083 return Field.Type.SELECT; 084 } 085 086/** 087Returns a List containing the selected options. Each object in 088the collection will be of type {@link Select.Option}. If there 089are no selected options, the returned list will be an empty list. 090 091@param fd the submited form data 092**/ 093public List getValue(FormData fd) 094 { 095 Data data = (Data) fd.getData(name); 096 if (data == null || data.selectedList == null) { 097 return Form.empty_list; 098 } 099 return data.selectedList; 100 } 101 102/** 103Convenience method that returns the selected option as a String. <u>No 104guarantees are made as to which selection is returned when more than one 105selection is selected (this method is really meant for when the select only 106allows single selections as a dropdown).</u> 107<p> 108The returned value is of type String obtained by called the selected 109Select.Option's {@link Select.Option#getValue} method. 110<p> 111If there is no selection, returns <tt>null</tt> 112**/ 113public String getStringValue(FormData fd) 114 { 115 Data data = (Data) fd.getData(name); 116 if (data == null) { 117 return null; 118 } 119 120 List list = data.selectedList; 121 if (list == null || list.size() == 0) { 122 return null; 123 } 124 else { 125 Select.Option opt = (Select.Option) list.get(0); 126 return opt.getValue(); 127 } 128 } 129 130 131/** 132Convenience method that returns the single value of this field 133as a Integer. 134<p> 135All the caveats of {@link #getSingleValueAsString()} apply. 136 137@throws NumberFormatException if the value could not be 138 returned as in integer. 139*/ 140public int getIntValue(FormData fd) { 141 return Integer.parseInt(getStringValue(fd)); 142 } 143 144/** 145Convenience method that returns the single value of this field 146as a boolean. 147<p> 148All the caveats of {@link #getSingleValueAsString()} apply. 149In particular, the formdata should contain non-null data 150with at least one selection. 151*/ 152public boolean getBooleanValue(FormData fd) { 153 return Boolean.valueOf(getStringValue(fd)).booleanValue(); 154 } 155 156/** 157Returns <tt>true</tt> if some option has been selected by the user, 158<tt>false</tt> otherwise. (also returns <tt>false</tt> is the specified 159form data is <tt>null</tt>). 160*/ 161public boolean isFilled(FormData fd) 162 { 163 if (fd == null) 164 return false; 165 166 Data data = (Data) fd.getData(name); 167 168 if (data == null) 169 return false; 170 171 List list = data.selectedList; 172 return (list != null && list.size() != 0); 173 } 174 175/** 176@see Select#setValueFromSubmit 177*/ 178public void setValueFromSubmit(FormData fd, HttpServletRequest req) 179 { 180 dependency.setDependencyDataFromSubmit(fd, req); 181 182 //value(s) associated with the selection field name 183 String[] values = req.getParameterValues(name); 184 185 Data data = new Data(); 186 fd.putData(name, data); 187 188 if (values == null) { 189 return; 190 } 191 192 data.selectedMap = new HashMap(); 193 data.selectedList = new ArrayList(); 194 195 for (int n = 0; n < values.length; n++) { 196 Select.Option opt = new Select.Option(values[n]); 197 data.selectedMap.put(opt.getValue(), opt); 198 data.selectedList.add(opt); 199 } 200 } 201 202public void renderImpl(FormData fd, Writer writer) 203throws SQLException, IOException 204 { 205 Data data = null; 206 207 if (fd == null) { 208 data = (Data) dependency.getInitialValues(this); 209 } 210 else{ 211 //Case 1. Form submitted 212 // dependency updates the data object for this field in the formdata. 213 // _after_ the form is submitted. The dependency will set render data 214 // in a non-null data object (creating it if necessary). 215 // (even though the submitted data parts in the data object can be null) 216 // 217 //Case 2. Form not submitted but FormData created for initial values. 218 // The dependency will not be invoked by the form handling 219 // mechanism so we still need to get initial values manually. 220 // 221 data = (Data) fd.getData(name); 222 if (data == null) { 223 data = (Data) dependency.getInitialValues(this); 224 } 225 } 226 227 Argcheck.notnull(data, "Internal error: unexpected state, data was null"); 228 229 writer.write("<select"); 230 writer.write(" name='"); 231 writer.write(name); 232 writer.write("'"); 233 234 if (data.size > 0) { 235 writer.write(" size='"); 236 writer.write(String.valueOf(data.size)); 237 writer.write("'"); 238 } 239 240 if (data.multiple) { 241 writer.write(" multiple"); 242 } 243 244 if (! enabled || ! isEnabled(fd)) { 245 writer.write(" disabled"); 246 } 247 248 if (renderStyleTag) { 249 writer.write(" style='"); 250 writer.write(styleTag); 251 writer.write("'"); 252 } 253 254 final int arlen = arbitraryString.size(); 255 for (int n = 0; n < arlen; n++) { 256 writer.write(" "); 257 writer.write(arbitraryString.get(n).toString()); 258 } 259 260 writer.write(">\n"); 261 262 Iterator it = data.options.values().iterator(); 263 while (it.hasNext()) 264 { 265 Select.Option item = (Select.Option) it.next(); 266 String itemval = item.getValue(); 267 268 boolean selected = false; 269 270 if (fd != null) /* maintain submit state */ 271 { 272 if (data.selectedMap != null){ 273 selected = data.selectedMap.containsKey(itemval); 274 } 275 else { 276 //form submitted but no submit data for this field 277 //hence selecteMap == null 278 } 279 } 280 else{ /* form not submitted, show original state */ 281 selected = data.origSelectedMap.containsKey(itemval); 282 } 283 284 writer.write(item.render(selected)); 285 writer.write("\n"); //sufficient for easy view source in browser 286 } 287 288 writer.write("</select>\n"); 289 290 dependency.renderDependencyData(fd, writer); 291 } 292 293/** 294<tt>true</tt> if multiple selections are allowed, <tt>false</tt> otherwise. 295This value (if set) is rendered as the html <tt>MULTIPLE</tt> tag. 296 297@return this object for method chaining convenience 298**/ 299public DependentSelect allowMultiple(FormData fd, boolean allow) 300 { 301 Data d = getDataObject(fd); 302 d.multiple = allow; 303 return this; 304 } 305 306/** 307This value (if set) is rendered as the html <tt>SIZE</tt> tag. 308If the list contains more options than specified by size, the browser 309will display the selection list with scrollbars. 310 311@return this object for method chaining convenience 312**/ 313public DependentSelect setSize(FormData fd, int size) 314 { 315 Data d = getDataObject(fd); 316 d.size = size; 317 return this; 318 } 319 320/** 321Sets the selected values for this select in the specified form data. This 322is useful for showing different <i>initial</i> values to each user 323(before the form has been submitted by that user). The values are set in 324a newly created {@link DependentSelect.Data} object which is then stored 325inside the form data. Since these are set in the FormData object, these 326values are request specific and can differ per user. 327<p> 328If the form has not been submitted, there is no form data object. A form 329data object should be manually created if needed for storing the value. 330 331@param data an existing form data object. 332@param values a list of {@link Select.Option} objects 333*/ 334public void setValue(FormData fd, List values) 335 { 336 Argcheck.notnull(fd, "specified fd param was null"); 337 338 DependentSelect.Data data = new DependentSelect.Data(); 339 fd.putData(name, data); 340 setValue(data, values); 341 } 342 343/** 344Convenience method to set values in the specified {@link 345DependentSelect.Data} object. 346The following two are equivalent: 347<blockquote> 348<pre> 349 List values ..... 350 DependentSelect.Data data = new DependentSelect.Data(); 351 fd.putData(name, data); 352 setValue(<i>data</i>, values); 353</blockquote> 354</pre> 355and 356<blockquote> 357<pre> 358 List values ..... 359 setValue(<i>fd</i>, values); 360</blockquote> 361</pre> 362@param data an existing DependentSelect.Data object. 363@param values a list of {@link Select.Option} objects 364*/ 365public void setValue(DependentSelect.Data data, List values) 366 { 367 Iterator it = values.iterator(); 368 while (it.hasNext()) { 369 Select.Option item = (Select.Option) it.next(); 370 String itemval = item.getValue(); 371 data.options.put(itemval, item); 372 if (item.isOrigSelected()) { 373 data.origSelectedMap.put(itemval, item); 374 } 375 } 376 } 377 378/** 379Returns the data object for this field from the formdata. returns 380the initial data (if applicable) if the form is being rendered for 381the first time or if no dependent data has been created. 382*/ 383Data getDataObject(FormData fd) 384 { 385 Data d = (Data) fd.getData(name); 386 return d; 387 } 388 389} //~class DependentSelect