001: /* ***** BEGIN LICENSE BLOCK *****
002: * Version: MPL 1.1
003: * The contents of this file are subject to the Mozilla Public License Version
004: * 1.1 (the "License"); you may not use this file except in compliance with
005: * the License. You may obtain a copy of the License at
006: * http://www.mozilla.org/MPL/
007: *
008: * Software distributed under the License is distributed on an "AS IS" basis,
009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
010: * for the specific language governing rights and limitations under the
011: * License.
012: *
013: * The Original Code is Riot.
014: *
015: * The Initial Developer of the Original Code is
016: * Neteye GmbH.
017: * Portions created by the Initial Developer are Copyright (C) 2006
018: * the Initial Developer. All Rights Reserved.
019: *
020: * Contributor(s):
021: * Felix Gnass [fgnass at neteye dot de]
022: *
023: * ***** END LICENSE BLOCK ***** */
024: package org.riotfamily.forms.element;
025:
026: import java.beans.PropertyEditor;
027: import java.io.PrintWriter;
028: import java.util.ArrayList;
029: import java.util.Iterator;
030: import java.util.List;
031:
032: import org.riotfamily.common.markup.Html;
033: import org.riotfamily.common.markup.TagWriter;
034: import org.riotfamily.common.util.PasswordGenerator;
035: import org.riotfamily.forms.AbstractEditorBase;
036: import org.riotfamily.forms.AbstractElement;
037: import org.riotfamily.forms.Editor;
038: import org.riotfamily.forms.ErrorUtils;
039: import org.riotfamily.forms.event.ChangeEvent;
040: import org.riotfamily.forms.event.ChangeListener;
041: import org.riotfamily.forms.event.JavaScriptEvent;
042: import org.riotfamily.forms.event.JavaScriptEventAdapter;
043: import org.riotfamily.forms.request.FormRequest;
044: import org.springframework.util.Assert;
045: import org.springframework.util.ObjectUtils;
046: import org.springframework.util.StringUtils;
047:
048: /**
049: * Abstract base class for elements that handle textual input from a single HTTP
050: * parameter. Optionally a <code>PropertyEditor</code> can be set to convert
051: * the text into an arbitrary object.
052: *
053: * @see org.riotfamily.forms.EditorBinder#bind(Editor, String)
054: */
055: public abstract class AbstractTextElement extends AbstractEditorBase
056: implements Editor, JavaScriptEventAdapter {
057:
058: private String type = "text";
059:
060: private Integer maxLength;
061:
062: private boolean trim = true;
063:
064: private boolean randomParamName;
065:
066: private List listeners;
067:
068: private String text;
069:
070: private Object value;
071:
072: private PropertyEditor propertyEditor;
073:
074: private boolean validateOnChange = false;
075:
076: public AbstractTextElement() {
077: }
078:
079: public AbstractTextElement(String type) {
080: this .type = type;
081: }
082:
083: public String getType() {
084: return type;
085: }
086:
087: public void setType(String type) {
088: this .type = type;
089: }
090:
091: /**
092: * Overrides {@link AbstractElement#getStyleClass()} and returns the
093: * element's type if no custom style has been set.
094: *
095: * @see #getType()
096: */
097: public String getStyleClass() {
098: String styleClass = super .getStyleClass();
099: return styleClass != null ? styleClass : type;
100: }
101:
102: public Integer getMaxLength() {
103: return this .maxLength;
104: }
105:
106: /**
107: * Sets the maximum string length.
108: */
109: public void setMaxLength(Integer maxLength) {
110: this .maxLength = maxLength;
111: }
112:
113: /**
114: * Sets whether the user input should be trimmed.
115: */
116: public void setTrim(boolean trim) {
117: this .trim = trim;
118: }
119:
120: /**
121: * Sets whether a random (nonce) word should be used as parameter name
122: * to disable the browser's autocompletion feature.
123: */
124: public void setRandomParamName(boolean randomParamName) {
125: //REVIST Consider using setAttribute("autocomlete", "off") instead
126: this .randomParamName = randomParamName;
127: }
128:
129: /**
130: * Sets whether the element should be validated as soon as a change event
131: * is received.
132: */
133: public void setValidateOnChange(boolean validateOnChange) {
134: this .validateOnChange = validateOnChange;
135: }
136:
137: public final void setPropertyEditor(PropertyEditor propertyEditor) {
138: this .propertyEditor = propertyEditor;
139: }
140:
141: protected final PropertyEditor getPropertyEditor() {
142: return propertyEditor;
143: }
144:
145: protected void initPropertyEditor() {
146: if (propertyEditor == null) {
147: this .propertyEditor = getEditorBinding()
148: .getPropertyEditor();
149: }
150: }
151:
152: protected void afterBindingSet() {
153: initPropertyEditor();
154: }
155:
156: public final String getText() {
157: return text;
158: }
159:
160: /**
161: * Sets the element's text value. If {@link #setTrim(boolean)} is set to
162: * <code>true</code>, leading and trailing whitespaces are stripped.
163: */
164: public void setText(String text) {
165: if (trim && text != null) {
166: this .text = text.trim();
167: } else {
168: this .text = text;
169: }
170: }
171:
172: public final Object getValue() {
173: return value;
174: }
175:
176: public void setValue(Object value) {
177: this .value = value;
178: setTextFromValue();
179: }
180:
181: protected void setTextFromValue() {
182: if (value == null) {
183: text = null;
184: } else if (value instanceof String) {
185: text = (String) value;
186: } else {
187: if (propertyEditor == null) {
188: initPropertyEditor();
189: Assert.notNull(propertyEditor,
190: "Can't handle value of type "
191: + value.getClass().getName()
192: + " - no PropertyEditor " + "present");
193: }
194: propertyEditor.setValue(value);
195: text = propertyEditor.getAsText();
196: }
197: }
198:
199: public void processRequest(FormRequest request) {
200: text = request.getParameter(getParamName());
201: validate();
202: if (!ErrorUtils.hasErrors(this )) {
203: setValueFromText();
204: }
205: }
206:
207: public void handleJavaScriptEvent(JavaScriptEvent event) {
208: if (event.getType() == JavaScriptEvent.ON_CHANGE) {
209: text = event.getValue();
210: ErrorUtils.removeErrors(this );
211: validateSyntax();
212: if (!ErrorUtils.hasErrors(this )) {
213: setValueFromText();
214: }
215: }
216: }
217:
218: protected void setValueFromText() {
219: Object oldValue = value;
220: if (!StringUtils.hasText(text)) {
221: value = null;
222: } else {
223: if (propertyEditor != null) {
224: propertyEditor.setAsText(text);
225: value = propertyEditor.getValue();
226: } else {
227: value = text;
228: }
229: }
230: if (!ObjectUtils.nullSafeEquals(value, oldValue)) {
231: fireChangeEvent(value, oldValue);
232: }
233: }
234:
235: protected void validate() {
236: if (isRequired() && !StringUtils.hasLength(text)) {
237: ErrorUtils.reject(this , "required");
238: }
239: validateSyntax();
240: }
241:
242: protected void validateSyntax() {
243: }
244:
245: protected String getDesiredParamName() {
246: if (randomParamName) {
247: return PasswordGenerator.getDefaultInstance().generate();
248: }
249: return super .getDesiredParamName();
250: }
251:
252: public void renderInternal(PrintWriter writer) {
253: TagWriter input = new TagWriter(writer);
254: input.startEmpty(Html.INPUT).attribute(Html.INPUT_TYPE,
255: getType())
256: .attribute(Html.COMMON_CLASS, getStyleClass())
257: .attribute(Html.COMMON_ID, getId()).attribute(
258: Html.INPUT_NAME, getParamName()).attribute(
259: Html.INPUT_VALUE, getText());
260: if (getMaxLength() != null) {
261: input.attribute(Html.INPUT_MAX_LENGTH, getMaxLength()
262: .intValue());
263: }
264: input.end();
265: }
266:
267: public void addChangeListener(ChangeListener listener) {
268: if (listeners == null) {
269: listeners = new ArrayList();
270: }
271: listeners.add(listener);
272: }
273:
274: protected void fireChangeEvent(Object newValue, Object oldValue) {
275: if (listeners != null) {
276: ChangeEvent event = new ChangeEvent(this , newValue,
277: oldValue);
278: Iterator it = listeners.iterator();
279: while (it.hasNext()) {
280: ChangeListener listener = (ChangeListener) it.next();
281: listener.valueChanged(event);
282: }
283: }
284: }
285:
286: public int getEventTypes() {
287: if (validateOnChange || listeners != null) {
288: return JavaScriptEvent.ON_CHANGE;
289: }
290: return JavaScriptEvent.NONE;
291: }
292:
293: }
|