org.metawidget.faces.component.html.widgetbuilder.HtmlWidgetBuilder Maven / Gradle / Ivy
// Metawidget (licensed under LGPL)
//
// This library 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.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package org.metawidget.faces.component.html.widgetbuilder;
import static org.metawidget.inspector.InspectionResultConstants.*;
import static org.metawidget.inspector.faces.FacesInspectionResultConstants.*;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.faces.application.Application;
import javax.faces.component.UIColumn;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectMany;
import javax.faces.component.ValueHolder;
import javax.faces.component.html.HtmlColumn;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.component.html.HtmlInputSecret;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlInputTextarea;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.html.HtmlSelectBooleanCheckbox;
import javax.faces.component.html.HtmlSelectManyCheckbox;
import javax.faces.component.html.HtmlSelectOneMenu;
import javax.faces.component.html.HtmlSelectOneRadio;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.MethodBinding;
import javax.faces.model.DataModel;
import javax.faces.model.SelectItem;
import org.metawidget.faces.FacesUtils;
import org.metawidget.faces.component.UIMetawidget;
import org.metawidget.faces.component.UIStub;
import org.metawidget.faces.component.html.HtmlMetawidget;
import org.metawidget.faces.component.widgetprocessor.ConverterProcessor;
import org.metawidget.faces.component.widgetprocessor.StandardBindingProcessor;
import org.metawidget.util.ArrayUtils;
import org.metawidget.util.CollectionUtils;
import org.metawidget.util.WidgetBuilderUtils;
import org.metawidget.util.XmlUtils;
import org.metawidget.util.simple.StringUtils;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;
import org.metawidget.widgetbuilder.iface.WidgetBuilderException;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* WidgetBuilder for Java Server Faces environments.
*
* Creates native JSF HTML UIComponents, such as HtmlInputText
and
* HtmlSelectOneMenu
, to suit the inspected fields.
*
* @author Richard Kennard
*/
@SuppressWarnings( "deprecation" )
public class HtmlWidgetBuilder
implements WidgetBuilder {
//
// Private statics
//
private static final String DATATABLE_ROW_ACTION = "dataTableRowAction";
/**
* The number of items in a multi-select lookup at which it should change from being a
* 'lineDirection' to a 'pageDirection' layout. The latter is generally the safer choice, as it
* stops the Metawidget blowing out horizontally.
*/
private static final int SHORT_LOOKUP_SIZE = 3;
//
// Private members
//
private final String mDataTableStyleClass;
private final String[] mDataTableColumnClasses;
private final String[] mDataTableRowClasses;
private final int mMaximumColumnsInDataTable;
//
// Constructor
//
public HtmlWidgetBuilder() {
this( new HtmlWidgetBuilderConfig() );
}
public HtmlWidgetBuilder( HtmlWidgetBuilderConfig config ) {
mDataTableStyleClass = config.getDataTableStyleClass();
mDataTableColumnClasses = config.getDataTableColumnClasses();
mDataTableRowClasses = config.getDataTableRowClasses();
mMaximumColumnsInDataTable = config.getMaximumColumnsInDataTable();
}
//
// Public methods
//
/**
* Purely creates the widget. Does not concern itself with the widget's id, value binding or
* preparing metadata for the renderer.
*
* @return the widget to use in non-read-only scenarios
*/
public UIComponent buildWidget( String elementName, Map attributes, UIMetawidget metawidget ) {
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
// Hidden
if ( TRUE.equals( attributes.get( HIDDEN ) ) ) {
return application.createComponent( UIStub.COMPONENT_TYPE );
}
// Overridden component
UIComponent component = null;
String componentName = attributes.get( FACES_COMPONENT );
if ( componentName != null ) {
component = application.createComponent( componentName );
}
// Action
if ( ACTION.equals( elementName ) ) {
if ( component == null ) {
component = application.createComponent( HtmlCommandButton.COMPONENT_TYPE );
}
( (UICommand) component ).setValue( metawidget.getLabelString( attributes ) );
return component;
}
// Lookup the class
Class> clazz = WidgetBuilderUtils.getActualClassOrType( attributes, String.class );
// Faces Lookups
String facesLookup = attributes.get( FACES_LOOKUP );
if ( facesLookup != null && !"".equals( facesLookup ) ) {
if ( component == null ) {
// UISelectMany...
if ( clazz != null && ( List.class.isAssignableFrom( clazz ) || clazz.isArray() ) ) {
component = application.createComponent( HtmlSelectManyCheckbox.COMPONENT_TYPE );
}
// ...otherwise just a UISelectOne
else {
component = application.createComponent( HtmlSelectOneMenu.COMPONENT_TYPE );
}
}
initFacesSelect( component, facesLookup, attributes, metawidget );
return component;
}
// clazz may be null, if type is symbolic (eg. type="Login Screen")
if ( clazz != null ) {
// Support mandatory Booleans (can be rendered as a checkbox, even though they have a
// Lookup)
if ( component == null && Boolean.class.equals( clazz ) && TRUE.equals( attributes.get( REQUIRED ) ) ) {
return application.createComponent( HtmlSelectBooleanCheckbox.COMPONENT_TYPE );
}
// String Lookups
String lookup = attributes.get( LOOKUP );
if ( lookup != null && !"".equals( lookup ) ) {
if ( component == null ) {
// UISelectMany...
if ( List.class.isAssignableFrom( clazz ) || clazz.isArray() ) {
component = application.createComponent( HtmlSelectManyCheckbox.COMPONENT_TYPE );
}
// ...otherwise just a UISelectOne
else {
component = application.createComponent( HtmlSelectOneMenu.COMPONENT_TYPE );
}
}
initStaticSelect( component, lookup, clazz, attributes, metawidget );
}
// If no component specified yet, pick one
if ( component == null ) {
if ( boolean.class.equals( clazz ) ) {
component = application.createComponent( HtmlSelectBooleanCheckbox.COMPONENT_TYPE );
} else if ( char.class.equals( clazz ) || Character.class.isAssignableFrom( clazz ) ) {
component = application.createComponent( HtmlInputText.COMPONENT_TYPE );
( (HtmlInputText) component ).setMaxlength( 1 );
} else if ( clazz.isPrimitive() ) {
component = application.createComponent( HtmlInputText.COMPONENT_TYPE );
} else if ( Date.class.isAssignableFrom( clazz ) ) {
// Support Date as standard, so that StandardConverterProcessor can attach a
// DateTimeConverter
component = application.createComponent( HtmlInputText.COMPONENT_TYPE );
} else if ( Number.class.isAssignableFrom( clazz ) ) {
component = application.createComponent( HtmlInputText.COMPONENT_TYPE );
} else if ( String.class.equals( clazz ) ) {
if ( TRUE.equals( attributes.get( MASKED ) ) ) {
component = application.createComponent( HtmlInputSecret.COMPONENT_TYPE );
} else if ( TRUE.equals( attributes.get( LARGE ) ) ) {
component = application.createComponent( HtmlInputTextarea.COMPONENT_TYPE );
// XHTML requires the 'cols' and 'rows' attributes be set, even though
// most people override them with CSS widths and heights. The default is
// generally 20 columns by 2 rows
( (HtmlInputTextarea) component ).setCols( 20 );
( (HtmlInputTextarea) component ).setRows( 2 );
} else {
component = application.createComponent( HtmlInputText.COMPONENT_TYPE );
}
}
// Supported Collections
else if ( List.class.isAssignableFrom( clazz ) || DataModel.class.isAssignableFrom( clazz ) || clazz.isArray() ) {
return createDataTableComponent( elementName, attributes, metawidget );
}
// Unsupported Collections
else if ( Collection.class.isAssignableFrom( clazz ) ) {
return application.createComponent( UIStub.COMPONENT_TYPE );
}
}
setMaximumLength( component, attributes );
if ( component != null ) {
return component;
}
}
// Not simple, but don't expand
if ( TRUE.equals( attributes.get( DONT_EXPAND ) ) ) {
return application.createComponent( HtmlInputText.COMPONENT_TYPE );
}
// Nested Metawidget
return null;
}
//
// Protected methods
//
protected void initFacesSelect( UIComponent component, String facesLookup, Map attributes, UIMetawidget metawidget ) {
// (pageDirection is a 'safer' default for anything but short lists)
if ( component instanceof HtmlSelectManyCheckbox ) {
( (HtmlSelectManyCheckbox) component ).setLayout( "pageDirection" );
} else if ( component instanceof HtmlSelectOneRadio ) {
( (HtmlSelectOneRadio) component ).setLayout( "pageDirection" );
}
addSelectItems( component, facesLookup, attributes, metawidget );
}
protected void initStaticSelect( UIComponent component, String lookup, Class> clazz, Map attributes, UIMetawidget metawidget ) {
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
// (pageDirection is a 'safer' default for anything but short lists)
List> values = CollectionUtils.fromString( lookup );
if ( values.size() > SHORT_LOOKUP_SIZE ) {
if ( component instanceof HtmlSelectManyCheckbox ) {
( (HtmlSelectManyCheckbox) component ).setLayout( "pageDirection" );
} else if ( component instanceof HtmlSelectOneRadio ) {
( (HtmlSelectOneRadio) component ).setLayout( "pageDirection" );
}
}
// Convert values of SelectItems (eg. from Strings to ints)...
List> valuesAfterConversion = values;
if ( component instanceof ValueHolder ) {
// ...using the specified converter (call setConverter prematurely so
// we can find out what Converter to use)...
Converter converter = null;
ConverterProcessor processor = metawidget.getWidgetProcessor( ConverterProcessor.class );
if ( processor != null ) {
converter = processor.getConverter( (ValueHolder) component, attributes );
}
// ...(setConverter doesn't do application-wide converters)...
if ( converter == null ) {
// ...(we don't try a 'clazz' converter for a UISelectMany,
// because the 'clazz' will generally be a Collection. setConverter
// will have already tried PARAMETERIZED_TYPE)...
if ( !( component instanceof UISelectMany ) ) {
converter = application.createConverter( clazz );
}
}
// ...if any
if ( converter != null ) {
int size = valuesAfterConversion.size();
List