blob: fca3d70d1e82c2a25c9af04c14eb8421dfe2eb50 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.empire.jsf2.controls;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIData;
import javax.faces.component.UIInput;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.apache.empire.commons.ObjectUtils;
import org.apache.empire.commons.Options;
import org.apache.empire.commons.StringUtils;
import org.apache.empire.data.Column;
import org.apache.empire.data.DataType;
import org.apache.empire.exceptions.InvalidArgumentException;
import org.apache.empire.exceptions.ItemNotFoundException;
import org.apache.empire.exceptions.UnexpectedReturnValueException;
import org.apache.empire.jsf2.app.TextResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class InputControl
{
private static final Logger log = LoggerFactory.getLogger(InputControl.class);
// format attributes
public static final String FORMAT_NULL = "null:";
public static final String FORMAT_NULL_ATTRIBUTE = "format:null";
public static final String FORMAT_VALUE_STYLES = "valueStyles";
public static final String FORMAT_VALUE_STYLES_ATTRIBUTE = "format:valueStyles";
// HTML-TAGS
public static final String HTML_TAG_DIV = "div";
public static final String HTML_TAG_SPAN = "span";
public static final String HTML_TAG_TABLE = "table";
public static final String HTML_TAG_TR = "tr";
public static final String HTML_TAG_TD = "td";
public static final String HTML_TAG_INPUT = "input";
public static final String HTML_TAG_LABEL = "label";
// HTML-ATTRIBUTES
public static final String HTML_ATTR_ID = "id";
public static final String HTML_ATTR_CLASS = "class";
public static final String HTML_ATTR_STYLE = "style";
public static final String HTML_ATTR_TYPE = "type";
public static final String HTML_ATTR_DISABLED = "disabled";
public static final String HTML_ATTR_CHECKED = "checked";
// HTML
public static String HTML_EXPR_NBSP = " ";
// style classes
public static final String STYLECLASS_REQUIRED = "eInpReq";
public InputControl()
{
InputControl.log.info("InputControl of class {} created.", getClass().getName());
}
/**
* This interface allows access to a value and its metainformation
* used with the renderData function
*/
public interface ValueInfo
{
Column getColumn();
Options getOptions();
Object getValue(boolean evalExpression);
String getFormat(); // Custom Formatting options specific to each InputControl-type
Locale getLocale();
String getText(String key);
TextResolver getTextResolver();
String getStyleClass(String addlStyle);
/*
Object getNullValue();
String getOnclick();
String getOndblclick();
String getCssClass();
String getCssStyle();
String getId();
*/
boolean isInsideUIData();
}
/**
* This interface extends the value information by information about the input control
* used with the renderInput function
*/
public interface InputInfo extends ValueInfo
{
// perform action
void setValue(Object value);
void validate(Object value);
boolean isRequired();
boolean isDisabled(); // disabled or readOnly
boolean isFieldReadOnly(); // not disabled only readOnly (for input[type=text] only!)
// input
String getInputId();
boolean hasError();
/*
String getName();
String getTabindex();
String getAccesskey();
boolean isValid(); // Indicates whether the value supplied is valid
String getOnchange();
String getOnfocus();
String getOnblur();
*/
Object getAttribute(String name); /* gets tag attribute only */
Object getAttributeEx(String name); /* check Column attributes too, and resolves references to other columns. */
}
private String name;
protected InputControl(String name)
{
this.name = name;
}
public final String getName()
{
return this.name;
}
public String getLabelForId(InputInfo ii)
{
return ii.getInputId();
}
/**
* Flag indicating whether child components are being created
*/
private boolean creatingComponents = false;
public boolean isCreatingComponents()
{
return this.creatingComponents;
}
/* createInput */
public void createInput(UIComponent comp, InputInfo ii, FacesContext context)
{ // createInputComponents
List<UIComponent> children = comp.getChildren();
try {
this.creatingComponents = true;
createInputComponents(comp, ii, context, children);
// check
boolean resetChildId = ii.isInsideUIData();
if (resetChildId && log.isDebugEnabled())
{ // Debug-Info only
UIComponent c1 = comp.getChildren().get(0);
String clientId = c1.getClientId();
log.debug("Performing ChildId-reset for {}", clientId);
}
// add attached objects
UIComponent parent = comp;
while (!(parent instanceof UIInput))
parent = parent.getParent();
for (UIComponent child : children)
{ // reset child-id
if (resetChildId && child.getId()!=null)
child.setId(child.getId());
// check type
if (!(child instanceof ClientBehaviorHolder))
continue;
// add attached objects
addAttachedObjects(parent, context, ii, ((UIComponentBase)child));
}
} finally {
this.creatingComponents = false;
}
}
/**
* Renders the control value with a surrounding HTML tag, if a tagName is supplied
* @param comp the JSF component
* @param tagName the tag name of the HTML wrapper tag (optional)
* @param styleClass the style class of the HTML wrapper tag (optional)
* @param tooltip the title of the HTML wrapper tag (optional)
* @param vi the value info
* @param context the FacesContext
* @throws IOException
*/
public void renderValue(UIComponent comp, String tagName, String styleClass, String tooltip, ValueInfo vi, FacesContext context)
throws IOException
{
// writer
ResponseWriter writer = context.getResponseWriter();
// has tag?
if (tagName!=null)
{ // write start tag
writer.startElement(tagName, comp);
if (hasFormatOption(vi, FORMAT_VALUE_STYLES, FORMAT_VALUE_STYLES_ATTRIBUTE))
styleClass = addDataValueStyle(vi, vi.getValue(true), styleClass);
if (StringUtils.isNotEmpty(styleClass))
writer.writeAttribute("class", styleClass, null);
if (StringUtils.isNotEmpty(tooltip))
writer.writeAttribute("title", tooltip, null);
// style
Object style = comp.getAttributes().get("style");
if (style!=null)
writer.writeAttribute("style", style, null);
}
// render Value
renderValue(vi, writer);
// has tag?
if (tagName!=null)
{ // write end tag
writer.endElement(tagName);
}
}
/**
* Renders the control value without a surrounding tag (Text only)
* @param vi the value info
* @param writer the output writer
* @throws IOException
*/
public void renderValue(ValueInfo vi, ResponseWriter writer)
throws IOException
{
String text = formatValue(vi);
writer.append((StringUtils.isEmpty(text) ? HTML_EXPR_NBSP : text));
}
/**
* Renders the input element(s) for editing the underlying record value
* @param comp the JSF component
* @param ii the input info
* @param context the FacesContext
* @throws IOException
*/
public void renderInput(UIComponent comp, InputInfo ii, FacesContext context)
throws IOException
{
// Encode all
for (UIComponent child : comp.getChildren())
{ // render
if (child.isRendered())
child.encodeAll(context);
}
}
public void updateInputState(UIComponent parent, InputInfo ii, FacesContext context, boolean setValue)
{
List<UIComponent> cl = parent.getChildren();
if (cl.isEmpty())
return;
updateInputState(cl, ii, context, setValue);
// update attached objects
List<UIComponent> children = parent.getChildren();
while (!(parent instanceof UIInput))
parent = parent.getParent();
for (UIComponent child : children)
{ // check type
if (!(child instanceof ClientBehaviorHolder))
continue;
// update attached objects
updateAttachedObjects(parent, context, ii, ((UIComponentBase)child));
}
}
public void postUpdateModel(UIComponent comp, InputInfo ii, FacesContext fc)
{
UIInput input = getInputComponent(comp);
if (input == null)
return; /* May want to override this */
// Clear submitted value
clearSubmittedValue(input);
}
public Object getInputValue(UIComponent comp, InputInfo ii, boolean submitted)
{
UIInput input = getInputComponent(comp);
if (input == null)
{ // throw new ObjectNotValidException(this);
return null; // ignore
}
// Get value from Input
Object value;
if (submitted)
{ // check disabled
if (ii.isDisabled())
{ // Ignore submitted value
InputControl.log.debug("Ignoring submitted value for disabled field {}.", ii.getColumn().getName());
input.setSubmittedValue(null);
// throw new FieldIsReadOnlyException(ii.getColumn());
return null;
}
// get submitted value
value = input.getSubmittedValue();
if (value == null && input.isLocalValueSet()) // required for MyFaces!
{ // take local value
if (log.isDebugEnabled())
log.debug("No submitted value but local value available for InputComponent {}. Local value is '{}'", input.getClientId(), input.getLocalValue());
value = input.getLocalValue();
if (value == null)
{ // Empty-String
value = "";
}
}
// debug
if (log.isDebugEnabled())
log.debug("Submitted value for {} is {}", comp.getClientId(), value);
}
else
{ // the current value
value = input.getValue();
}
return value;
}
public Object getConvertedValue(UIComponent comp, InputInfo ii, Object submittedValue)
{
// Value supplied?
if (submittedValue != null)
{ // Save submitted value in request-map
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> reqMap = fc.getExternalContext().getRequestMap();
// Save submitted value
UIInput input = getInputComponent(comp);
String clientId = input.getClientId();
if (reqMap.containsKey(clientId))
{ Object oldValue = reqMap.get(clientId);
if (ObjectUtils.compareEqual(oldValue, submittedValue)==false)
InputControl.log.debug("Replacing submitted value from '{}' to '{}' for " + clientId, oldValue, submittedValue);
}
reqMap.put(clientId, submittedValue);
}
// Convert
if ((submittedValue instanceof String) && ((String) submittedValue).length() > 0)
{ // debug
if (log.isDebugEnabled())
log.debug("Converting value for colum {}. Value is {}", ii.getColumn().getName(), submittedValue);
// parse
return parseInputValue((String) submittedValue, ii);
}
return submittedValue;
}
/**
* adds style attributes related to the current value
* @param vi
* @param value the current value
* @param styleClass
* @return
*/
protected String addDataValueStyle(ValueInfo vi, Object value, String styleClass)
{
DataType dataType = vi.getColumn().getDataType();
if (ObjectUtils.isEmpty(value))
{ // Null
styleClass += " eValNull";
}
else if (dataType.isNumeric())
{ // Check negative
if (ObjectUtils.getLong(value)<0)
styleClass += " eValNeg";
}
return styleClass;
}
protected void addAttachedObjects(UIComponent parent, FacesContext context, InputInfo ii, UIComponentBase inputComponent)
{
InputAttachedObjectsHandler aoh = InputControlManager.getAttachedObjectsHandler();
if (aoh!=null)
aoh.addAttachedObjects(parent, context, ii.getColumn(), inputComponent);
}
protected void updateAttachedObjects(UIComponent parent, FacesContext context, InputInfo ii, UIComponentBase inputComponent)
{
InputAttachedObjectsHandler aoh = InputControlManager.getAttachedObjectsHandler();
if (aoh!=null)
aoh.updateAttachedObjects(parent, context, ii.getColumn(), inputComponent);
}
protected UIInput getFirstInput(List<UIComponent> compList)
{
for (int i=0; i<compList.size(); i++)
{
UIComponent child = compList.get(i);
if (child instanceof UIInput)
return ((UIInput)child);
}
throw new ItemNotFoundException("UIInput");
}
protected void setInputValue(UIInput input, InputInfo ii)
{
// Restore submitted value
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> reqMap = fc.getExternalContext().getRequestMap();
String clientId = input.getClientId();
if (reqMap.containsKey(clientId))
{ // Set the local value from the request map
Object value = reqMap.get(clientId);
if (input.isLocalValueSet() == false)
input.setSubmittedValue(value);
// change the style
addRemoveValueNullStyle(input, ObjectUtils.isEmpty(value));
return;
}
else if (input.getSubmittedValue() != null) // && FacesUtils.isClearSubmittedValues(fc)
{ // Clear submitted value
if (InputControl.log.isDebugEnabled())
InputControl.log.debug("clearing submitted value for {}. value is {}.", ii.getColumn().getName(), input.getSubmittedValue());
input.setSubmittedValue(null);
}
/* -------------------------------------- */
// Assign value
Object value = ii.getValue(false);
if (value instanceof ValueExpression)
{
input.setValue(null);
input.setLocalValueSet(false);
input.setValueExpression("value", (ValueExpression) value);
// Object check = ((ValueExpression)value).getValue(FacesContext.getCurrentInstance().getELContext());
// log.info("Expression value is {}.", check);
}
else
{ // Set the value
value = formatInputValue(value, ii);
input.setValue(value);
// change the style
addRemoveValueNullStyle(input, ObjectUtils.isEmpty(value));
}
}
protected void clearSubmittedValue(UIInput input)
{
input.setSubmittedValue(null);
// check Request Map
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> reqMap = fc.getExternalContext().getRequestMap();
String clientId = input.getClientId();
if (reqMap.containsKey(clientId))
reqMap.remove(clientId);
}
/**
* Override this to format a value for output
*
* @param value
* @param ii
* @return
*/
protected Object formatInputValue(Object value, InputInfo ii)
{
return value;
}
protected Object parseInputValue(String value, InputInfo ii)
{
return value;
}
/* validate
public boolean validateValue(UIComponent comp, InputInfo ii, FacesContext context)
{
UIInput input = getInputComponent(comp);
if (input==null)
throw new ObjectNotValidException(this);
input.validate(context);
if (input.isValid()==false)
return false;
/-*
Object value = getInputValue(comp, ii, context, false);
try {
ii.getColumn().validate(value);
Object xxx = input.getLocalValue();
// Wert geändert?
Object previous = ii.getValue();
if (ObjectUtils.compareEqual(value, previous)==false)
{
comp.queueEvent(new ValueChangeEvent(comp, previous, value));
// Wert setzen
ii.setValue(value, true);
}
return true;
} catch(Exception e) {
// Add Error Messgae
String text = e.getLocalizedMessage();
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, text, text);
context.addMessage(comp.getClientId(), msg);
// Invalid
ii.setValue(value, false);
return false;
}
*-/
return true;
}
*/
/* Input helpers */
protected abstract void createInputComponents(UIComponent parent, InputInfo ii, FacesContext context, List<UIComponent> compList);
protected abstract void updateInputState(List<UIComponent> compList, InputInfo ii, FacesContext context, boolean setValue);
protected UIInput getInputComponent(UIComponent parent)
{
// default implementation
int count = parent.getChildCount();
if (count < 1)
return null;
// find the UIInput component (only one allowed here)
UIInput inp = null;
for (int i = 0; i < count; i++)
{ // check UIInput
UIComponent comp = parent.getChildren().get(i);
if (comp instanceof UIInput)
{ if (inp != null)
throw new UnexpectedReturnValueException(comp, "comp.getChildren().get(" + String.valueOf(i) + ")");
inp = (UIInput) comp;
}
}
// No UIInput found
if (inp == null)
{ // Check whether inside a DataTable (javax.faces.component.UIData)
for (UIComponent p = parent.getParent(); p!=null; p=p.getParent())
{ // Check whether inside UIData
if (p instanceof UIData) {
log.info("Ignore value component for id '{}' inside a DataTable (javax.faces.component.UIData)", parent.getClientId());
return null;
}
}
// Should not happen!
throw new UnexpectedReturnValueException(null, "comp.getChildren().get()");
}
// found one
return inp;
}
protected String getInputStyleClass(InputInfo ii, String additonalStyle)
{
return ii.getStyleClass(additonalStyle);
}
protected void setInputStyleClass(UIInput input, String cssStyleClass)
{
input.getAttributes().put("styleClass", cssStyleClass);
}
protected void copyAttributes(UIComponent parent, InputInfo ii, UIInput input, String additonalStyle)
{
String inputId = ii.getInputId();
if (StringUtils.isNotEmpty(inputId))
{
input.getAttributes().put("id", inputId);
}
// set style class
String styleClass = getInputStyleClass(ii, additonalStyle);
setInputStyleClass(input, styleClass);
// copy standard attributes
copyAttribute(ii, input, "style");
copyAttribute(ii, input, "tabindex");
copyAttribute(ii, input, "onchange");
copyAttribute(ii, input, "onfocus");
copyAttribute(ii, input, "onblur");
copyAttribute(ii, input, "onkeydown");
copyAttribute(ii, input, "onkeyup");
copyAttribute(ii, input, "onclick");
// immediate
Object immediate = ii.getAttribute("immediate");
if (immediate != null && ObjectUtils.getBoolean(immediate))
{
InputControl.log.warn("Immediate attribute is not yet supported for {}!", ii.getColumn().getName());
// input.setImmediate(true);
}
// validator
// input.addValidator(new ColumnValueValidator(ii.getColumn()));
}
protected final void copyAttributes(UIComponent parent, InputInfo ii, UIInput input)
{
copyAttributes(parent, ii, input, (ii.isRequired() ? STYLECLASS_REQUIRED : null));
}
protected void copyAttribute(InputInfo ii, UIInput input, String name)
{
if (ii == null)
throw new InvalidArgumentException("InputInfo", ii);
// get Attribute
Object value = ii.getAttribute(name);
if (value == null)
value = ii.getColumn().getAttribute(name);
if (value != null)
input.getAttributes().put(name, String.valueOf(value));
}
public void addRemoveValueNullStyle(UIInput input, boolean nullValue)
{
addRemoveStyle(input, " eValNull", nullValue);
}
public void addRemoveDisabledStyle(UIInput input, boolean disabled)
{
addRemoveStyle(input, " eInpDis", disabled);
}
public void addRemoveInvalidStyle(UIInput input, boolean invalid)
{
addRemoveStyle(input, " eInvalid", invalid);
}
public void addRemoveStyle(UIInput input, String styleName, boolean setStyle)
{
String styleClass = StringUtils.toString(input.getAttributes().get("styleClass"), "");
boolean hasStyle = (styleClass.indexOf(styleName) >= 0);
if (setStyle == hasStyle)
return; // Nothing to do
// Special IceFaces patch
if (styleClass.endsWith("-dis"))
styleClass = styleClass.substring(0, styleClass.length() - 4);
// add or remove disabled style
if (setStyle)
styleClass += styleName;
else
styleClass = styleClass.replace(styleName, "");
// add Style
setInputStyleClass(input, styleClass);
}
/**
* Returns the value formated as a string
* this is a simple default implementation that does no type-secific formatting
* Derived classes may override formatString an provide further formmatting
* see TextInputControl for details
*
* @param value
* the value to be formatted
* @param vi
* Meta-information about the value
* @return the formatted value
*/
protected String formatValue(Object value, ValueInfo vi)
{
// For Enums use toString() to retrieve Value
if (value != null && value.getClass().isEnum() && !hasFormatOption(vi, "nolookup"))
{ // Handle enum
String text = ((Enum<?>) value).toString();
if (text != null)
return vi.getText(text);
// Error
InputControl.log.error("The enum '" + ((Enum<?>) value).name() + "' has no text!");
}
// Lookup and Print value
Options options = vi.getOptions();
if (options != null && !options.isEmpty() && !hasFormatOption(vi, "nolookup"))
{ // Check for Options
String text = options.get(value);
if (text != null)
return vi.getText(text);
// Error
InputControl.log.error("The element '" + String.valueOf(value) + "' is not part of the supplied option list.");
}
// value
if (value == null)
value = getFormatOption(vi, InputControl.FORMAT_NULL, InputControl.FORMAT_NULL_ATTRIBUTE);
// Convert to String
String s = StringUtils.toString(value, "");
if (hasFormatOption(vi, "noencode"))
return s;
// Encode Html
return escapeHTML(s);
}
/**
* Returns the value formated as a string
* This is a shortcut for formatString(vi.getValue(), vi)
* Derived classes may override formatString
*/
protected final String formatValue(ValueInfo vi)
{
// boolean hasError = ((vi instanceof InputInfo) && !((InputInfo)vi).isValid());
return formatValue(vi.getValue(true), vi);
}
/**
* escapes a String for html
*
* @param text
* @return the escaped html String
*/
protected String escapeHTML(String text)
{
if (text==null || text.length()==0)
return text;
// &amp;
if (text.indexOf('&')>=0)
text = StringUtils.replaceAll(text, "&", "&amp;");
// &lt;
if (text.indexOf('<')>=0)
text = StringUtils.replaceAll(text, "<", "&lt;");
// &gt;
if (text.indexOf('>')>=0)
text = StringUtils.replaceAll(text, ">", "&gt;");
// done
return text;
}
/**
* checks if a particular formating option has been specified.
*
* @param vi
* the value info
* @param option
* the formating option to check
* @return true if the requested formating option has been specified or false otherwise
*/
protected boolean hasFormatOption(ValueInfo vi, String option)
{
String format = vi.getFormat();
return (format != null ? format.indexOf(option) >= 0 : false);
}
protected boolean hasFormatOption(ValueInfo vi, String option, String columnAttributeName)
{
if (hasFormatOption(vi, option))
return true;
// column format provided?
Column column = vi.getColumn();
return (column!=null ? !ObjectUtils.isEmpty(column.getAttribute(columnAttributeName)) : false);
}
protected String getFormatOption(ValueInfo vi, String option)
{
// Is unit supplied with format
String format = vi.getFormat();
if (format == null)
return null;
// Check for option
int beg = format.indexOf(option);
if (beg < 0)
return null;
// Find
beg = beg + option.length();
int end = format.indexOf(';', beg + 1);
if (end < beg)
return format.substring(beg);
// The cbValue
return format.substring(beg, end);
}
protected Object getFormatOption(ValueInfo vi, String option, String columnAttributeName)
{
// direct format provided?
String format = getFormatOption(vi, option);
if (format!=null)
return format;
// column format provided?
Column column = vi.getColumn();
return (column!=null ? column.getAttribute(columnAttributeName) : null);
}
protected String getFormatString(ValueInfo vi, String option, String columnAttributeName)
{
return StringUtils.toString(getFormatOption(vi, option, columnAttributeName));
}
protected int getFormatInteger(ValueInfo vi, String option, String columnAttributeName)
{
return ObjectUtils.getInteger(getFormatOption(vi, option, columnAttributeName));
}
}