001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017: import org.wings.plaf.FormCG;
018:
019: import javax.swing.event.EventListenerList;
020: import java.awt.event.ActionEvent;
021: import java.awt.event.ActionListener;
022: import java.net.URL;
023: import java.util.*;
024:
025: /**
026: * Container in which you need to wrap HTML input fields (ie. <code>STextField</code>)
027: * to work correctly.
028: * <p/>
029: * The browser uses this object/tag to identify how (POST or GET) and where
030: * to send an request originating from any input inside this form.
031: * <p/>
032: * <b>Note:</b>Please be aware, that some components render differently if
033: * placed inside a <code>SForm</code>.
034: *
035: * @author <a href="mailto:armin.haaf@mercatis.de">Armin Haaf</a>
036: */
037: public class SForm extends SContainer implements LowLevelEventListener {
038: private final static Log log = LogFactory.getLog(SForm.class);
039:
040: /**
041: * Default Form encoding type. See {@link #setEncodingType(String)}.
042: */
043: public final static String ENC_TYPE_TEXT_PLAIN = "text/plain";
044: /**
045: * Multipart form encoding. Needed for file uploads. See {@link #setEncodingType(String)}.
046: */
047: public final static String ENC_TYPE_MULTIPART_FORM = "multipart/form-data";
048: /**
049: * URL form encoding. See {@link #setEncodingType(String)}.
050: */
051: public static final String URL_ENCODING = "application/x-www-form-urlencoded";
052:
053: /**
054: * Use method POST for submission of the data.
055: */
056: private boolean postMethod = true;
057:
058: /**
059: * EncondingType for submission of the data.
060: */
061: private String encType;
062:
063: /**
064: * Target URL to which data should be sent to
065: */
066: private URL action;
067:
068: protected final EventListenerList listenerList = new EventListenerList();
069:
070: protected String actionCommand;
071:
072: /**
073: * the button, that is activated, if no other button is pressed in this
074: * form.
075: */
076: private SButton defaultButton;
077:
078: /**
079: * the WingS event thread is the servlet doGet()/doPost() context
080: * thread. Within this thread, we collect all armed components. A
081: * 'armed' component is a component, that will 'fire' an event after the
082: * first processRequest() stage is completed.
083: */
084: private static ThreadLocal threadArmedComponents = new ThreadLocal() {
085: protected synchronized Object initialValue() {
086: return new HashSet(2);
087: }
088: };
089:
090: /**
091: * Create a standard form component.
092: */
093: public SForm() {
094: }
095:
096: /**
097: * Create a standard form component but redirects the request to the passed
098: * URL. Use this i.e. to address other servlets.
099: *
100: * @param action The target URL.
101: */
102: public SForm(URL action) {
103: setAction(action);
104: }
105:
106: /**
107: * Create a standard form component.
108: *
109: * @param layout The layout to apply to this container.
110: * @see SContainer
111: */
112: public SForm(SLayoutManager layout) {
113: super (layout);
114: }
115:
116: /**
117: * A SForm fires an event each time it was triggered (i.e. pressing asubmit button inside)
118: *
119: * @param actionCommand The action command to place insiside the {@link ActionEvent}
120: */
121: public void setActionCommand(String actionCommand) {
122: this .actionCommand = actionCommand;
123: }
124:
125: /**
126: * @see #setActionCommand(String)
127: */
128: public String getActionCommand() {
129: return actionCommand;
130: }
131:
132: /**
133: * Set the default button activated upon <b>enter</b>.
134: * The button is triggered if you press <b>enter</b> inside a form to submit it.
135: * @param defaultButton A button which will be rendered <b>invisible</b>.
136: * If <code>null</code> enter key pressed will be catched by the wings framework.
137: */
138: public void setDefaultButton(SButton defaultButton) {
139: reloadIfChange(this .defaultButton, defaultButton);
140: this .defaultButton = defaultButton;
141: }
142:
143: /**
144: * @see #setDefaultButton(SButton)
145: */
146: public SButton getDefaultButton() {
147: return this .defaultButton;
148: }
149:
150: /**
151: * Add a listener for Form events. A Form event is always triggered, when
152: * a form has been submitted. Usually, this happens, whenever a submit
153: * button is pressed or some other mechanism triggered the posting of the
154: * form. Other mechanisms are
155: * <ul>
156: * <li> Java Script submit() event</li>
157: * <li> If a form contains a single text input, then many browsers
158: * submit the form, if the user presses RETURN in that field. In that
159: * case, the submit button will <em>not</em> receive any event but
160: * only the form.
161: * <li> The {@link SFileChooser} will trigger a form event, if the file
162: * size exceeded the allowed size. In that case, even if the submit
163: * button has been pressed, no submit-button event will be triggered.
164: * (For details, see {@link SFileChooser}).
165: * </ul>
166: * Form events are guaranteed to be triggered <em>after</em> all
167: * Selection-Changes and Button ActionListeners.
168: */
169: public void addActionListener(ActionListener listener) {
170: listenerList.add(ActionListener.class, listener);
171: }
172:
173: /**
174: * Remove a form action listener, that has been added in
175: * {@link #addActionListener(ActionListener)}
176: */
177: public void removeActionListener(ActionListener listener) {
178: listenerList.remove(ActionListener.class, listener);
179: }
180:
181: /**
182: * Fire a ActionEvent at each registered listener.
183: */
184: protected void fireActionPerformed(String pActionCommand) {
185: ActionEvent e = null;
186: // Guaranteed to return a non-null array
187: Object[] listeners = listenerList.getListenerList();
188: // Process the listeners last to first, notifying
189: // those that are interested in this event
190: for (int i = listeners.length - 2; i >= 0; i -= 2) {
191: if (listeners[i] == ActionListener.class) {
192: // lazy create ActionEvent
193: if (e == null) {
194: e = new ActionEvent(this ,
195: ActionEvent.ACTION_PERFORMED,
196: pActionCommand);
197: }
198: ((ActionListener) listeners[i + 1]).actionPerformed(e);
199: }
200: }
201: }
202:
203: /**
204: * Register a components to be subject to fire component events in a later phase of
205: * the request processing. <code>SForm</code> will call
206: * {@link org.wings.LowLevelEventListener#fireIntermediateEvents()} and later
207: * {@link org.wings.LowLevelEventListener#fireFinalEvents()} in a later phase of
208: * the request. The calls on the components will be ordered dependend on their type.
209: *
210: * @param component The component to callback for event firing in a later phase of the request
211: * @see #fireEvents()
212: */
213: public static void addArmedComponent(LowLevelEventListener component) {
214: Set armedComponents = (Set) threadArmedComponents.get();
215: armedComponents.add(component);
216: }
217:
218: /**
219: * clear armed components. This is usually not necessary, since sessions
220: * clear clear their armed components. But if there was some Exception, it
221: * might well be, that this does not happen.
222: */
223: public static void clearArmedComponents() {
224: Set armedComponents = (Set) threadArmedComponents.get();
225: armedComponents.clear();
226: }
227:
228: /*
229: * Die Sache muss natuerlich Thread Save sein, d.h. es duerfen nur
230: * die Events gefeuert werden, die auch aus dem feuernden Thread
231: * stammen (eben dem Dispatcher Thread). Sichergestellt wird das
232: * dadurch das beim abfeuern der Event in eine Queue (ArrayList)
233: * gestellt wird, die zu dem feuernden Event gehoert. Diese Queues
234: * der verschiedenen Threads werden in einer Map verwaltet.
235: * Beim feuern wird dann die Queue, die dem aktuellen Thread
236: * entspricht gefeuert und aus der Map entfernt.
237: */
238: /**
239: * This method fires the low level events for all "armed" components of
240: * this thread (http session) in an ordered manner:
241: * <ul><li>forms
242: * <li>buttons / clickables
243: * <li>"regular" components</ul>
244: * This order derives out of the assumption, that a user first modifies
245: * regular components before he presses the button submitting his changes.
246: * Otherwise button actions would get fired before the edit components
247: * fired their events.
248: */
249: public static void fireEvents() {
250: Set armedComponents = (Set) threadArmedComponents.get();
251: // use a copy to avoid concurrent modification exceptions if a
252: // LowLevelEventListener adds itself to the armedComponents again
253: Set armedComponentsCopy = new HashSet(armedComponents);
254: try {
255: // handle form special, form event should be fired last
256: // hopefully there is only one form ;-)
257: Iterator iterator = armedComponentsCopy.iterator();
258: LinkedList formEvents = null;
259: LinkedList buttonEvents = null;
260:
261: while (iterator.hasNext()) {
262: LowLevelEventListener component = (LowLevelEventListener) iterator
263: .next();
264: /* fire form events at last
265: * there could be more than one form event (e.g. mozilla posts a
266: * hidden element even if it is in a form outside the posted
267: * form (if the form is nested). Forms should not be nested in HTML.
268: */
269: if (component instanceof SForm) {
270: if (formEvents == null) {
271: formEvents = new LinkedList();
272: } // end of if ()
273: formEvents.add(component);
274: iterator.remove();
275: } else if (component instanceof SAbstractIconTextCompound) {
276: if (buttonEvents == null) {
277: buttonEvents = new LinkedList();
278: }
279: buttonEvents.add(component);
280: iterator.remove();
281: } else {
282: component.fireIntermediateEvents();
283: }
284: }
285:
286: /*
287: * no buttons in forms pressed ? Then consider the default-Button.
288: */
289: // Wrong - this fires default button for page scrollers!
290: /*if (buttonEvents == null && formEvents != null) {
291: Iterator fit = formEvents.iterator();
292: while (fit.hasNext()) {
293: SForm form = (SForm) fit.next();
294: SButton defaultButton = form.getDefaultButton();
295: if (defaultButton != null) {
296: if (buttonEvents == null) {
297: buttonEvents = new LinkedList();
298: }
299: buttonEvents.add(defaultButton);
300: }
301: }
302: } */
303:
304: if (buttonEvents != null) {
305: iterator = buttonEvents.iterator();
306: while (iterator.hasNext()) {
307: ((SAbstractIconTextCompound) iterator.next())
308: .fireIntermediateEvents();
309: }
310: }
311:
312: if (formEvents != null) {
313: iterator = formEvents.iterator();
314: while (iterator.hasNext()) {
315: ((SForm) iterator.next()).fireIntermediateEvents();
316: }
317: }
318:
319: iterator = armedComponentsCopy.iterator();
320: while (iterator.hasNext()) {
321: LowLevelEventListener component = (LowLevelEventListener) iterator
322: .next();
323: // fire form events at last
324: component.fireFinalEvents();
325: }
326:
327: if (buttonEvents != null) {
328: iterator = buttonEvents.iterator();
329: while (iterator.hasNext()) {
330: ((SAbstractIconTextCompound) iterator.next())
331: .fireFinalEvents();
332: }
333: buttonEvents.clear();
334: }
335:
336: if (formEvents != null) {
337: iterator = formEvents.iterator();
338: while (iterator.hasNext()) {
339: ((SForm) iterator.next()).fireFinalEvents();
340: }
341: formEvents.clear();
342: }
343: } finally {
344: armedComponents.clear();
345: }
346: }
347:
348: /**
349: * Set, whether this form is to be transmitted via <code>POST</code> (true)
350: * or <code>GET</code> (false). The default, and this is what you
351: * usually want, is <code>POST</code>.
352: */
353: public void setPostMethod(boolean postMethod) {
354: if (isDifferent(this .postMethod, postMethod))
355: update(getCG().getMethodUpdate(this ,
356: postMethod ? "post" : "get"));
357:
358: this .postMethod = postMethod;
359: }
360:
361: /**
362: * Returns, whether this form is transmitted via <code>POST</code> (true)
363: * or <code>GET</code> (false). <p>
364: * <b>Default</b> is <code>true</code>.
365: *
366: * @return <code>true</code> if form postedt via <code>POST</code>,
367: * <code>false</code> if via <code>GET</code> (false).
368: */
369: public boolean isPostMethod() {
370: return postMethod;
371: }
372:
373: /**
374: * Set the encoding of this form. This actually is an HTML interna
375: * that bubbles up here. By default, the encoding type of any HTML-form
376: * is <code>application/x-www-form-urlencoded</code>, and as such, needn't
377: * be explicitly set with this setter. However, if you've included a
378: * file upload element (as represented by {@link SFileChooser}) in your
379: * form, this must be set to <code>multipart/form-data</code>, since only
380: * then, files are transmitted correctly. In 'normal' forms without
381: * file upload, it is not necessary to set it to
382: * <code>multipart/form-data</code>; actually it enlarges the data to
383: * be transmitted, so you probably don't want to do this, then.
384: *
385: * @param type the encoding type; one of <code>multipart/form-data</code>,
386: * <code>application/x-www-form-urlencoded</code> or null to detect encoding.
387: */
388: public void setEncodingType(String type) {
389: if (isDifferent(encType, type))
390: update(getCG().getEncodingUpdate(this , type));
391:
392: encType = type;
393: }
394:
395: /**
396: * Get the current encoding type, as set with
397: * {@link #setEncodingType(String)}. If no encoding type was set, this
398: * method detects the best encoding type. This can be expensive, so if
399: * you can, set the encoding type.
400: *
401: * @return string containing the encoding type. This is something like
402: * <code>multipart/form-data</code>,
403: * <code>application/x-www-form-urlencoded</code> .. or 'null'
404: * by default.
405: */
406: public String getEncodingType() {
407: return encType;
408: }
409:
410: int fileChooserCount;
411:
412: public void registerFileChooser(SFileChooser fileChooser) {
413: fileChooserCount++;
414: if (!ENC_TYPE_MULTIPART_FORM.equals(encType))
415: setEncodingType(ENC_TYPE_MULTIPART_FORM);
416: }
417:
418: public void unregisterFileChooser(SFileChooser fileChooser) {
419: fileChooserCount--;
420: if (fileChooserCount == 0
421: && ENC_TYPE_MULTIPART_FORM.equals(encType))
422: setEncodingType(ENC_TYPE_TEXT_PLAIN);
423: }
424:
425: public void setAction(URL action) {
426: this .action = action;
427: }
428:
429: public URL getAction() {
430: return action;
431: }
432:
433: public RequestURL getRequestURL() {
434: RequestURL addr = super .getRequestURL();
435: if (getAction() != null) {
436: addr.addParameter(getAction().toString()); // ??
437: }
438: return addr;
439: }
440:
441: public void processLowLevelEvent(String action, String[] values) {
442: processKeyEvents(values);
443: if (action.endsWith("_keystroke"))
444: return;
445:
446: // we have to wait, until all changed states of our form have
447: // changed, before we anything can happen.
448: SForm.addArmedComponent(this );
449: }
450:
451: public void fireIntermediateEvents() {
452: }
453:
454: public void fireFinalEvents() {
455: fireKeyEvents();
456: fireActionPerformed(getActionCommand());
457: }
458:
459: /** @see LowLevelEventListener#isEpochCheckEnabled() */
460: private boolean epochCheckEnabled = true;
461:
462: /** @see LowLevelEventListener#isEpochCheckEnabled() */
463: public boolean isEpochCheckEnabled() {
464: return epochCheckEnabled;
465: }
466:
467: /** @see LowLevelEventListener#isEpochCheckEnabled() */
468: public void setEpochCheckEnabled(boolean epochCheckEnabled) {
469: this .epochCheckEnabled = epochCheckEnabled;
470: }
471:
472: public SComponent addComponent(SComponent c, Object constraint,
473: int index) {
474: if (c instanceof SForm)
475: log.warn("WARNING: attempt to nest forms; won't work.");
476: return super .addComponent(c, constraint, index);
477: }
478:
479: public void setCG(FormCG cg) {
480: super .setCG(cg);
481: }
482:
483: public FormCG getCG() {
484: return (FormCG) super.getCG();
485: }
486: }
|