org.wings.SForm.java Source code

Java tutorial

Introduction

Here is the source code for org.wings.SForm.java

Source

/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wings.plaf.FormCG;

import javax.swing.event.EventListenerList;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.*;

/**
 * Container in which you need to wrap HTML input fields (ie&#x2e; <code>STextField</code>)
 * to work correctly.
 * <p/>
 * The browser uses this object/tag to identify how (POST or GET) and where
 * to send an request originating from any input inside this form.
 * <p/>
 * <b>Note:</b>Please be aware, that some components render differently if
 * placed inside a <code>SForm</code>.
 *
 * @author <a href="mailto:armin.haaf@mercatis.de">Armin Haaf</a>
 */
public class SForm extends SContainer implements LowLevelEventListener {
    private final static Log log = LogFactory.getLog(SForm.class);

    /**
     * Default Form encoding type. See {@link #setEncodingType(String)}.
     */
    public final static String ENC_TYPE_TEXT_PLAIN = "text/plain";
    /**
     * Multipart form encoding. Needed for file uploads. See {@link #setEncodingType(String)}.
     */
    public final static String ENC_TYPE_MULTIPART_FORM = "multipart/form-data";
    /**
     * URL form encoding. See {@link #setEncodingType(String)}.
     */
    public static final String URL_ENCODING = "application/x-www-form-urlencoded";

    /**
     * Use method POST for submission of the data.
     */
    private boolean postMethod = true;

    /**
     * EncondingType for submission of the data.
     */
    private String encType;

    /**
     * Target URL to which data should be sent to
     */
    private URL action;

    protected final EventListenerList listenerList = new EventListenerList();

    protected String actionCommand;

    /**
     * the button, that is activated, if no other button is pressed in this
     * form.
     */
    private SButton defaultButton;

    /**
     * the WingS event thread is the servlet doGet()/doPost() context
     * thread. Within this thread, we collect all armed components. A
     * 'armed' component is a component, that will 'fire' an event after the
     * first processRequest() stage is completed.
     */
    private static ThreadLocal threadArmedComponents = new ThreadLocal() {
        protected synchronized Object initialValue() {
            return new HashSet(2);
        }
    };

    /**
     * Create a standard form component.
     */
    public SForm() {
    }

    /**
     * Create a standard form component but redirects the request to the passed
     * URL. Use this i.e. to address other servlets.
     *
     * @param action The target URL.
     */
    public SForm(URL action) {
        setAction(action);
    }

    /**
     * Create a standard form component.
     *
     * @param layout The layout to apply to this container.
     * @see SContainer
     */
    public SForm(SLayoutManager layout) {
        super(layout);
    }

    /**
     * A SForm fires an event each time it was triggered (i.e. pressing asubmit button inside)
     *
     * @param actionCommand The action command to place insiside the {@link ActionEvent}
     */
    public void setActionCommand(String actionCommand) {
        String oldVal = this.actionCommand;
        this.actionCommand = actionCommand;
        propertyChangeSupport.firePropertyChange("actionCommand", oldVal, this.actionCommand);
    }

    /**
     * @see #setActionCommand(String)
     */
    public String getActionCommand() {
        return actionCommand;
    }

    /**
     * Set the default button activated upon <b>enter</b>.
     * The button is triggered if you press <b>enter</b> inside a form to submit it.
     * @param defaultButton A button which will be rendered <b>invisible</b>.
     * If <code>null</code> enter key pressed will be catched by the wings framework.
     */
    public void setDefaultButton(SButton defaultButton) {
        SButton oldButton = this.defaultButton;
        String oldName = oldButton != null ? oldButton.getName() : null;
        String newName = defaultButton != null ? defaultButton.getName() : null;
        if (isDifferent(oldName, newName))
            update(getCG().getDefaultButtonNameUpdate(this, newName));
        this.defaultButton = defaultButton;
        propertyChangeSupport.firePropertyChange("defaultButton", oldButton, this.defaultButton);
    }

    /**
     * @see #setDefaultButton(SButton)
     */
    public SButton getDefaultButton() {
        return this.defaultButton;
    }

    /**
     * Add a listener for Form events. A Form event is always triggered, when
     * a form has been submitted. Usually, this happens, whenever a submit
     * button is pressed or some other mechanism triggered the posting of the
     * form. Other mechanisms are
     * <ul>
     * <li> Java Script submit() event</li>
     * <li> If a form contains a single text input, then many browsers
     * submit the form, if the user presses RETURN in that field. In that
     * case, the submit button will <em>not</em> receive any event but
     * only the form.
     * <li> The {@link SFileChooser} will trigger a form event, if the file
     * size exceeded the allowed size. In that case, even if the submit
     * button has been pressed, no submit-button event will be triggered.
     * (For details, see {@link SFileChooser}).
     * </ul>
     * Form events are guaranteed to be triggered <em>after</em> all
     * Selection-Changes and Button ActionListeners.
     */
    public void addActionListener(ActionListener listener) {
        listenerList.add(ActionListener.class, listener);
    }

    /**
     * Remove a form action listener, that has been added in
     * {@link #addActionListener(ActionListener)}
     */
    public void removeActionListener(ActionListener listener) {
        listenerList.remove(ActionListener.class, listener);
    }

    /**
     * Fire a ActionEvent at each registered listener.
     */
    protected void fireActionPerformed(String pActionCommand) {
        ActionEvent e = null;
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ActionListener.class) {
                // lazy create ActionEvent
                if (e == null) {
                    e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, pActionCommand);
                }
                ((ActionListener) listeners[i + 1]).actionPerformed(e);
            }
        }
    }

    /**
     * Register a components to be subject to fire component events in a later phase of
     * the request processing. <code>SForm</code> will call
     * {@link org.wings.LowLevelEventListener#fireIntermediateEvents()} and later
     * {@link org.wings.LowLevelEventListener#fireFinalEvents()} in a later phase of
     * the request. The calls on the components will be ordered dependend on their type.
     *
     * @param component The component to callback for event firing in a later phase of the request
     * @see #fireEvents()
     */
    public static void addArmedComponent(LowLevelEventListener component) {
        Set armedComponents = (Set) threadArmedComponents.get();
        armedComponents.add(component);
    }

    /**
     * clear armed components. This is usually not necessary, since sessions
     * clear clear their armed components. But if there was some Exception, it
     * might well be, that this does not happen.
     */
    public static void clearArmedComponents() {
        Set armedComponents = (Set) threadArmedComponents.get();
        armedComponents.clear();
    }

    /*
     * Die Sache muss natuerlich Thread Save sein, d.h. es duerfen nur
     * die Events gefeuert werden, die auch aus dem feuernden Thread
     * stammen (eben dem Dispatcher Thread). Sichergestellt wird das
     * dadurch das beim abfeuern der Event in eine Queue (ArrayList)
     * gestellt wird, die zu dem feuernden Event gehoert. Diese Queues
     * der verschiedenen Threads werden in einer Map verwaltet.
     * Beim feuern wird dann die Queue, die dem aktuellen Thread
     * entspricht gefeuert und aus der Map entfernt.
     */
    /**
     * This method fires the low level events for all "armed" components of
     * this thread (http session) in an ordered manner:
     * <ul><li>forms
     * <li>buttons / clickables
     * <li>"regular" components</ul>
     * This order derives out of the assumption, that a user first modifies
     * regular components before he presses the button submitting his changes.
     * Otherwise button actions would get fired before the edit components
     * fired their events.
     */
    public static void fireEvents() {
        Set armedComponents = (Set) threadArmedComponents.get();
        // use a copy to avoid concurrent modification exceptions if a
        // LowLevelEventListener adds itself to the armedComponents again
        Set armedComponentsCopy = new HashSet(armedComponents);
        try {
            // handle form special, form event should be fired last
            // hopefully there is only one form ;-)
            Iterator iterator = armedComponentsCopy.iterator();
            LinkedList formEvents = null;
            LinkedList buttonEvents = null;

            while (iterator.hasNext()) {
                LowLevelEventListener component = (LowLevelEventListener) iterator.next();
                /* fire form events at last
                 * there could be more than one form event (e.g. mozilla posts a
                 * hidden element even if it is in a form outside the posted
                 * form (if the form is nested). Forms should not be nested in HTML.
                 */
                if (component instanceof SForm) {
                    if (formEvents == null) {
                        formEvents = new LinkedList();
                    } // end of if ()
                    formEvents.add(component);
                    iterator.remove();
                } else if (component instanceof SAbstractIconTextCompound) {
                    if (buttonEvents == null) {
                        buttonEvents = new LinkedList();
                    }
                    buttonEvents.add(component);
                    iterator.remove();
                } else {
                    component.fireIntermediateEvents();
                }
            }

            /*
             * no buttons in forms pressed ? Then consider the default-Button.
             */
            // Wrong - this fires default button for page scrollers!
            /*if (buttonEvents == null && formEvents != null) {
            Iterator fit = formEvents.iterator();
            while (fit.hasNext()) {
                SForm form = (SForm) fit.next();
                SButton defaultButton = form.getDefaultButton();
                if (defaultButton != null) {
                    if (buttonEvents == null) {
                        buttonEvents = new LinkedList();
                    }
                    buttonEvents.add(defaultButton);
                }
            }
            } */

            if (buttonEvents != null) {
                iterator = buttonEvents.iterator();
                while (iterator.hasNext()) {
                    ((SAbstractIconTextCompound) iterator.next()).fireIntermediateEvents();
                }
            }

            if (formEvents != null) {
                iterator = formEvents.iterator();
                while (iterator.hasNext()) {
                    ((SForm) iterator.next()).fireIntermediateEvents();
                }
            }

            iterator = armedComponentsCopy.iterator();
            while (iterator.hasNext()) {
                LowLevelEventListener component = (LowLevelEventListener) iterator.next();
                // fire form events at last
                component.fireFinalEvents();
            }

            if (buttonEvents != null) {
                iterator = buttonEvents.iterator();
                while (iterator.hasNext()) {
                    ((SAbstractIconTextCompound) iterator.next()).fireFinalEvents();
                }
                buttonEvents.clear();
            }

            if (formEvents != null) {
                iterator = formEvents.iterator();
                while (iterator.hasNext()) {
                    ((SForm) iterator.next()).fireFinalEvents();
                }
                formEvents.clear();
            }
        } finally {
            armedComponents.clear();
        }
    }

    /**
     * Set, whether this form is to be transmitted via <code>POST</code> (true)
     * or <code>GET</code> (false). The default, and this is what you
     * usually want, is <code>POST</code>.
     */
    public void setPostMethod(boolean postMethod) {
        boolean oldVal = this.postMethod;
        if (isDifferent(this.postMethod, postMethod))
            update(getCG().getMethodUpdate(this, postMethod ? "post" : "get"));
        this.postMethod = postMethod;
        propertyChangeSupport.firePropertyChange("postMethod", oldVal, this.postMethod);
    }

    /**
     * Returns, whether this form is transmitted via <code>POST</code> (true)
     * or <code>GET</code> (false). <p>
     * <b>Default</b> is <code>true</code>.
     *
     * @return <code>true</code> if form postedt via <code>POST</code>,
     * <code>false</code> if via <code>GET</code> (false).
     */
    public boolean isPostMethod() {
        return postMethod;
    }

    /**
     * Set the encoding of this form. This actually is an HTML interna
     * that bubbles up here. By default, the encoding type of any HTML-form
     * is <code>application/x-www-form-urlencoded</code>, and as such, needn't
     * be explicitly set with this setter. However, if you've included a
     * file upload element (as represented by {@link SFileChooser}) in your
     * form, this must be set to <code>multipart/form-data</code>, since only
     * then, files are transmitted correctly. In 'normal' forms without
     * file upload, it is not necessary to set it to
     * <code>multipart/form-data</code>; actually it enlarges the data to
     * be transmitted, so you probably don't want to do this, then.
     *
     * @param type the encoding type; one of <code>multipart/form-data</code>,
     *             <code>application/x-www-form-urlencoded</code> or null to detect encoding.
     */
    public void setEncodingType(String type) {
        String oldVal = this.encType;
        if (isDifferent(encType, type))
            update(getCG().getEncodingUpdate(this, type));
        encType = type;
        propertyChangeSupport.firePropertyChange("encodingType", oldVal, this.encType);
    }

    /**
     * Get the current encoding type, as set with
     * {@link #setEncodingType(String)}. If no encoding type was set, this
     * method detects the best encoding type. This can be expensive, so if
     * you can, set the encoding type.
     *
     * @return string containing the encoding type. This is something like
     *         <code>multipart/form-data</code>,
     *         <code>application/x-www-form-urlencoded</code> .. or 'null'
     *         by default.
     */
    public String getEncodingType() {
        return encType;
    }

    int fileChooserCount;

    public void registerFileChooser(SFileChooser fileChooser) {
        fileChooserCount++;
        if (!ENC_TYPE_MULTIPART_FORM.equals(encType))
            setEncodingType(ENC_TYPE_MULTIPART_FORM);
    }

    public void unregisterFileChooser(SFileChooser fileChooser) {
        fileChooserCount--;
        if (fileChooserCount == 0 && ENC_TYPE_MULTIPART_FORM.equals(encType))
            setEncodingType(ENC_TYPE_TEXT_PLAIN);
    }

    public void setAction(URL action) {
        URL oldVal = this.action;
        this.action = action;
        propertyChangeSupport.firePropertyChange("action", oldVal, this.action);
    }

    public URL getAction() {
        return action;
    }

    public RequestURL getRequestURL() {
        RequestURL addr = super.getRequestURL();
        if (getAction() != null) {
            addr.addParameter(getAction().toString()); // ??
        }
        return addr;
    }

    public void processLowLevelEvent(String action, String[] values) {
        processKeyEvents(values);
        if (action.endsWith("_keystroke"))
            return;

        // we have to wait, until all changed states of our form have
        // changed, before we anything can happen.
        SForm.addArmedComponent(this);
    }

    public void fireIntermediateEvents() {
    }

    public void fireFinalEvents() {
        fireKeyEvents();
        fireActionPerformed(getActionCommand());
    }

    /** @see LowLevelEventListener#isEpochCheckEnabled() */
    private boolean epochCheckEnabled = true;

    /** @see LowLevelEventListener#isEpochCheckEnabled() */
    public boolean isEpochCheckEnabled() {
        return epochCheckEnabled;
    }

    /** @see LowLevelEventListener#isEpochCheckEnabled() */
    public void setEpochCheckEnabled(boolean epochCheckEnabled) {
        boolean oldVal = this.epochCheckEnabled;
        this.epochCheckEnabled = epochCheckEnabled;
        propertyChangeSupport.firePropertyChange("epochCheckEnabled", oldVal, this.epochCheckEnabled);
    }

    public SComponent addComponent(SComponent c, Object constraint, int index) {
        if (c instanceof SForm)
            log.warn("WARNING: attempt to nest forms; won't work.");
        return super.addComponent(c, constraint, index);
    }

    public void setCG(FormCG cg) {
        super.setCG(cg);
    }

    public FormCG getCG() {
        return (FormCG) super.getCG();
    }
}