name.martingeisse.wicket.component.stdform.StandardFormPanel.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.wicket.component.stdform.StandardFormPanel.java

Source

/**
 * Copyright (c) 2010 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.wicket.component.stdform;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import name.martingeisse.wicket.component.misc.BootstrapComponentFeedbackPanel;
import name.martingeisse.wicket.component.stateless.StatelessAjaxSubmitLink;
import name.martingeisse.wicket.util.AjaxRequestUtil;

import org.apache.wicket.Component;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.EmailTextField;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponentLabel;
import org.apache.wicket.markup.html.form.IFormModelUpdateListener;
import org.apache.wicket.markup.html.form.LabeledWebMarkupContainer;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.StatelessForm;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.mapper.parameter.PageParameters;

/**
 * This panel implements a "standard form", using a very regular
 * Bootstrap-based layout. It is intended to quickly build forms,
 * usually to edit data records, without any special layout or
 * content.
 * 
 * Form elements are stacked vertically using a {@link RepeatingView}
 * with manually added components. Each component uses a basic
 * piece of markup that contains a label, help text, error message and
 * the form control itself. Special form elements can be added by
 * adding components with intrinsic markup, such as panels or
 * fragments. Standard special components such as a submit button
 * are provided as fragments by this class.
 * 
 * The FORM element is contained in this panel.
 */
public class StandardFormPanel extends Panel {

    /**
     * The wicket:id to use for the form control within the
     * form control wrapper fragment.
     */
    public static final String FORM_CONTROL_ID = "formControl";

    /**
     * the modelUpdateListeners
     */
    private List<ModelUpdateListener> modelUpdateListeners;

    /**
     * Constructor.
     * @param id the wicket id
     * @param stateless whether to use a StatelessForm
     */
    public StandardFormPanel(final String id, final boolean stateless) {
        super(id);
        construct(stateless);
    }

    /**
     * Constructor.
     * @param id the wicket id
     * @param model the model
     * @param stateless whether to use a StatelessForm
     */
    public StandardFormPanel(final String id, final IModel<?> model, final boolean stateless) {
        super(id, model);
        construct(stateless);
    }

    /**
     * 
     */
    private final void construct(final boolean stateless) {

        // Create the form. Stupid StatelessForm class requires us to duplicate code here :(
        final Form<Void> form;
        if (stateless) {
            form = new StatelessForm<Void>("form") {

                @Override
                protected void onValidate() {
                    AjaxRequestUtil.markForRender(StandardFormPanel.this);
                }

                @Override
                protected void onSubmit() {
                    internalOnSubmit();
                }

            };
        } else {
            form = new Form<Void>("form") {

                @Override
                protected void onValidate() {
                    AjaxRequestUtil.markForRender(StandardFormPanel.this);
                }

                @Override
                protected void onSubmit() {
                    internalOnSubmit();
                }

            };
        }

        // build the rest of the panel
        setOutputMarkupId(true);
        add(form);
        final RepeatingView formElements = new RepeatingView("formElements");
        form.add(formElements);

    }

    /**
     * @return the form
     */
    public final Form<?> getForm() {
        return (Form<?>) get("form");
    }

    /**
     * @return the {@link RepeatingView} for the form elements
     */
    public final RepeatingView getFormElementsRepeatingView() {
        return (RepeatingView) get("form:formElements");
    }

    /**
     * Generates a component ID for a new raw form element.
     * 
     * @return the component ID
     */
    public final String newRawFormElementId() {
        return getFormElementsRepeatingView().newChildId();
    }

    /**
     * Adds the specified component as a "raw" form element, i.e. without
     * an error text or label. This is typically used to add custom
     * components that are not really form controls, or contain the error
     * text and label in a special way.
     * 
     * @param component the component to add
     */
    public final void addRawFormElement(final Component component) {
        getFormElementsRepeatingView().add(component);
    }

    /**
     * Adds a form control wrapper fragment for the specified form control.
     * 
     * @param label the label for the form control
     * @param formControl the form control to wrap
     * @param labelTarget the component to refer to in the "for" attribute of the label.
     * This parameter may be null; in this case, the "for" attribute will be omitted.
     * @param errorSource the source for error messages. This parameter may be null;
     * in this case, no error messages will be shown.
     * @return the wrapper fragment
     */
    public final Fragment addFormControl(final String label, final Component formControl,
            final LabeledWebMarkupContainer labelTarget, final Component errorSource) {
        final Fragment wrapperFragment = new Fragment(newRawFormElementId(), "formControlWrapperFragment", this);
        wrapperFragment.add(new BootstrapComponentFeedbackPanel("error", errorSource));
        wrapperFragment.add(new FormComponentLabel("label", labelTarget).add(new Label("text", label)));
        wrapperFragment.add(new Label("helpText"));
        wrapperFragment.add(new RepeatingView("toolLinks"));
        wrapperFragment.add(formControl);
        addRawFormElement(wrapperFragment);
        return wrapperFragment;
    }

    /**
     * Adds a string-typed text field using the specified model.
     * @param label the label for the text field
     * @param model the model
     * @return a configurator that can be used to further configure the added components
     */
    public final SingleFormComponentElementConfigurator addTextField(final String label,
            final IModel<String> model) {
        final TextField<String> textField = new TextField<String>("textField", model);
        final Fragment wrapperFragment = addFormControl(label,
                new Fragment(FORM_CONTROL_ID, "textFieldFragment", this).add(textField), textField, textField);
        return new SingleFormComponentElementConfigurator(wrapperFragment, textField);
    }

    /**
     * Adds a text field using the specified type and model.
     * @param label the label for the text field
     * @param type the model type
     * @param model the model
     * @return a configurator that can be used to further configure the added components
     */
    public final <T> SingleFormComponentElementConfigurator addTextField(final String label, final Class<T> type,
            final IModel<T> model) {
        final TextField<T> textField = new TextField<T>("textField", model);
        textField.setType(type);
        final Fragment wrapperFragment = addFormControl(label,
                new Fragment(FORM_CONTROL_ID, "textFieldFragment", this).add(textField), textField, textField);
        return new SingleFormComponentElementConfigurator(wrapperFragment, textField);
    }

    /**
     * Adds an email text field using the specified model.
     * @param label the label for the text field
     * @param model the model
     * @return a configurator that can be used to further configure the added components
     */
    public final SingleFormComponentElementConfigurator addEmailTextField(final String label,
            final IModel<String> model) {
        final EmailTextField textField = new EmailTextField("textField", model);
        final Fragment wrapperFragment = addFormControl(label,
                new Fragment(FORM_CONTROL_ID, "emailTextFieldFragment", this).add(textField), textField, textField);
        return new SingleFormComponentElementConfigurator(wrapperFragment, textField);
    }

    /**
     * Adds a password field using the specified model.
     * @param label the label for the password field
     * @param model the model
     * @return a configurator that can be used to further configure the added components
     */
    public final SingleFormComponentElementConfigurator addPasswordTextField(final String label,
            final IModel<String> model) {
        final PasswordTextField textField = new PasswordTextField("textField", model);
        textField.setRequired(false); // probably *is* required, but no magic defaults please
        final Fragment wrapperFragment = addFormControl(label,
                new Fragment(FORM_CONTROL_ID, "passwordTextFieldFragment", this).add(textField), textField,
                textField);
        return new SingleFormComponentElementConfigurator(wrapperFragment, textField);
    }

    /**
     * Adds a text area using the specified model.
     * @param label the label for the text area
     * @param model the model
     * @param rows number of visible rows
     * @return a configurator that can be used to further configure the added components
     */
    public final SingleFormComponentElementConfigurator addTextArea(final String label, final IModel<String> model,
            final int rows) {
        final TextArea<String> textArea = new TextArea<String>("textArea", model) {
            @Override
            protected void onComponentTag(final ComponentTag tag) {
                super.onComponentTag(tag);
                tag.put("rows", rows);
            }
        };
        final Fragment wrapperFragment = addFormControl(label,
                new Fragment(FORM_CONTROL_ID, "textAreaFragment", this).add(textArea), textArea, textArea);
        return new SingleFormComponentElementConfigurator(wrapperFragment, textArea);
    }

    /**
     * Adds an HTML fragment for an info text to the form.
     * 
     * @param model the model for the html fragment
     * @return a configurator that can be used to further configure the added components
     */
    public final ElementConfigurator addInfoHtml(final IModel<String> model) {
        final Fragment wrapperFragment = new Fragment(newRawFormElementId(), "infoHtmlFragment", this);
        wrapperFragment.add(new Label("html", model).setEscapeModelStrings(false));
        addRawFormElement(wrapperFragment);
        return new ElementConfigurator(wrapperFragment);
    }

    /**
     * Adds a submit button for the form. 
     */
    public final void addSubmitButton() {
        addSubmitButton("Submit");
    }

    /**
     * Adds a submit button for the form. 
     * @param text the button text
     */
    public final void addSubmitButton(final String text) {
        final Label label = new Label("button", text);
        addRawFormElement(new Fragment(newRawFormElementId(), "submitButtonFragment", this).add(label));
    }

    /**
     * Adds an AJAX submit button for the form. 
     */
    public final void addAjaxSubmitButton() {
        addAjaxSubmitButton("Submit");
    }

    /**
     * Adds an AJAX submit button for the form. 
     * @param text the button text
     */
    public final void addAjaxSubmitButton(final String text) {
        final AjaxSubmitLink submitLink = new AjaxSubmitLink("button") {
        };
        submitLink.setBody(Model.of(text));
        addRawFormElement(new Fragment(newRawFormElementId(), "submitButtonFragment", this).add(submitLink));
    }

    /**
     * Adds a stateless AJAX submit button for the form. 
     * @param pageParameters the page parameters used for submitting the form
     */
    public final void addStatelessAjaxSubmitButton(PageParameters pageParameters) {
        addStatelessAjaxSubmitButton("Submit", pageParameters);
    }

    /**
     * Adds a stateless AJAX submit button for the form. 
     * @param text the button text
     * @param pageParameters the page parameters used for submitting the form
     */
    public final void addStatelessAjaxSubmitButton(final String text, PageParameters pageParameters) {
        final StatelessAjaxSubmitLink submitLink = new StatelessAjaxSubmitLink("button", pageParameters);
        submitLink.setBody(Model.of(text));
        addRawFormElement(new Fragment(newRawFormElementId(), "submitButtonFragment", this).add(submitLink));
    }

    /**
     * Adds a listener that updates complex models. See {@link ModelUpdateListener}.
     * @param listener the listener to add
     */
    public void addModelUpdateListener(final ModelUpdateListener listener) {
        if (modelUpdateListeners == null) {
            modelUpdateListeners = new ArrayList<StandardFormPanel.ModelUpdateListener>(3);
        }
        modelUpdateListeners.add(listener);
    }

    /**
     * This callback is called by the form itself when submitted. It invokes
     * the model update listeners, then delegates to {@link #onSubmit()}.
     */
    private void internalOnSubmit() {
        if (modelUpdateListeners != null) {
            for (final ModelUpdateListener listener : modelUpdateListeners) {
                listener.updateModel();
            }
        }
        onSubmit();
    }

    /**
     * This callback is called when the form was submitted, after model update
     * listeners have been invoked.
     */
    protected void onSubmit() {
    }

    /**
     * Clients can provide objects of this type to update complex models using the values
     * from one or more form components. This is basically the equivalent to a custom
     * {@link IFormModelUpdateListener}, except that the latter cannot be used in
     * {@link StandardFormPanel}s because there is no enclosing component that could be
     * a listener.
     */
    public static interface ModelUpdateListener extends Serializable {

        /**
         * This method is invoked after the individual form components have updated
         * their models, but before {@link StandardFormPanel#onSubmit()} is called.
         */
        public void updateModel();

    }

}