empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/InputControl.java [51:666]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public abstract class InputControl
{

    private static final Logger log                    = LoggerFactory.getLogger(InputControl.class);

    // StyleClass 
    public static final String  CSS_STYLE_CLASS        = "styleClass";

    // format attributes
    public static final String  FORMAT_NULL            = "null:";
    public static final String  FORMAT_NULL_ATTRIBUTE  = "format:null";
    public static final String  FORMAT_NO_VALUE_STYLES = "noValueStyles";
    /* obsolte from 2024-06-03
    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         = "&nbsp;";

    /*
    public InputControl()
    {
        InputControl.log.info("InputControl of class {} created.", getClass().getName());
    }
    */

    /**
     * DisabledType
     * @author doebele
     */
    public enum DisabledType 
    {
        NO,
        READONLY,
        DISABLED;
    }
    
    /**
     * 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 isModified();
        
        boolean isDisabled(); // disabled or readOnly

        DisabledType getDisabled();
        // 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 final String name;
    private final String cssStyleClass;

    protected InputControl(String name)
    {
        this.name = name;
        this.cssStyleClass = initCssStyleClass();
    }

    public final String getName()
    {
        return this.name;
    }
    
    public final String getCssStyleClass() 
    {
        return this.cssStyleClass;
    }

    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;
        }
    }
    
    /**
     * Returns the formatted value
     * Do not override this function, but override formatValue(Object, ValueInfo) instead.   
     * @param value the value to format
     * @param vi the valueInfo
     * @param escapeHtml when true the value will be escaped for Html
     * @return the formatted value
     */
    public String formatValue(Object value, ValueInfo vi, boolean escapeHtml)
    {
        String s = formatValue(value, vi);
        if (escapeHtml && s!=null && s.length()!=0)
            s = HtmlUtils.getInstance().escapeText(s);
        return s;
    }
    
    /**
     * 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 from ResponseWriter
     */
    public void renderValue(UIComponent comp, String tagName, String styleClass, String tooltip, ValueInfo vi, FacesContext context)
        throws IOException
    {
        // writer
        ResponseWriter writer = context.getResponseWriter();
        // has tag?
        Object value = vi.getValue(true);
        if (tagName!=null)
        {   // write start tag
            writer.startElement(tagName, comp);
            if (!hasFormatOption(vi, FORMAT_NO_VALUE_STYLES))
                styleClass = addDataValueStyle(vi, value, 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(value, vi, writer);
        // has tag?
        if (tagName!=null)
        {   // write end tag
            writer.endElement(tagName);
        }
    }
    
    /**
     * Renders the control value without a surrounding tag (Text only)
     * @param value the value to render
     * @param vi the value info
     * @param writer the output writer
     * @throws IOException from ResponseWriter
     */
    public void renderValue(Object value, ValueInfo vi, ResponseWriter writer)
        throws IOException
    {
        // check if html needs to be escaped
        boolean escapeHtml = !hasFormatOption(vi, "noescape"); 
        String text = formatValue(value, vi, escapeHtml);
        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 from ResponseWriter
     */
    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, PhaseId phaseId)
    {
        List<UIComponent> children = parent.getChildren(); 
        if (children.isEmpty())
            return;
        // update state
        updateInputState(children, ii, context, phaseId);
        // update attached objects
        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);
        // Clear local values
        clearLocalValues(fc, 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;
    }
    
    protected String initCssStyleClass() 
    {
        return StringUtils.concat(TagStyleClass.INPUT_TYPE_PREFIX.get(), name.substring(0,1).toUpperCase(), name.substring(1));
    }
    
    /**
     * adds style attributes related to the current value
     * @param vi the value info
     * @param value the current value
     * @param styleClass the style class
     * @return the data value string
     */
    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() && value instanceof Number)
        {   // 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 boolean isInputValueExpressionEnabled()
    {
        return InputControlManager.isInputValueExpressionEnabled();
    }
    
    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
        boolean evalExpression = !isInputValueExpressionEnabled();
        Object value = ii.getValue(evalExpression);
        if (value instanceof ValueExpression)
        {   // set value expression
            ValueExpression current = input.getValueExpression("value");
            if (current!=value)
                setInputValueExpression(input, (ValueExpression)value, ii);
            // 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 setInputValueExpression(UIInput input, ValueExpression value, InputInfo ii)
    {
        input.setValue(null);
        input.setLocalValueSet(false);
        input.setValueExpression("value", 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);
    }
    
    protected void clearLocalValues(FacesContext context, UIComponent comp)
    {
        // UIInput
        if (comp instanceof UIInput)
        {   // Check LocalValue set 
            UIInput input = (UIInput)comp; 
            if (input.isValid() && input.isLocalValueSet())
            {   // Check ValueExpression
                // @see: UIInput:updateModel(FacesContext context)
                ValueExpression expression = input.getValueExpression("value");
                if (expression != null)
                {   // Reset localValue if ValueExpression is set
                    input.resetValue();
                }
            }
            // we're done here
            return;
        }
        // clearLocalValues of all facets and children of this UIComponent
        if (comp.getFacetCount() > 0)
        {
            for (UIComponent facet : comp.getFacets().values())
            {
                clearLocalValues(context, facet);
            }
        }
        // clear children
        if (comp.getChildCount() > 0)
        {
            for (UIComponent child : comp.getChildren())
            {
                clearLocalValues(context, child);
            }
        }
    }

    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, PhaseId phaseId);
    
    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 (jakarta.faces.component.UIData)
            for (UIComponent p = parent.getParent(); p!=null; p=p.getParent())
            {   // Check whether inside UIData
                if (p instanceof UIData) {
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/InputControl.java [51:666]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public abstract class InputControl
{

    private static final Logger log                    = LoggerFactory.getLogger(InputControl.class);

    // StyleClass 
    public static final String  CSS_STYLE_CLASS        = "styleClass";

    // format attributes
    public static final String  FORMAT_NULL            = "null:";
    public static final String  FORMAT_NULL_ATTRIBUTE  = "format:null";
    public static final String  FORMAT_NO_VALUE_STYLES = "noValueStyles";
    /* obsolte from 2024-06-03
    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         = "&nbsp;";

    /*
    public InputControl()
    {
        InputControl.log.info("InputControl of class {} created.", getClass().getName());
    }
    */

    /**
     * DisabledType
     * @author doebele
     */
    public enum DisabledType 
    {
        NO,
        READONLY,
        DISABLED;
    }
    
    /**
     * 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 isModified();
        
        boolean isDisabled(); // disabled or readOnly

        DisabledType getDisabled();
        // 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 final String name;
    private final String cssStyleClass;

    protected InputControl(String name)
    {
        this.name = name;
        this.cssStyleClass = initCssStyleClass();
    }

    public final String getName()
    {
        return this.name;
    }
    
    public final String getCssStyleClass() 
    {
        return this.cssStyleClass;
    }

    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;
        }
    }
    
    /**
     * Returns the formatted value
     * Do not override this function, but override formatValue(Object, ValueInfo) instead.   
     * @param value the value to format
     * @param vi the valueInfo
     * @param escapeHtml when true the value will be escaped for Html
     * @return the formatted value
     */
    public String formatValue(Object value, ValueInfo vi, boolean escapeHtml)
    {
        String s = formatValue(value, vi);
        if (escapeHtml && s!=null && s.length()!=0)
            s = HtmlUtils.getInstance().escapeText(s);
        return s;
    }
    
    /**
     * 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 from ResponseWriter
     */
    public void renderValue(UIComponent comp, String tagName, String styleClass, String tooltip, ValueInfo vi, FacesContext context)
        throws IOException
    {
        // writer
        ResponseWriter writer = context.getResponseWriter();
        // has tag?
        Object value = vi.getValue(true);
        if (tagName!=null)
        {   // write start tag
            writer.startElement(tagName, comp);
            if (!hasFormatOption(vi, FORMAT_NO_VALUE_STYLES))
                styleClass = addDataValueStyle(vi, value, 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(value, vi, writer);
        // has tag?
        if (tagName!=null)
        {   // write end tag
            writer.endElement(tagName);
        }
    }
    
    /**
     * Renders the control value without a surrounding tag (Text only)
     * @param value the value to render
     * @param vi the value info
     * @param writer the output writer
     * @throws IOException from ResponseWriter
     */
    public void renderValue(Object value, ValueInfo vi, ResponseWriter writer)
        throws IOException
    {
        // check if html needs to be escaped
        boolean escapeHtml = !hasFormatOption(vi, "noescape"); 
        String text = formatValue(value, vi, escapeHtml);
        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 from ResponseWriter
     */
    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, PhaseId phaseId)
    {
        List<UIComponent> children = parent.getChildren(); 
        if (children.isEmpty())
            return;
        // update state
        updateInputState(children, ii, context, phaseId);
        // update attached objects
        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);
        // Clear local values
        clearLocalValues(fc, 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;
    }
    
    protected String initCssStyleClass() 
    {
        return StringUtils.concat(TagStyleClass.INPUT_TYPE_PREFIX.get(), name.substring(0,1).toUpperCase(), name.substring(1));
    }
    
    /**
     * adds style attributes related to the current value
     * @param vi the value info
     * @param value the current value
     * @param styleClass the style class
     * @return the data value string
     */
    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() && value instanceof Number)
        {   // 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 boolean isInputValueExpressionEnabled()
    {
        return InputControlManager.isInputValueExpressionEnabled();
    }
    
    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
        boolean evalExpression = !isInputValueExpressionEnabled();
        Object value = ii.getValue(evalExpression);
        if (value instanceof ValueExpression)
        {   // set value expression
            ValueExpression current = input.getValueExpression("value");
            if (current!=value)
                setInputValueExpression(input, (ValueExpression)value, ii);
            // 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 setInputValueExpression(UIInput input, ValueExpression value, InputInfo ii)
    {
        input.setValue(null);
        input.setLocalValueSet(false);
        input.setValueExpression("value", 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);
    }
    
    protected void clearLocalValues(FacesContext context, UIComponent comp)
    {
        // UIInput
        if (comp instanceof UIInput)
        {   // Check LocalValue set 
            UIInput input = (UIInput)comp; 
            if (input.isValid() && input.isLocalValueSet())
            {   // Check ValueExpression
                // @see: UIInput:updateModel(FacesContext context)
                ValueExpression expression = input.getValueExpression("value");
                if (expression != null)
                {   // Reset localValue if ValueExpression is set
                    input.resetValue();
                }
            }
            // we're done here
            return;
        }
        // clearLocalValues of all facets and children of this UIComponent
        if (comp.getFacetCount() > 0)
        {
            for (UIComponent facet : comp.getFacets().values())
            {
                clearLocalValues(context, facet);
            }
        }
        // clear children
        if (comp.getChildCount() > 0)
        {
            for (UIComponent child : comp.getChildren())
            {
                clearLocalValues(context, child);
            }
        }
    }

    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, PhaseId phaseId);
    
    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) {
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



