All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.numdata.oss.web.form.FormContainer Maven / Gradle / Ivy

There is a newer version: 1.22
Show newest version
/*
 * Copyright (c) 2017, Numdata BV, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Numdata nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NUMDATA BV BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.numdata.oss.web.form;

import java.io.*;
import java.math.*;
import java.util.*;
import javax.servlet.http.*;

import com.numdata.oss.*;
import com.numdata.oss.web.*;
import org.jetbrains.annotations.*;

/**
 * This class defines a container for form components.
 *
 * @author Peter S. Heijnen
 */
public class FormContainer
extends FormComponent
{
	/**
	 * Components contained within this container.
	 */
	private final List _components = new ArrayList();

	/**
	 * Whether qualified names are used by the components in the container.
	 */
	private final boolean _qualifiedNameUsed;

	/**
	 * Whether this container should be hidden if the form is not editable.
	 */
	private boolean _hideIfReadOnly = false;

	/**
	 * Add a component to this container. The component's parent is updated
	 * accordingly.
	 *
	 * @param component Component to add.
	 */
	public void addComponent( @NotNull final FormComponent component )
	{
		final FormContainer oldContainer = component.getParent();
		//noinspection ObjectEquality
		if ( oldContainer != this )
		{
			if ( oldContainer != null )
			{
				oldContainer._components.remove( component );
			}

			component.setParent( this );
			_components.add( component );
		}
	}

	/**
	 * Construct container.
	 */
	public FormContainer()
	{
		this( null, false );
	}

	/**
	 * Construct container.
	 *
	 * @param name Name of the field.
	 */
	public FormContainer( @Nullable final String name )
	{
		this( name, false );
	}

	/**
	 * Constructs a container with the specified name, using either unqualified
	 * or qualified names (see {@link FormComponent#getQualifiedName()}).
	 *
	 * @param name           Name of the container.
	 * @param qualifiedNames {@code true} to use qualified names; {@code false}
	 *                       otherwise.
	 */
	public FormContainer( @Nullable final String name, final boolean qualifiedNames )
	{
		super( name );
		_qualifiedNameUsed = qualifiedNames;
	}

	public boolean isQualifiedNameUsed()
	{
		return _qualifiedNameUsed;
	}

	public boolean isHideIfReadOnly()
	{
		return _hideIfReadOnly;
	}

	public void setHideIfReadOnly( final boolean hideIfReadOnly )
	{
		_hideIfReadOnly = hideIfReadOnly;
	}

	/**
	 * Add line-break to this container. This can be used to force a line-break
	 * between fields. This is especially useful for multi-line input fields.
	 */
	public void addLineBreak()
	{
		addComponent( new FormStaticText( "
\n" ) ); } /** * Add field spacer to this container. This can be used to create some space * between two fields to get a more pleasing output. */ public void addFieldSpacer() { addComponent( new FormStaticText( "  " ) ); } /** * Convenience method to quickly add a checkbox field to this container. * * @param bundle Bundle containing the field label (key=name). * @param target Target object. * * @return Checkbox field that was created. */ public FormCheckbox addCheckbox( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target ) { return addCheckbox( bundle, target.getName(), target ); } /** * Convenience method to quickly add a checkbox field to this container. * * @param bundle Bundle containing the field label. * @param resourceKey Key to use in resource bundle. * @param target Target object. * * @return Checkbox field that was created. */ public FormCheckbox addCheckbox( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target ) { final FormCheckbox checkbox = new FormCheckbox( target ); addComponent( new FormLabel( ResourceBundleTools.getString( bundle, resourceKey, resourceKey ), checkbox ) ); addComponent( checkbox ); return checkbox; } /** * Convenience method to quickly add a choice field to this container. The * available value choices must be contained within the specified resource * bundle. * * @param bundle Bundle containing the field label and value choices * (key=name). * @param target Target object. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public FormChoice addChoice( @NotNull final ResourceBundle bundle, @NotNull final FieldTarget target ) { return addChoice( bundle, target.getName(), target ); } /** * Convenience method to quickly add a choice field to this container. The * available value choices must be contained within the specified resource * bundle. * * @param bundle Bundle containing the field label and value choices. * @param resourceKey Key to use in resource bundle. * @param target Target object. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public FormChoice addChoice( @NotNull final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target ) { final List optionValues = Arrays.asList( ResourceBundleTools.getStringList( bundle, resourceKey + "Values" ) ); final List optionLabels = new ArrayList( optionValues.size() ); for ( final String value : optionValues ) { String label = TextTools.isNonEmpty( resourceKey ) ? ResourceBundleTools.getString( bundle, resourceKey + '.' + value, null ) : null; if ( label == null ) { label = ResourceBundleTools.getString( bundle, value, value ); } optionLabels.add( label ); } final FormChoice choice = new FormChoice( target, optionValues, optionLabels ); addComponent( new FormLabel( bundle.getString( resourceKey ), choice ) ); addComponent( choice ); return choice; } /** * Convenience method to quickly add a choice field to this container. The * available value choices are defined by the specified enumeration class. * * @param bundle Bundle containing the field label and value choices * (key=name). * @param target Target object. * @param locale Locale for enumeration value translations. * @param enumType Enumeration type to determine choices. * @param Enumeration type. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public > FormChoice addChoice( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, @NotNull final Locale locale, @NotNull final Class enumType ) { return addChoice( bundle, target.getName(), target, locale, enumType ); } /** * Convenience method to quickly add a choice field to this container. The * available value choices are defined by the specified enumeration class. * * @param bundle Bundle containing the field label and value choices. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param locale Locale for enumeration value translations. * @param enumType Enumeration type to determine choices. * @param Enumeration type. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public > FormChoice addChoice( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, @NotNull final Locale locale, @NotNull final Class enumType ) { final FormChoice choice = createChoice( bundle, target, locale, enumType ); addComponent( new FormLabel( ResourceBundleTools.getString( bundle, resourceKey, resourceKey ), choice ) ); addComponent( choice ); return choice; } /** * Convenience method to quickly create a choice field. The available value * choices are defined by the specified enumeration class. * * @param bundle Bundle containing the field label and value choices * (key=name). * @param target Target object. * @param locale Locale for enumeration value translations. * @param enumType Enumeration type to determine choices. * @param Enumeration type. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ @NotNull public > FormChoice createChoice( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, @NotNull final Locale locale, @NotNull final Class enumType ) { return createChoice( bundle, target.getName(), target, locale, enumType ); } /** * Convenience method to quickly create a choice field. The available value * choices are defined by the specified enumeration class. * * @param bundle Bundle containing the field label and value choices. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param locale Locale for enumeration value translations. * @param enumType Enumeration type to determine choices. * @param Enumeration type. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ @NotNull public > FormChoice createChoice( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, @NotNull final Locale locale, @NotNull final Class enumType ) { final T[] constants = enumType.getEnumConstants(); final List optionValues = new ArrayList( constants.length ); final List optionLabels = new ArrayList( constants.length ); for ( final T constant : constants ) { String label = ResourceBundleTools.getString( bundle, resourceKey + '.' + constant.name(), null ); if ( label == null ) { label = ResourceBundleTools.getString( locale, constant ); } optionValues.add( constant.name() ); optionLabels.add( label ); } return new FormChoice( target, optionValues, optionLabels ); } /** * Convenience method to quickly add a choice field to this container. The * available value choices are defined by the given collection. * * @param bundle Bundle containing the field label and value choices * (key=name). * @param target Target object. * @param optionMap Map option values to option labels. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public FormChoice addChoice( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, @NotNull final Map optionMap ) { return addChoice( bundle, target.getName(), target, optionMap ); } /** * Convenience method to quickly add a choice field to this container. The * available value choices are defined by the given collection. * * @param bundle Bundle containing the field label and value choices. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param optionMap Map option values to option labels. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public FormChoice addChoice( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, @NotNull final Map optionMap ) { final Collection optionValues = new ArrayList( optionMap.size() ); final Collection optionLabels = new ArrayList( optionMap.size() ); for ( final Map.Entry entry : optionMap.entrySet() ) { optionValues.add( entry.getKey() ); optionLabels.add( entry.getValue() ); } return addChoice( bundle, resourceKey, target, optionValues, optionLabels ); } /** * Convenience method to quickly add a choice field to this container. The * available value choices are defined by the given collection. * * @param bundle Bundle containing the field label and value choices * (key=name). * @param target Target object. * @param optionValues Available options for choice. * @param optionLabels Descriptions of options for choice. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public FormChoice addChoice( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, @NotNull final Collection optionValues, @NotNull final Collection optionLabels ) { return addChoice( bundle, target.getName(), target, optionValues, optionLabels ); } /** * Convenience method to quickly add a choice field to this container. The * available value choices are defined by the given collection. * * @param bundle Bundle containing the field label and value choices. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param optionValues Available options for choice. * @param optionLabels Descriptions of options for choice. * * @return Choice field that was created. * * @see ResourceBundleTools#getChoices */ public FormChoice addChoice( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, @NotNull final Collection optionValues, @NotNull final Collection optionLabels ) { final FormChoice choice = new FormChoice( target, optionValues, optionLabels ); addComponent( new FormLabel( ResourceBundleTools.getString( bundle, resourceKey, resourceKey ), choice ) ); addComponent( choice ); return choice; } /** * Convenience method to quickly add a column to this container for * table-based forms. * * @param bundle Bundle containing the field label (key=name). * @param head Column defines a header vs. a data column. * @param text Text content of column. * * @return {@link FormColumn} that was created. */ public FormColumn addColumn( final boolean head, @Nullable final ResourceBundle bundle, @Nullable final String text ) { final FormColumn column = new FormColumn(); column.setHead( head ); if ( ( text != null ) && TextTools.isNonEmpty( text ) ) { column.setText( ResourceBundleTools.getString( bundle, text, text ) ); } addComponent( column ); return column; } /** * Convenience method to quickly add labeled static text to this container. * * @param label Label text. * @param text Static text. * * @return Text field that was created. */ public FormStaticText addStaticText( @NotNull final String label, @Nullable final String text ) { final FormStaticText result = new FormStaticText( text ); addComponent( new FormLabel( label ) ); addComponent( result ); return result; } /** * Convenience method to quickly add a text field to this container. * * @param bundle Bundle containing the field label (key=name). * @param target Target object. * @param size Visible size of field (-1 = unspecified). * @param maxLength Maximum number of characters in field (-1 = * unspecified). * * @return Text field that was created. */ public FormTextField addTextField( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, final int size, final int maxLength ) { return addTextField( bundle, target.getName(), target, size, maxLength ); } /** * Convenience method to quickly add a text field to this container. * * @param bundle Bundle containing the field label. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param size Visible size of field (-1 = unspecified). * @param maxLength Maximum number of characters in field (-1 = * unspecified). * * @return Text field that was created. */ public FormTextField addTextField( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, final int size, final int maxLength ) { final FormTextField field = new FormTextField( target, size, maxLength ); addComponent( new FormLabel( ResourceBundleTools.getString( bundle, resourceKey, resourceKey ), field ) ); addComponent( field ); return field; } /** * Convenience method to quickly add a text field to this container. * * @param bundle Bundle containing the field label (key=name). * @param target Target object. * @param size Visible size of field (-1 = unspecified). * @param maxLength Maximum number of characters in field (-1 = * unspecified). * @param value Initial value to assign to text field. * * @return Text field that was created. */ public FormTextField addTextField( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, final int size, final int maxLength, @Nullable final String value ) { return addTextField( bundle, target.getName(), target, size, maxLength, value ); } /** * Convenience method to quickly add a text field to this container. * * @param bundle Bundle containing the field label. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param size Visible size of field (-1 = unspecified). * @param maxLength Maximum number of characters in field (-1 = * unspecified). * @param value Initial value to assign to text field. * * @return Text field that was created. */ public FormTextField addTextField( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, final int size, final int maxLength, @Nullable final String value ) { final FormTextField result = addTextField( bundle, resourceKey, target, size, maxLength ); result.setValue( value ); return result; } /** * Convenience method to quickly add a text area to this container. * * @param bundle Bundle containing the field label (key=name). * @param target Target object. * @param rows Number of rows (-1 = unspecified). * @param columns Number of columns (-1 = unspecified). * * @return Text area that was created. */ public FormTextArea addTextArea( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, final int rows, final int columns ) { return addTextArea( bundle, target.getName(), target, rows, columns ); } /** * Convenience method to quickly add a text area to this container. * * @param bundle Bundle containing the field label. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param rows Number of rows (-1 = unspecified). * @param columns Number of columns (-1 = unspecified). * * @return Text area that was created. */ public FormTextArea addTextArea( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, final int rows, final int columns ) { final FormTextArea area = new FormTextArea( target, rows, columns ); addComponent( new FormLabel( ResourceBundleTools.getString( bundle, resourceKey, resourceKey ), area ) ); addComponent( area ); return area; } /** * Convenience method to quickly add a number field to this container. * * @param bundle Bundle containing the field label * (key=name). * @param target Target object. * @param allowNegative Allow negative numbers. * @param maximumFractionDigits Maximum number of fraction digits (0 ⇒ * integer only). * * @return Number field that was created. */ public FormNumberField addNumberField( @Nullable final ResourceBundle bundle, @NotNull final FieldTarget target, final boolean allowNegative, final int maximumFractionDigits ) { return addNumberField( bundle, target.getName(), target, allowNegative, maximumFractionDigits ); } /** * Convenience method to quickly add a number field to this container. * * @param bundle Bundle containing the field label. * @param resourceKey Key to use in resource bundle. * @param target Target object. * @param allowNegative Allow negative numbers. * @param maximumFractionDigits Maximum number of fraction digits (0 ⇒ * integer only). * * @return Number field that was created. */ public FormNumberField addNumberField( @Nullable final ResourceBundle bundle, @NotNull final String resourceKey, @NotNull final FieldTarget target, final boolean allowNegative, final int maximumFractionDigits ) { final FormNumberField field = new FormNumberField( target, maximumFractionDigits, allowNegative ? null : BigDecimal.ZERO, null ); addComponent( new FormLabel( ResourceBundleTools.getString( bundle, resourceKey, resourceKey ), field ) ); addComponent( field ); return field; } /** * Convenience method to add a hidden field (to define a variable). This * field is not visible nor editable. * * @param target Target object of field. */ public void addHiddenField( @NotNull final FieldTarget target ) { addComponent( new FormHiddenField( target ) ); } /** * Convenience method to add a hidden field (to define a variable). This * field is not visible nor editable. * * @param name Name of field. * @param value Value to set the field to. */ public void addHiddenField( @NotNull final String name, @NotNull final String value ) { addComponent( new FormHiddenField( new VariableFieldTarget( name ), value ) ); } /** * Remove a component from this container. The component's parent is updated * accordingly. * * @param component Component to remove. */ public void removeComponent( @NotNull final FormComponent component ) { //noinspection ObjectEquality if ( component.getParent() == this ) { component.setParent( null ); } _components.remove( component ); } /** * Get component with specified index from this container. * * @param index Index of component in container. * * @return The requested component. * * @throws IndexOutOfBoundsException if the index is out of range. */ public FormComponent getComponent( final int index ) { return _components.get( index ); } /** * Get component with specified name from this container. * * @param name Name of component in container. * * @return The requested component; {@code null} if no matching component * was found. */ @Nullable public FormComponent getComponent( @NotNull final String name ) { FormComponent result = null; for ( final FormComponent component : _components ) { if ( name.equals( component.getName() ) ) { result = component; break; } } return result; } /** * Get number of components in this container. * * @return Number of components in this container. */ public int getComponentCount() { return _components.size(); } /** * Get number of components in this container. * * @return Number of components in this container. */ public int getVisibleComponentCount() { int result = 0; for ( final FormComponent component : _components ) { if ( component.isVisible() ) { result++; } } return result; } /** * Get index of component in this container. * * @param component Component to get index of. * * @return Index of the specified component in this container; {@code -1} if * the component is not in this container. */ public int getComponentIndex( @NotNull final FormComponent component ) { return _components.indexOf( component ); } public List getComponents() { return Collections.unmodifiableList( _components ); } @SuppressWarnings( "ClassReferencesSubclass" ) @Override protected void generate( @NotNull final String contextPath, @NotNull final Form form, @Nullable final HTMLTable table, @NotNull final IndentingJspWriter iw, @NotNull final HTMLFormFactory formFactory ) throws IOException { if ( !_hideIfReadOnly || isEditable() ) { for ( int i = 0; i < getComponentCount(); i++ ) { final FormComponent component = getComponent( i ); if ( component.isVisible() ) { component.generate( contextPath, form, table, iw, formFactory ); if ( component.isEditable() && ( component instanceof FormField ) ) { final FormField field = (FormField)component; final String note = field.getDescription(); if ( !TextTools.isEmpty( note ) ) { if ( table != null ) { table.newRow( iw ); table.newColumn( iw ); table.newColumn( iw ); } iw.append( "
" ); iw.append( note ); iw.append( "
" ); } } } } } } @NotNull @Override public SubmitStatus submitData( @NotNull final HttpServletRequest request ) throws InvalidFormDataException { return submitDataToComponents( request ); } /** * Delegate for {@link #submitData} method to let all components in this * container process data that was submitted by the specified request. * * @param request Request that was used to process the form. * * @return Combined {@link SubmitStatus} of components in this container. * * @throws InvalidFormDataException if there was problem with the submitted * data. */ @NotNull protected SubmitStatus submitDataToComponents( @NotNull final HttpServletRequest request ) throws InvalidFormDataException { SubmitStatus result = SubmitStatus.UNKNOWN; for ( int i = getComponentCount(); --i >= 0; ) { final FormComponent component = getComponent( i ); if ( component.isVisible() ) { try { try { result = result.combineWith( component.submitData( request ) ); } catch ( final NumberFormatException e ) { //noinspection ThrowCaughtLocally throw new InvalidFormDataException( e.getMessage(), InvalidFormDataException.Type.INVALID_NUMBER, e ); } catch ( final RuntimeException e ) { //noinspection ThrowCaughtLocally throw new InvalidFormDataException( e.getMessage(), InvalidFormDataException.Type.INVALID_DATA, e ); } } catch ( final InvalidFormDataException e ) { if ( component.isEditable() && ( component instanceof FormField ) ) { final FormField field = (FormField)component; field.setException( e ); result = SubmitStatus.SUBMITTED_WITH_ERRORS; } else { throw e; } } } } if ( result != SubmitStatus.NOT_SUBMITTED ) { for ( int i = 0; i < getComponentCount(); i++ ) { final FormComponent component = getComponent( i ); if ( component.isVisible() && component.isEditable() && ( component instanceof FormField ) ) { final FormField field = (FormField)component; if ( field.getException() == null ) { try { field.check(); } catch ( final InvalidFormDataException e ) { field.setException( e ); result = SubmitStatus.SUBMITTED_WITH_ERRORS; } } } } } return result; } /** * Returns the component with the specified name. This component and all * nested components are considered. Components names depend on the value of * {@link #isQualifiedNameUsed()}. * * @param name Name to find. * * @return Found form component; {@code null} if not found. */ @Nullable public FormComponent getNestedComponent( @NotNull final String name ) { FormComponent result = null; if ( name.equals( getName() ) ) { result = this; } else { for ( final FormComponent component : _components ) { if ( component instanceof FormContainer ) { result = ( (FormContainer)component ).getNestedComponent( name ); if ( result != null ) { break; } } else if ( name.equals( component.getName() ) ) { result = component; break; } } } return result; } @Override public void writeAsText( @NotNull final Appendable out, @NotNull final String indent ) throws IOException { final String nextIndent = indent + " "; for ( final FormComponent component : getComponents() ) { component.writeAsText( out, nextIndent ); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy