001: package simpleorm.simplewebapp.core;
002:
003: import java.text.ParseException;
004: import java.util.List;
005: import java.util.ArrayList;
006:
007: /**
008: * The data field properties.
009: * Each instance represents one crud or list field in a form.
010: * Subtypes implement different data types and validation, eg. WFieldString.
011: * <p/>
012: * This class stores the rawText entered by the user
013: * (normally via the HttpRequest.parameter).<p>
014: *
015: * When this is Validated it is Parsed to produce a typed value.
016: * If the parse fails then the rawText value is retained so that
017: * the user can edit it and re-submit the form.<p>
018: *
019: * Note that if it does parse, then this.text is nulled, so that
020: * this.value is the source of truth.<p>
021: *
022: * getText returns either the non-null thus non-validated text, or a
023: * formatted version of getValue.<p>
024: *
025: * It is tempting to split out the different widgets (vs data type) into a separate
026: * delegated hierarchy. But that is not necessary, the jstl .tags render them
027: * and the few special properties required do NOT warant that complexity.
028: * Just merge per widget properties in WField, maybe add a Properties later.<p>
029: *
030: * It is also tempting to split out the data source into a separate heirarchy, so
031: * that tight integration with an ORM can be achieved.
032: * However, that again is unnecessary as it is far better to achieve the integration
033: * with a simple generate approach rather than direct references involving complex
034: * inheritance rules.<p>
035: *
036: * It took quite a while to get this class simple!<p>
037: *
038: */
039: public abstract class WField extends WFieldNode {
040:
041: WFieldGroup group; // immutable. Could pull up to WFieldNode later.
042:
043: /**
044: * Used by auto List to access the columns/data field, defaults to name.
045: * todo this usage conflicts with dataField, only one of these should be used.
046: */
047: String dataName;
048:
049: /**
050: * Reference to a field in an external ORM etc.
051: * Not directly used by SimpleWebApp.
052: */
053: Object dataField;
054:
055: /**
056: * The raw value retrieved from HtmlRequest.
057: * This is what is referenced by the .jsps.
058: * See class docs.
059: */
060: protected String rawText;
061:
062: /**
063: * The parsed and processed value, eg. an Integer.
064: * Used by code, but Not used by the .jsps, see class docs.
065: */
066: protected Object value;
067:
068: /** The type of widget/control. */
069: String widget = "text"; // todo generalize this
070: public static final String TEXT = "text";
071: public static final String PASSWORD = "password";
072: public static final String HIDDEN = "hidden";
073: public static final String CHECKBOX = "checkbox";
074: public static final String RADIO = "radio";
075: public static final String RADIO_VERTICAL = "radiovertical";
076: public static final String SELECT = "select";
077: public static final String IMAGE = "image";
078: public static final String FILE = "file";
079:
080: /**
081: * For Select/Option, radio buttons etc.
082: */
083: List options = new ArrayList(5);
084:
085: /** Default display length, in chars, mainly for input boxes. */
086: int displayLength = 30;
087: /** Enforced max length of the raw text, in 16 bit UTF char lengths. -1 means no limit.*/
088: int maxLength = -1;
089: /** End user must not leave the field null **/
090: boolean required = false;
091: /** Cannot be changed by the end user.
092: * Normally rendered as a Hidden <Input> with
093: * the actual value shown in a <span>
094: * Mainly for database keys.
095: */
096: boolean readOnly = false;
097:
098: /**
099: * Can be set to indicate that a parameter should not be retrieved
100: * by WPage.retrieveParameters.
101: */
102: boolean notRetrieved = false;
103:
104: /** If non-null, wrap the (list) field with an anchor with HRef= this. */
105: String anchorHRef;
106:
107: /**
108: * Arbitrary extra properties
109: * Not used directly by SimpleWebApp.
110: */
111: Object extraProperties;
112:
113: public WField(String shortName) {
114: this .shortName = shortName;
115: }
116:
117: public WField(String shortName, String widget) {
118: this .shortName = shortName;
119: this .widget = widget;
120: }
121:
122: /**
123: * Name used to idenify the column in a data source.
124: * Defaults to name if null.
125: */
126: public String getDataName() {
127: return dataName != null ? dataName : shortName;
128: }
129:
130: /**
131: * Sets the initial value of the raw text field,
132: * typically directly from a HttpRequest.parameter.<p>
133: *
134: * Called by the framework or test cases between
135: * doInitialize and doProcess, the latter which then
136: * parses it to a getValue.<p>
137: *
138: * Also trims spaces and converts "" to null.
139: */
140: public WField setPostedText(String text) {
141: if (text != null) {
142: String val = text.trim(); // todo should really only trim trailing spaces
143: if (val.length() == 0)
144: val = null;
145: this .rawText = val;
146: } else
147: this .rawText = null;
148: return this ;
149: }
150:
151: /**
152: * Called by doProcess to validates the field and parses it into the value. <p>
153: * <p/>
154: * A range of common formatting exceptions are converted to WValidationExceptions,
155: * but as Java is totally inconsistent this is not clean.
156: * But common real errors such as NullPointerException will not be converted.
157: * (It is not fatal if some non-validation exceptions are trapped, the form
158: * will be rejected either way. But we do not want to make implementors
159: * of WData to be dependent on SimpleWebApp.)
160: *
161: * @throws simpleorm.simplewebapp.core.WValidationException to report user errors
162: */
163: void validateField() throws Exception {
164: if (getPagelet().wasSubmitted())
165: checkRequired();
166: if (maxLength >= 0 && rawText != null
167: && rawText.length() > maxLength) // todo add maxlength= to <input tag
168: throw new WValidationException("InputTooLong", this )
169: .setParameter(maxLength + "");
170: try {
171: parse(rawText);
172: rawText = null; // no exception was thrown
173: if (getValue() != null)
174: onValidateNotNull();
175: onValidateAlways();
176: } catch (NumberFormatException ex) {
177: throw new WValidationException("NumberFormat", this );
178: } catch (ParseException ex) {
179: throw new WValidationException("ParseException", this );
180: }
181: }
182:
183: /**
184: * Override this method to add extra validation.
185: * Called after required and data checks but before
186: * all other fields have been validated. <p>
187: * <p/>
188: * Note that this is ALWAYS called even if the form was not
189: * submitted, but is ONLY called if the field is not NULL.
190: */
191: protected void onValidateNotNull() throws Exception {
192: }
193:
194: /**
195: * Always called regardless of whether form wasSubmitted() or value is null.
196: * Called after onValidateNotNull.
197: */
198: protected void onValidateAlways() throws Exception {
199: }
200:
201: /**
202: * Convert the value to a string.<p>
203: *
204: * The is called by getText to set the INPUT value= etc. so
205: * it should return a value that can be parsed in a subsequent
206: * post.
207: */
208: abstract protected String format();
209:
210: /**
211: * Parse text and store it as the value.
212: * Should be fairly forgiving in what it accepts, and should parse
213: * whatever format() produces.
214: */
215: abstract protected void parse(String rawText) throws Exception;
216:
217: /**
218: * Set the value, must be the correct type,
219: * eg. String for WFieldString, Integer for WFieldInteger.
220: */
221: public WField setValue(Object value) {
222: if (value != null && !getValueClass().isInstance(value))
223: throw new WException("Wrong class setValueing " + this
224: + " to " + value + " "
225: + value.getClass().getSimpleName());
226: this .value = value;
227: return this ;
228: }
229:
230: /** Just casts value to correct type.
231: * Nulls returned as nulls, including booleans. */
232: public Object getValue() {
233: return value;
234: }
235:
236: public <T extends Object> T getValue(T defalt) {
237: return value != null ? (T) value : defalt;
238: }
239:
240: /**
241: * Return the text representation of this field.
242: * Normally just format(), but if the
243: * field failed to validate then returns the raw text
244: * actually entered by the user.<p>
245: *
246: * This is what is used to set values of <INPUTS> etc.
247: * See class docs.<p>
248: *
249: * (Not cached, may involve Integer.toString etc.)<p>
250: */
251: public String getText() {
252: String txt;
253: if (rawText == null)
254: txt = format();
255: else
256: txt = rawText;
257: return txt;
258: }
259:
260: /**
261: * Interpret the field as a boolean, mainly for checkbox widgets.
262: * Implemented for Strings, Integers etc. as well as Booleans
263: * because databases do not support booleans cleanly.
264: */
265: public boolean getBooleanValue(boolean defalt) {
266: throw new RuntimeException("Not Implemented for " + this );
267: }
268:
269: public boolean getBooleanValue() {
270: return getBooleanValue(false);
271: }
272:
273: void checkRequired() {
274: if (required && rawText == null)
275: throw new WValidationException("RequiredField", this );
276: }
277:
278: /** Returns the class for which the value must be a subclass.<p>
279: *
280: * Also used to find the set*() method of the bean by WBeanUtils.
281: */
282: abstract public Class getValueClass();
283:
284: public String toString() {
285: return "{" + this .getClass().getSimpleName() + " "
286: + pagelet.name + "." + shortName + " = " + getText()
287: + "}";
288: }
289:
290: /////////////// generated (+ return this) ///////////////////
291:
292: public WField setDataName(String dataName) {
293: this .dataName = dataName;
294: return this ;
295: }
296:
297: /**
298: * The widget, eg radiobutton, text, select...
299: */
300: public String getWidget() {
301: return widget;
302: }
303:
304: public void setWidget(String widget) {
305: this .widget = widget;
306: }
307:
308: public boolean isRequired() {
309: return required;
310: }
311:
312: public <F extends WField> F setRequired(boolean required) {
313: this .required = required;
314: return (F) this ;
315: }
316:
317: public boolean isReadOnly() {
318: return readOnly;
319: }
320:
321: public <F extends WField> F setReadOnly(boolean readOnly) {
322: this .readOnly = readOnly;
323: return (F) this ;
324: }
325:
326: public List getOptions() {
327: return options;
328: }
329:
330: public void setOptions(List options) {
331: this .options = options;
332: }
333:
334: public int getDisplayLength() {
335: return displayLength;
336: }
337:
338: public <F extends WField> F setDisplayLength(int displayLength) {
339: this .displayLength = displayLength;
340: return (F) this ;
341: }
342:
343: public int getMaxLength() {
344: return maxLength;
345: }
346:
347: public <F extends WField> F setMaxLength(int maxLength) {
348: this .maxLength = maxLength;
349: return (F) this ;
350: }
351:
352: public Object getExtraProperties() {
353: return extraProperties;
354: }
355:
356: public String getAnchorHRef() {
357: return anchorHRef;
358: }
359:
360: public void setAnchorHRef(String anchorHRef) {
361: this .anchorHRef = anchorHRef;
362: }
363:
364: public WField setExtraProperties(Object extraProperties) {
365: this .extraProperties = extraProperties;
366: return this ;
367: }
368:
369: public Object getDataField() {
370: return dataField;
371: }
372:
373: public WField setDataField(Object dataField) {
374: this .dataField = dataField;
375: return this ;
376: }
377:
378: public boolean isNotRetrieved() {
379: return notRetrieved;
380: }
381:
382: public WField setNotRetrieved(boolean notRetrieved) {
383: this.notRetrieved = notRetrieved;
384: return this;
385: }
386: }
|