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;
025:
026: import java.io.PrintWriter;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Set;
035:
036: import javax.servlet.http.HttpServletRequest;
037:
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040: import org.riotfamily.common.markup.DocumentWriter;
041: import org.riotfamily.common.markup.Html;
042: import org.riotfamily.common.markup.TagWriter;
043: import org.riotfamily.forms.request.FormRequest;
044: import org.riotfamily.forms.request.HttpFormRequest;
045: import org.riotfamily.forms.resource.FormResource;
046: import org.riotfamily.forms.resource.LoadingCodeGenerator;
047: import org.riotfamily.forms.resource.ResourceElement;
048: import org.riotfamily.forms.resource.StylesheetResource;
049: import org.riotfamily.forms.support.MapOrBeanWrapper;
050: import org.springframework.util.Assert;
051: import org.springframework.validation.Validator;
052:
053: /**
054: * Serverside representation of a HTML form.
055: */
056: public class Form implements BeanEditor {
057:
058: private static final String DEFAULT_ID = "form";
059:
060: private static final String FORM_ATTR = "form";
061:
062: private static final String ELEMENT_CONTAINER_ATTR = "elements";
063:
064: private Log log = LogFactory.getLog(Form.class);
065:
066: private String id = DEFAULT_ID;
067:
068: /** Elements keyed by their id */
069: private Map elementMap = new HashMap();
070:
071: /** Counter to create unique ids */
072: private int idCount;
073:
074: /** Set of used parameter names */
075: private Set paramNames = new HashSet();
076:
077: /** EditorBinder to bind toplevel elements to properties */
078: private EditorBinder editorBinder;
079:
080: /** Set containing resources required by the form itself (not it's elements) */
081: private List globalResources = new ArrayList();
082:
083: private FormInitializer initializer;
084:
085: private List containers = new ArrayList();
086:
087: private Container elements;
088:
089: private FormContext formContext;
090:
091: private FormErrors errors;
092:
093: private Validator validator;
094:
095: private FormListener formListener;
096:
097: private boolean rendering;
098:
099: private boolean includeStylesheet;
100:
101: private String template = TemplateUtils.getTemplatePath(this );
102:
103: private Map renderModel = new HashMap();
104:
105: private String hint;
106:
107: public Form() {
108: setAttribute(FORM_ATTR, this );
109: elements = createContainer(ELEMENT_CONTAINER_ATTR);
110: }
111:
112: public Form(Class type) {
113: this ();
114: setBeanClass(type);
115: }
116:
117: /**
118: * @since 6.4
119: */
120: public Form(Object object) {
121: this ();
122: editorBinder = new EditorBinder(new MapOrBeanWrapper(object
123: .getClass()));
124: editorBinder.setBackingObject(object);
125: }
126:
127: public String getId() {
128: return this .id;
129: }
130:
131: public void setId(String id) {
132: this .id = id;
133: }
134:
135: public void setTemplate(String template) {
136: this .template = template;
137: }
138:
139: public Collection getRegisteredElements() {
140: return elementMap.values();
141: }
142:
143: public void setAttribute(String key, Object value) {
144: renderModel.put(key, value);
145: }
146:
147: public Object getAttribute(String key) {
148: return renderModel.get(key);
149: }
150:
151: public void setBeanClass(Class beanClass) {
152: Assert.notNull(beanClass, "The beanClass must not be null.");
153: editorBinder = new EditorBinder(new MapOrBeanWrapper(beanClass));
154: }
155:
156: public void setValue(Object backingObject) {
157: if (backingObject != null) {
158: editorBinder.setBackingObject(backingObject);
159: }
160: }
161:
162: public Object getValue() {
163: return editorBinder.getBackingObject();
164: }
165:
166: public boolean isNew() {
167: return !editorBinder.isEditingExistingBean();
168: }
169:
170: public EditorBinder getEditorBinder() {
171: return editorBinder;
172: }
173:
174: public Editor getEditor(String property) {
175: return editorBinder.getEditor(property);
176: }
177:
178: public void bind(Editor editor, String property) {
179: editorBinder.bind(editor, property);
180: }
181:
182: public void addElement(Element element) {
183: elements.addElement(element);
184: }
185:
186: public List getElements() {
187: return this .elements.getComponents();
188: }
189:
190: public String getHint() {
191: if (hint == null) {
192: hint = MessageUtils.getHint(this , editorBinder
193: .getBeanClass());
194: }
195: return hint;
196: }
197:
198: /**
199: * Convenience method to add and bind an element in a single step.
200: */
201: public void addElement(Editor element, String property) {
202: addElement(element);
203: bind(element, property);
204: }
205:
206: public Container createContainer(String name) {
207: Container container = new Container();
208: containers.add(container);
209: registerElement(container);
210: setAttribute(name, container);
211: return container;
212: }
213:
214: public void addResource(FormResource resource) {
215: globalResources.add(resource);
216: }
217:
218: protected List getResources() {
219: List resources = new ArrayList(globalResources);
220: Iterator it = getRegisteredElements().iterator();
221: while (it.hasNext()) {
222: Element element = (Element) it.next();
223: if (element instanceof ResourceElement) {
224: ResourceElement re = (ResourceElement) element;
225: FormResource res = re.getResource();
226: if (res != null) {
227: resources.add(res);
228: }
229: }
230: }
231: return resources;
232: }
233:
234: /**
235: * Creates and sets an id for the given element and puts it into the
236: * internal elementMap.
237: *
238: * @param element the element to register
239: */
240: public void registerElement(Element element) {
241: String id = createId();
242: element.setId(id);
243: element.setForm(this );
244: if (formContext != null) {
245: element.setFormContext(formContext);
246: }
247: elementMap.put(id, element);
248: }
249:
250: public void unregisterElement(Element element) {
251: elementMap.remove(element.getId());
252: }
253:
254: public String createId() {
255: return "e" + idCount++;
256: }
257:
258: /**
259: * Returns the previously registered element with the given id.
260: */
261: public Element getElementById(String id) {
262: return (Element) elementMap.get(id);
263: }
264:
265: /**
266: * Returns a String that can be used as parameter name for input elements.
267: * Subsequent calls will return different values to ensure uniqueness of
268: * parameter names within a form.
269: */
270: public String createUniqueParameterName() {
271: return createUniqueParameterName(null);
272: }
273:
274: /**
275: * Like {@link #createUniqueParameterName()}this method returns a String
276: * that can be used as parameter name. Since most modern browsers keep track
277: * of previously entered values (with the same parameter name) a desired
278: * name can be passed to this method. Typically an element will use the name
279: * of the property it is bound to as name. As this name might already be
280: * taken by another element (especially when lists of nested forms are used)
281: * this method will append an integer value to the given name if necessary.
282: */
283: public String createUniqueParameterName(String desiredName) {
284: if (desiredName == null) {
285: desiredName = "p" + paramNames.size();
286: }
287: String name = desiredName;
288: if (name.equalsIgnoreCase("target")) {
289: // Otherwise changing the target of the form would not work
290: name = "_target";
291: }
292: //TODO Assure uniqueness of synthetic names
293: if (paramNames.contains(name)) {
294: name = desiredName + paramNames.size();
295: }
296: paramNames.add(name);
297: return name;
298: }
299:
300: public void render(PrintWriter writer) {
301: rendering = true;
302:
303: formContext.setWriter(writer);
304: DocumentWriter doc = new DocumentWriter(writer);
305:
306: doc.start(Html.SCRIPT);
307: doc.attribute(Html.SCRIPT_SRC, formContext.getContextPath()
308: + formContext.getResourcePath()
309: + "riot-js/resources.js");
310:
311: doc.attribute(Html.SCRIPT_TYPE, "text/javascript");
312: doc.attribute(Html.SCRIPT_LANGUAGE, "JavaScript");
313: doc.end();
314:
315: doc.start(Html.SCRIPT);
316: doc.attribute(Html.SCRIPT_SRC, formContext.getContextPath()
317: + formContext.getResourcePath() + "form/hint.js");
318:
319: doc.attribute(Html.SCRIPT_TYPE, "text/javascript");
320: doc.attribute(Html.SCRIPT_LANGUAGE, "JavaScript");
321: doc.end();
322:
323: doc.start(Html.SCRIPT).body();
324: LoadingCodeGenerator.renderLoadingCode(getResources(), writer);
325: doc.end();
326:
327: formContext.getTemplateRenderer().render(template, renderModel,
328: writer);
329:
330: rendering = false;
331: }
332:
333: public boolean isRendering() {
334: return rendering;
335: }
336:
337: public void elementRendered(Element element) {
338: log.debug("Element rendered: " + element.getId());
339: if (getFormListener() != null) {
340: getFormListener().elementRendered(element);
341: }
342: if (rendering && element instanceof DHTMLElement) {
343: DHTMLElement dhtml = (DHTMLElement) element;
344: PrintWriter writer = formContext.getWriter();
345: TagWriter script = new TagWriter(writer);
346: String initScript = dhtml.getInitScript();
347: if (initScript != null) {
348: script.start(Html.SCRIPT);
349: script.attribute(Html.SCRIPT_LANGUAGE, "JavaScript");
350: script.attribute(Html.SCRIPT_TYPE, "text/javascript");
351: script.body().print("//").cData().println();
352: if (dhtml instanceof ResourceElement) {
353: ResourceElement resEle = (ResourceElement) dhtml;
354: FormResource res = resEle.getResource();
355: if (res != null) {
356: script.print("Resources.execWhenLoaded(['");
357: script.print(res.getUrl());
358: script.print("'], function() {");
359: script.print(initScript);
360: script.print("})");
361: } else {
362: script.print(initScript);
363: }
364: } else {
365: script.print(initScript);
366: }
367: script.print("//").end();
368: }
369: }
370: }
371:
372: public void setInitializer(FormInitializer initializer) {
373: this .initializer = initializer;
374: }
375:
376: public void setValidator(Validator validator) {
377: this .validator = validator;
378: }
379:
380: public void init() {
381: if (initializer != null) {
382: initializer.initForm(this );
383: }
384: editorBinder.initEditors();
385: if (includeStylesheet) {
386: addResource(new StylesheetResource("form/form.css"));
387: }
388: }
389:
390: public void processRequest(HttpServletRequest request) {
391: processRequest(new HttpFormRequest(request));
392: }
393:
394: public void processRequest(FormRequest request) {
395: errors = new FormErrors(this );
396: Iterator it = containers.iterator();
397: while (it.hasNext()) {
398: Container container = (Container) it.next();
399: container.processRequest(request);
400: }
401: if (validator != null) {
402: validator.validate(populateBackingObject(), errors);
403: }
404: }
405:
406: public void processExclusiveRequest(String elementId,
407: HttpServletRequest request) {
408:
409: processExclusiveRequest(elementId, new HttpFormRequest(request));
410: }
411:
412: public void processExclusiveRequest(String elementId,
413: FormRequest request) {
414: errors = new FormErrors(this );
415: Element element = getElementById(elementId);
416: element.processRequest(request);
417: }
418:
419: public void setFormListener(FormListener formListener) {
420: this .formListener = formListener;
421: }
422:
423: public FormListener getFormListener() {
424: return formListener;
425: }
426:
427: public void requestFocus(Element element) {
428: if (formListener != null) {
429: formListener.elementFocused(element);
430: }
431: }
432:
433: public Object getBackingObject() {
434: return editorBinder.getBackingObject();
435: }
436:
437: public Object populateBackingObject() {
438: return editorBinder.populateBackingObject();
439: }
440:
441: public FormContext getFormContext() {
442: return this .formContext;
443: }
444:
445: public void setFormContext(FormContext formContext) {
446: this .formContext = formContext;
447: this .errors = new FormErrors(this );
448: Iterator it = getRegisteredElements().iterator();
449: while (it.hasNext()) {
450: Element element = (Element) it.next();
451: element.setFormContext(formContext);
452: }
453: }
454:
455: public FormErrors getErrors() {
456: return errors;
457: }
458:
459: public boolean hasErrors() {
460: return errors.hasErrors();
461: }
462:
463: public Collection getOptionValues(Object model) {
464: Iterator it = formContext.getOptionValuesAdapters().iterator();
465: while (it.hasNext()) {
466: OptionValuesAdapter adapter = (OptionValuesAdapter) it
467: .next();
468: if (adapter.supports(model)) {
469: return adapter.getValues(model, this );
470: }
471: }
472: throw new IllegalStateException("No adapter registered for "
473: + model);
474: }
475: }
|