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.*;
012
013import fc.jdbc.*;
014import fc.io.*;
015import fc.util.*;
016
017
018/** 
019Represents a field that:
020<ol>
021<li>returns one (1) string as their value. Contrast this with
022selects/with/multiple and radiogroups that may return
023a String[].
024<li>
025In addition, the <i>name</i> of this field is always
026returned on a form submit by the browser (barring browser
027hacks etc). For example, if a text field is not filled out
028by the user, the browser still returns that text field (with
029no value). Contrast this with radio buttons that are not
030returned at all (not even their name) if the user does not
031select them.
032</ol>
033<p>Important note: the {@link trimValues(boolean)} method
034will by default trim whitespace from the beginning/end of
035user input. If the resulting string is empty (""), then 
036it will be treated as if the user had not entered anything.
037(which is typically the desired behavior).
038
039@author hursh jain
040**/
041public abstract class AbstractText extends Field
042{
043static class Data {
044  String   value;
045  }
046
047String  orig_value;
048boolean trimValues = true;
049
050AbstractText (String name) {
051  super(name);
052  }
053
054AbstractText (String name, String value) 
055  {
056  super(name);
057  if (value == null) 
058    {
059    log.warn("specified value was null, defaulting to \"\"");
060    value = "";
061    }
062  orig_value = value;
063  }
064
065/**
066Sets the value of the field from the submitted form data.
067
068<b>Note, by default, it is assumed that all abstract text fields have
069different names in the html form</b>. Mulitple fields with the same name
070are not supported. <u>If there is more than one field with the same name
071(such as multiple text fields with the same name), then only 1 value will
072be saved (and the rest will be ignored)</u> [This is one of - or perhaps
073the only - restriction of this form API; without it, things become too
074complex to manage].
075*/
076public void setValueFromSubmit(FormData fd, HttpServletRequest req) 
077throws SubmitHackedException
078  {
079  String value = req.getParameter(name);
080  
081  //can be null if not sent by client for some reason
082  //will be "" is not isFilled by the client since the servlet
083  //api, for the request params 'foo=&bar=, getParam("foo")
084  //returns an empty string (not null).
085  //  
086  if (value == null) {  
087    //Browsers return no value for disabled fields !!
088    if (! enabled || ! isEnabled(fd))
089      return;
090    //client was buggy or hacked
091    hacklert(req, "Did not find ["+name+"] field in the request (but expected to), defaulting to \"\"");
092    return;
093    }
094  
095  if (trimValues) {
096    value = value.trim();
097    }
098    
099  //servlet api returns an empty string for param 'foo=' [not null]
100  //we treat that as a null value, we also treat white space
101  //as null if trimming is on.
102  if (value.equals(""))  
103    return;        
104    
105  //we have a non-null value
106  AbstractText.Data data = new AbstractText.Data();
107  fd.putData(name, data);
108  data.value = value;
109  }
110
111/** 
112Sets the <b>initial</b> value of this text element to the specified string.
113If the specified value is <tt>null</tt>, then the initial value is set to
114"" (the empty string).
115**/
116public void setValue(String value) 
117  {
118  if (value == null) {
119    log.warn("specified value was null, defaulting to \"\"");
120    value = "";
121    }
122  this.orig_value = value;
123  } 
124
125/**
126Sets the selected values for this select in the specified form data. This
127is useful for showing different <i>initial</i> values to each user (before
128the form has been submitted by that user).
129<p>
130If the form has not been submitted, there is no form data object. A form
131data object should be manually created if needed for storing the value.
132
133@param  fd    the non-null form data used for rendering the form
134@param  value the value to be set
135*/
136public void setValue(FormData fd, String value) 
137  {
138  Argcheck.notnull(fd, "specified fd param was null");
139
140  if (value == null) {
141    log.warn("specified value was null, defaulting to \"\"");
142    value = "";
143    }
144  AbstractText.Data data = new AbstractText.Data();
145
146  fd.putData(name, data);
147  data.value = value;
148  } 
149
150
151/** 
152Returns a string representing the value of this field or <tt>null</tt> if
153there is no current value. The <i>names</i> of Form fields like
154<tt>text</tt> and <tt>textarea</tt> are always sent back by the browser
155if the field is enabled. However if the <i>values</i> are empty, then
156this method will return <tt>null</tt>. For example, if the browser
157returned <xmp>foo=&bar=baz</xmp>, then <tt>foo</tt> will have a
158<tt>null</tt> value (even though the <i>servlet api</i> returns an empty
159string).
160*/
161public String getValue(FormData fd) 
162  {
163  AbstractText.Data data = (AbstractText.Data) fd.getData(name);
164  
165  if (data == null)
166    return null;
167      
168  return data.value;
169  }
170
171
172/**
173Convenience method that returns the value of this field as a String.
174<tt>null</tt> values (ie which can happen when the field is disabled and
175no value sent by the browser) <i>are returned as an empty string.</i>
176
177@throws NumberFormatException if the value could not be
178                returned as in integer. 
179*/
180public String getStringValue(FormData fd)
181  {
182  String  s = getValue(fd);
183  if (s == null)
184    s = "";
185  return s;
186  }
187
188/**
189Convenience method that returns the value of this 
190field as a Integer. 
191
192@throws NumberFormatException if the value could not be
193                returned as an integer. 
194*/
195public int getIntValue(FormData fd) {
196  String value = getValue(fd);
197  if (value != null)
198    value = value.trim();
199  return Integer.parseInt(value);
200  }
201
202/**
203Convenience method that returns the value of this field as a
204Short.
205
206@throws NumberFormatException if the value could not be
207                returned as a short.  
208*/
209public short getShortValue(FormData fd) {
210  String value = getValue(fd);
211  if (value != null)
212    value = value.trim();
213  return Short.parseShort(value);
214  }
215
216/**
217Convenience method that returns the value of this field as a
218Float.
219
220@throws NumberFormatException if the value could not be
221                returned as a float.  
222*/
223public float getFloatValue(FormData fd) {
224  String value = getValue(fd);
225  if (value != null)
226    value = value.trim();
227  return Float.parseFloat(value);
228  }
229
230
231/**
232Convenience method that returns the value of this field as a
233Double.
234
235@throws NumberFormatException if the value could not be
236                returned as a double. 
237*/
238public double getDoubleValue(FormData fd) {
239  String value = getValue(fd);
240  if (value != null)
241    value = value.trim();
242  return Double.parseDouble(value);
243  }
244
245/**
246Convenience method that returns the value of this field as a boolean. The
247value is converted into a boolean as per the {@link
248Boolean.valueOf(String)} method.
249*/
250public boolean getBooleanValue(FormData fd) {
251  return Boolean.valueOf(getValue(fd)).booleanValue();
252  }
253
254/**
255The value to render this field with. Maintains state and returns 
256whatever the user typed in last if applicable. If the form has
257not been shown to the user or if this field is disabled at all, returns 
258the original value.
259*/
260String getRenderValue(FormData fd) 
261  {
262  if (! enabled) {
263    return orig_value;
264    }
265    
266  if (fd != null) 
267    {
268    AbstractText.Data data = (AbstractText.Data) fd.getData(name);  
269    if (data == null)
270      return "";
271    else  
272      return data.value;
273    }
274  else { //fd == null, no form data, showing form for first time
275    return orig_value;
276    }
277  }
278
279public boolean isFilled(FormData fd) 
280  {
281  AbstractText.Data data = (AbstractText.Data) fd.getData(name);
282  
283  //data is created only if the submitted value was not null. If
284  //data is not null, then there was a sumbitted value.
285  if (data == null)
286    return false;
287  
288  String value = data.value;
289  
290  if (value == null) {
291    log.error("Internal error: unexpected state");
292    }
293  
294  //since it's always at least some string -- possibly
295  //all spaces but it'll be non-null
296  return true;
297  }
298
299public void reset(FormData fd) {
300  AbstractText.Data data = (AbstractText.Data) fd.getData(name);
301  if (data != null)
302    data.value = orig_value;
303  }
304
305/**
306Trims leading and ending spaces from all entered values. 
307This is <tt>true</tt> by default. Specify <tt>false</tt> to
308turn this off.
309*/
310public void trimValues(boolean val)
311  {
312  trimValues = val;
313  }
314
315public String toString() 
316  {
317  return super.toString() + "; Orig. value: [" + orig_value + "]"; 
318  } 
319
320}