empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/ControlTag.java [261:900]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    protected void saveState()
    {
        // getStateHelper().put(inpControlPropName, control);
        // getStateHelper().put(inputInfoPropName, inpInfo);
        getStateHelper().put(ControlTag.readOnlyState, (this.inpInfo == null));
    }

    protected boolean initInputState(FacesContext context)
    {
        // Custom input?
        if (getChildCount() <= 1)
            return false; // Ignore for custom input
        // Check read-Only
        Boolean ros = (Boolean) getStateHelper().get(ControlTag.readOnlyState);
        if (ros != null && ros.booleanValue())
            return false;
        // Must have children        
        if (getChildCount() == 0)
        {   log.warn("ControlTag '{}' has no children. encodeBegin may not have been called yet.", getClientId());
            return false;
        }
        // control = ;
        helper.prepareData();
        this.control = helper.getInputControl();
        this.inpInfo = helper.getInputInfo(context);
        return (this.control != null && this.inpInfo != null);
    }

    @Override
    public String getId()
    {
        String compId = super.getId();
        // Mojarra-Patch since Id might have been set to "null"
        if ("null".equals(compId))
            compId =  helper.completeInputTagId(null);
        // done
        return compId;
    }
    
    @Override
    public void setId(String id) 
    {   // complete
        id = helper.completeInputTagId(id); 
        // set
        super.setId(id);
    }

    @Override
    public void setParent(UIComponent parent)
    {
        super.setParent(parent);
        // check whether already set
        if (helper.hasComponentId())
            return;
        /*
         * Attention: Only works if FormGrid is a direct parent of the Control.
         * Does not work, if other components are between the Control and the FormGrid.
         */
        this.renderInfo = helper.getControlRenderInfo();
        if (this.renderInfo!=null && this.renderInfo.AUTO_CONTROL_ID!=null) {
            // Auto set component Id
            setId(this.renderInfo.AUTO_CONTROL_ID.toString());
            log.debug("Auto-Setting compontent id for Control to {}", this.getId());
        }
    }

    /**
     * remember original clientId
     * necessary only inside UIData
     */
    protected String treeClientId = null;
    
    @Override
    public boolean visitTree(VisitContext visitContext, VisitCallback callback) 
    {
        FacesContext context = visitContext.getFacesContext();
        treeClientId = this.getClientId(context);
        return super.visitTree(visitContext, callback);
    }

    @Override
    public String getClientId(FacesContext context)
    {
        // Check if dynamic components are being created
        // Check for "this.creatingComponents" has been removed on 2025-03-18
        if (this.treeClientId!=null && control!=null && control.isCreatingComponents())
        {   // return the original tree client id
            return treeClientId; 
        }
        // default behavior
        return super.getClientId(context);
    }

    @Override
    public boolean getRendersChildren()
    {
        // render children ourselves using 
        //     encodeChildren(FacesContext context)
        return true;
    }

    /*
     * Add label and input components when the view is loaded for the first time
     */
    @Override
    public void encodeBegin(FacesContext context)
        throws IOException
    {
        super.encodeBegin(context);

        // init
        helper.encodeBegin();

        // set InputControl
        if (this.control==null)
            this.control = helper.getInputControl();
        
        // renderInfo
        if (this.renderInfo==null) {
            this.renderInfo=helper.getControlRenderInfo();
            if (this.renderInfo==null)
                this.renderInfo=ControlRenderInfo.getDefault();  // No FormGrid found. Use Default!
        }

        // Check visiblity
        this.controlVisible = helper.isVisible();

        // Render
        if (this.controlVisible && renderInfo.CONTROL_TAG!=null)
        {   // control wrapper tag
            ResponseWriter writer = context.getResponseWriter();
            writer.startElement(renderInfo.CONTROL_TAG, this);
            // render id
            helper.writeComponentId(writer, false);
            // style class
            String controlClass = helper.getTagAttributeStringEx("controlClass", true);
            String styleClass   = helper.getTagAttributeString(InputControl.CSS_STYLE_CLASS);
            String contextClass = helper.getContextStyleClass(); 
            helper.writeStyleClass(writer, TagStyleClass.CONTROL.get(), controlClass, styleClass, contextClass);
        }
        else if (!this.controlVisible)
        {   // render placeholder
            renderInfo.renderPlaceholder(context, this);
        }

        // custom input?
        boolean customInput = isCustomInput();
        
        // Create LabelSeparatorComponent
        ControlSeparatorComponent labelSepTag = null;
        if (true)
        {   // Create Label Separator Tag
            if (getChildCount() > 0)
                labelSepTag = (ControlSeparatorComponent) getChildren().get(0);
            if (labelSepTag == null)
            {   labelSepTag = new LabelSeparatorComponent();
                getChildren().add(labelSepTag);
                helper.resetComponentId(labelSepTag);
            }
            // encode
            labelSepTag.setRendered(this.controlVisible);
            encodeLabel(context, labelSepTag, customInput);
        }
        
        // Create InputSeparatorComponent
        ControlSeparatorComponent inputSepTag = null;
        if (!customInput)
        {   // Create Input Separator Tag
            if (getChildCount() > 1)
                inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
            if (inputSepTag == null)
            {   inputSepTag = new InputSeparatorComponent();
                getChildren().add(inputSepTag);
                helper.resetComponentId(inputSepTag);
            }
            // encode
            inputSepTag.setRendered(this.controlVisible);
            encodeInput(context, inputSepTag);
        }
        else if (getChildCount() > 1) 
        {   // custom has changed. 
            getChildren().remove(1);
        }
        
        // Encode now
        if (labelSepTag!=null)
            labelSepTag.encodeAll(context);
        if (inputSepTag!=null)
            inputSepTag.encodeAll(context);
        
        // done
        saveState();
    }

    @Override
    public void encodeChildren(FacesContext context)
        throws IOException
    {
        /*
         * Composite component renderer must render children for custom components
         *   <cc:implementation>
         *       <c:choose>
         *           <c:when test="#{cc.attrs.custom == true}">
         *               <cc:insertChildren/>
         *           </c:when>
         *       </c:choose>
         *   </cc:implementation>
         */
        if (this.controlVisible && isCustomInput())
        {   // Custom input
            ResponseWriter writer = null;
            String tagName  = renderInfo.INPUT_WRAPPER_TAG;
            if (tagName!=null && tagName.length()>0)
            {   // render tag
                writer = context.getResponseWriter();
                writer.startElement(tagName, this);
                // style Class
                String inpClass = helper.getTagAttributeStringEx("inputClass", true);
                helper.writeStyleClass(writer, TagStyleClass.CONTROL_INPUT.get(), inpClass);
                // write more
                String colSpan = tagName.equalsIgnoreCase(InputControl.HTML_TAG_TD) ? helper.getTagAttributeStringEx("colspan", false) : null;
                if (colSpan!=null)
                    writer.writeAttribute("colspan", colSpan, null);
            }
            // encode children
            super.encodeChildren(context);
            // end of element
            if (writer!=null)
            {   // render tag
                writer.endElement(tagName);
            }
        }
    }

    @Override
    public void encodeEnd(FacesContext context)
        throws IOException
    {
        // encodeEnd
        if (this.controlVisible && renderInfo.CONTROL_TAG!=null)
        {   // control wrapper tag
            ResponseWriter writer = context.getResponseWriter();
            writer.endElement(renderInfo.CONTROL_TAG);
        }
        // call base
        super.encodeEnd(context);
    }

    public boolean isCustomInput()
    {
        // detect from attribute
        Object custom = getAttributes().get("custom");
        if (custom != null)
            return ObjectUtils.getBoolean(custom);
        return false;
    }
    
    /**
     * called from LabelSeparatorComponent
     * @param context the faces context
     * @param parent the LabelSeparatorComponent
     * @param customInput flag whether a custom input component is used
     * @throws IOException from ResponseWriter
     */
    protected void encodeLabel(FacesContext context, UIComponentBase parent, boolean customInput)
        throws IOException
    {
        UIComponent labelFacet = getFacet("label");
        if (labelFacet!=null)
        {   // label facet
            return; // Noting to do here
        }
        else
        {   // render components
            HtmlOutputLabel labelComponent = null;
            if (parent.getChildCount()==0)
            {   // Create components
                String forInput = (customInput ? helper.getTagAttributeString("for") : "*");
                // createLabelComponent 
                String styleClass = TagStyleClass.LABEL.get();
                labelComponent = helper.createLabelComponent(context, forInput, styleClass, null, getColon());
                parent.getChildren().add(0, labelComponent);
                helper.resetComponentId(labelComponent);
            }
            else if (this.controlVisible)
            {
                labelComponent = (HtmlOutputLabel) parent.getChildren().get(0);
                // update
                helper.updateLabelComponent(context, labelComponent, null);
            }
        }
    }

    /**
     * called from InputSeparatorComponent
     * @param context the faces context
     * @param parent the InputSeparatorComponent
     * @throws IOException from ResponseWriter
     */
    protected void encodeInput(FacesContext context, UIComponent parent)
        throws IOException
    {
        // check children
        int count = parent.getChildCount();
        UIComponent valueComp = (count>0 ? parent.getChildren().get(count-1) : null);
        boolean resetChildId = (count==0);
        // continue
        this.inpInfo = helper.getInputInfo(context);
        // set required
        super.setRequired(this.controlVisible && helper.isValueRequired());
        // create Input Controls
        // boolean recordReadOnly = helper.isRecordReadOnly();
        if (count==0)
        {   // Create components
            control.createInput(parent, inpInfo, context);
            // create Value Component
            if (valueComp == null)
            {   // create ValueOutputComponent
                valueComp = new ValueOutputComponent();
                parent.getChildren().add(valueComp);
            }
        }
        else if (this.controlVisible)
        {   // Update
            control.updateInputState(parent, inpInfo, context, context.getCurrentPhaseId());
        }
        // set rendered
        boolean renderValue = helper.isRenderValueComponent();
        List<UIComponent> children = parent.getChildren();
        for (UIComponent child : children)
        {   // reset child id
            if (resetChildId && child.getId()!=null)
                child.setId(child.getId());
            // set rendered
            boolean valueOutput = (child instanceof ValueOutputComponent);
            child.setRendered((valueOutput ? renderValue : !renderValue));
        }
    }
    
    public void renderInput(FacesContext context, UIComponent parent, boolean allowWrapper)
        throws IOException
    {
        if (allowWrapper)
        {   // render separate wrapper tag
            boolean renderValue = helper.isRenderValueComponent();
            // wrapperTag
            String wrapperTag = helper.writeWrapperTag(context, false, renderValue); 
            // render
            control.renderInput(parent, inpInfo, context);
            // wrapperTagEnd
            if (wrapperTag!=null)
            {   // control wrapper tag
                ResponseWriter writer = context.getResponseWriter();
                writer.endElement(wrapperTag);
            }
        }
        else
        {   // the InputSeparatorComponent acts as wrapper
            control.renderInput(parent, inpInfo, context);
        }
    }

    @Override
    public Object getValue()
    {
        // check for record
        if (helper.getRecord() != null)
            return helper.getDataValue(true);
        // default
        Object value = super.getValue();
        return value;
    }
    
    @Override
    public Object getSubmittedValue()
    {   // Already detected
        if (this.submittedValueDetected)
            return this.submittedValue; // already detected
        // Check children and custom input
        if (getChildCount() <= 1)
            return null; // No input available
        // Check state
        if (this.control == null || this.inpInfo == null || helper.isReadOnly())
            return null;
        // get Input Value
        ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
        // get Input Value
        this.submittedValue = this.control.getInputValue(inputSepTag, this.inpInfo, true);
        this.submittedValueDetected = true;
        return this.submittedValue; 
    }
    
    @Override
    public void setSubmittedValue(Object submittedValue)
    {
        // super.setSubmittedValue(submittedValue);
        this.submittedValue = submittedValue;
        this.submittedValueDetected = true;
    }
    
    @Override
    protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
        throws ConverterException
    { // Check state
        if (this.control == null || this.inpInfo == null || helper.isReadOnly())
            return null;
        // Get Input Tag
        if (getChildCount() <= 1)
            return null;
        // convert value
        try {
            // parse and convert value
            ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
            return this.control.getConvertedValue(inputSepTag, this.inpInfo, newSubmittedValue);
        } catch (Exception e) {
            // Add error message
            FacesMessage msg = helper.getFieldValueErrorMessage(context, e, newSubmittedValue);
            throw new ConverterException(msg);
        }
    }

    @Override
    public void processDecodes(FacesContext context) 
    {
        // check UI-Data
        if (getChildCount()>1 && helper.isInsideUIData())
        {   // update input state
            updateControlInputState(context);
            // Set Rendered for children (changed 2024-04-05)
            // boolean readOnly = helper.isRecordReadOnly();
            boolean renderValue = helper.isRenderValueComponent();
            setRenderInput(!renderValue);
        }
        // default
        super.processDecodes(context);
    }

    @Override
    public void processValidators(FacesContext context)
    {
        resetSubmittedValue();
        // check UI-Data
        if (getChildCount()>1 && helper.isInsideUIData())
        {   // update input state
            updateControlInputState(context);
            // Only if value was submitted
            boolean hasValue = (getSubmittedValue()!=null);
            setRenderInput(hasValue);
        }
        // process all validators (including children)
        super.processValidators(context);
    }

    @Override
    public void validate(FacesContext context)
    {   // init state
        if (initInputState(context) == false)
            return;
        // get submitted value and validate
        if (log.isDebugEnabled())
            log.debug("Validating input for {}.", this.inpInfo.getColumn().getName());
        // reset again (important!)
        resetSubmittedValue();
        // Will internally call getSubmittedValue() and validateValue()
        this.valueValidated = false;
        super.validate(context);
        // post check
        if (isValid() && !this.valueValidated && !helper.isReadOnly() && !helper.isDisabled())
        {   // New since 2024-04-04
            // Validate Record value
            Object value = helper.getDataValue(true);
            log.warn("No Submitted value for {}. Validating Record value of \"{}\" instead.", helper.getColumnName(), value);
            if (value!=null)
                validateValue(context, value);
        }
    }

    @Override
    public void validateValue(FacesContext context, Object value)
    {   // Validate value
        try
        {   // Check state
            if (this.inpInfo == null || !isValid())
                return;
            // validateValue
            if (helper.beginValidateValue(context, value))
                inpInfo.validate(value);
            setValid(true);
            // don't call base class!
            // super.validateValue(context, value);

        } catch (Exception e) {
            // Add error message
            helper.addFieldValueErrorMessage(context, e, value);
            setValid(false);

        } finally {
            this.valueValidated = true;
        }
    }
    
    @Override
    public void processUpdates(FacesContext context)
    {
        // Added for EMPIREDB-436 on 2024-09-27
        if (getChildCount() <= 1)
        {   // custom input
            super.processUpdates(context);
            return;
        }
        try
        {   
            // setCachedFacesContext(context);
            pushComponentToEL(context, this);
            if (!isRendered())
            {
                return;
            }
            try
            {
                updateModel(context);
            }
            catch (RuntimeException e)
            {
                context.renderResponse();
                throw e;
            }
            if (!isValid())
            {
                context.renderResponse();
            }
        }
        finally
        {
            // setCachedFacesContext(null);
            popComponentFromEL(context);
        }
    }

    @Override
    public void updateModel(FacesContext context)
    {
        if (initInputState(context) == false)
            return;
        // No Action
        if (!isValid() || !isLocalValueSet())
            return;
        // updateModel
        Object value = getLocalValue();
        if (helper.beginUpdateModel(context, value)) 
        {   // don't call base class!
            // super.updateModel(context);
            log.debug("Updating model input for {}.", this.inpInfo.getColumn().getName());
            this.inpInfo.setValue(value);
        }
        setValue(null);
        setLocalValueSet(false);
        // Post update
        if (getChildCount() <= 1)
            return; // should never come here
        ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
        this.control.postUpdateModel(inputSepTag, this.inpInfo, context);
    }

    public InputControl getInputControl()
    {
        return this.control;
    }

    public Column getInputColumn()
    {
        return helper.getColumn();
    }

    public boolean isInputReadOnly()
    {
        return helper.isRecordReadOnly();
    }

    public boolean isInputRequired()
    {
        return helper.isValueRequired();
    }
    
    protected void resetSubmittedValue()
    {
        submittedValueDetected = false;
        submittedValue = null;
    }
    
    protected void updateControlInputState(FacesContext context)
    {
        // custom input?
        if (getChildCount() <= 1)
            return; 
        // get control
        helper.prepareData();
        if (control==null)
            control = helper.getInputControl();
        if (inpInfo==null)
            inpInfo = helper.getInputInfo(context);
        // update control
        ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
        control.updateInputState(inputSepTag, inpInfo, context, context.getCurrentPhaseId());
    }

    protected void setRenderInput(boolean renderInput)
    {
        // custom input?
        if (getChildCount() <= 1)
            return; 
        if (getChildren().get(1) instanceof InputSeparatorComponent)
        {   // Make sure all inputs are rendered
            boolean hasChanged = false;
            InputSeparatorComponent parent = (InputSeparatorComponent)getChildren().get(1);
            // set rendered of children
            for (UIComponent child : parent.getChildren())
            {   // set rendered 
                boolean rendered = (child instanceof ValueOutputComponent) ? !renderInput : renderInput;
                if (child.isRendered()!=rendered)
                {   child.setRendered(rendered);
                    hasChanged = true;
                }    
            }
            // give control chance to update
            if (hasChanged && log.isDebugEnabled())
                log.debug("Changing UIInput readOnly state for {} to {}", helper.getColumnName(), renderInput);
        }
        else
        {   // Must have at least two children, for label and input
            log.warn("Control-Tag does not have separate Label and Input components");
        }
    }
    
    protected boolean getColon()
    {
        Object colon = getAttributes().get("colon");
        if (colon!=null)
            return ObjectUtils.getBoolean(colon);
        return true;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



empire-db-jsf2/src/main/java/org/apache/empire/jsf2/components/ControlTag.java [261:900]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    protected void saveState()
    {
        // getStateHelper().put(inpControlPropName, control);
        // getStateHelper().put(inputInfoPropName, inpInfo);
        getStateHelper().put(ControlTag.readOnlyState, (this.inpInfo == null));
    }

    protected boolean initInputState(FacesContext context)
    {
        // Custom input?
        if (getChildCount() <= 1)
            return false; // Ignore for custom input
        // Check read-Only
        Boolean ros = (Boolean) getStateHelper().get(ControlTag.readOnlyState);
        if (ros != null && ros.booleanValue())
            return false;
        // Must have children        
        if (getChildCount() == 0)
        {   log.warn("ControlTag '{}' has no children. encodeBegin may not have been called yet.", getClientId());
            return false;
        }
        // control = ;
        helper.prepareData();
        this.control = helper.getInputControl();
        this.inpInfo = helper.getInputInfo(context);
        return (this.control != null && this.inpInfo != null);
    }

    @Override
    public String getId()
    {
        String compId = super.getId();
        // Mojarra-Patch since Id might have been set to "null"
        if ("null".equals(compId))
            compId =  helper.completeInputTagId(null);
        // done
        return compId;
    }
    
    @Override
    public void setId(String id) 
    {   // complete
        id = helper.completeInputTagId(id); 
        // set
        super.setId(id);
    }

    @Override
    public void setParent(UIComponent parent)
    {
        super.setParent(parent);
        // check whether already set
        if (helper.hasComponentId())
            return;
        /*
         * Attention: Only works if FormGrid is a direct parent of the Control.
         * Does not work, if other components are between the Control and the FormGrid.
         */
        this.renderInfo = helper.getControlRenderInfo();
        if (this.renderInfo!=null && this.renderInfo.AUTO_CONTROL_ID!=null) {
            // Auto set component Id
            setId(this.renderInfo.AUTO_CONTROL_ID.toString());
            log.debug("Auto-Setting compontent id for Control to {}", this.getId());
        }
    }

    /**
     * remember original clientId
     * necessary only inside UIData
     */
    protected String treeClientId = null;
    
    @Override
    public boolean visitTree(VisitContext visitContext, VisitCallback callback) 
    {
        FacesContext context = visitContext.getFacesContext();
        treeClientId = this.getClientId(context);
        return super.visitTree(visitContext, callback);
    }

    @Override
    public String getClientId(FacesContext context)
    {
        // Check if dynamic components are being created
        // Check for "this.creatingComponents" has been removed on 2025-03-18
        if (this.treeClientId!=null && control!=null && control.isCreatingComponents())
        {   // return the original tree client id
            return treeClientId; 
        }
        // default behavior
        return super.getClientId(context);
    }

    @Override
    public boolean getRendersChildren()
    {
        // render children ourselves using 
        //     encodeChildren(FacesContext context)
        return true;
    }

    /*
     * Add label and input components when the view is loaded for the first time
     */
    @Override
    public void encodeBegin(FacesContext context)
        throws IOException
    {
        super.encodeBegin(context);

        // init
        helper.encodeBegin();

        // set InputControl
        if (this.control==null)
            this.control = helper.getInputControl();
        
        // renderInfo
        if (this.renderInfo==null) {
            this.renderInfo=helper.getControlRenderInfo();
            if (this.renderInfo==null)
                this.renderInfo=ControlRenderInfo.getDefault();  // No FormGrid found. Use Default!
        }

        // Check visiblity
        this.controlVisible = helper.isVisible();

        // Render
        if (this.controlVisible && renderInfo.CONTROL_TAG!=null)
        {   // control wrapper tag
            ResponseWriter writer = context.getResponseWriter();
            writer.startElement(renderInfo.CONTROL_TAG, this);
            // render id
            helper.writeComponentId(writer, false);
            // style class
            String controlClass = helper.getTagAttributeStringEx("controlClass", true);
            String styleClass   = helper.getTagAttributeString(InputControl.CSS_STYLE_CLASS);
            String contextClass = helper.getContextStyleClass(); 
            helper.writeStyleClass(writer, TagStyleClass.CONTROL.get(), controlClass, styleClass, contextClass);
        }
        else if (!this.controlVisible)
        {   // render placeholder
            renderInfo.renderPlaceholder(context, this);
        }

        // custom input?
        boolean customInput = isCustomInput();
        
        // Create LabelSeparatorComponent
        ControlSeparatorComponent labelSepTag = null;
        if (true)
        {   // Create Label Separator Tag
            if (getChildCount() > 0)
                labelSepTag = (ControlSeparatorComponent) getChildren().get(0);
            if (labelSepTag == null)
            {   labelSepTag = new LabelSeparatorComponent();
                getChildren().add(labelSepTag);
                helper.resetComponentId(labelSepTag);
            }
            // encode
            labelSepTag.setRendered(this.controlVisible);
            encodeLabel(context, labelSepTag, customInput);
        }
        
        // Create InputSeparatorComponent
        ControlSeparatorComponent inputSepTag = null;
        if (!customInput)
        {   // Create Input Separator Tag
            if (getChildCount() > 1)
                inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
            if (inputSepTag == null)
            {   inputSepTag = new InputSeparatorComponent();
                getChildren().add(inputSepTag);
                helper.resetComponentId(inputSepTag);
            }
            // encode
            inputSepTag.setRendered(this.controlVisible);
            encodeInput(context, inputSepTag);
        }
        else if (getChildCount() > 1) 
        {   // custom has changed. 
            getChildren().remove(1);
        }
        
        // Encode now
        if (labelSepTag!=null)
            labelSepTag.encodeAll(context);
        if (inputSepTag!=null)
            inputSepTag.encodeAll(context);
        
        // done
        saveState();
    }

    @Override
    public void encodeChildren(FacesContext context)
        throws IOException
    {
        /*
         * Composite component renderer must render children for custom components
         *   <cc:implementation>
         *       <c:choose>
         *           <c:when test="#{cc.attrs.custom == true}">
         *               <cc:insertChildren/>
         *           </c:when>
         *       </c:choose>
         *   </cc:implementation>
         */
        if (this.controlVisible && isCustomInput())
        {   // Custom input
            ResponseWriter writer = null;
            String tagName  = renderInfo.INPUT_WRAPPER_TAG;
            if (tagName!=null && tagName.length()>0)
            {   // render tag
                writer = context.getResponseWriter();
                writer.startElement(tagName, this);
                // style Class
                String inpClass = helper.getTagAttributeStringEx("inputClass", true);
                helper.writeStyleClass(writer, TagStyleClass.CONTROL_INPUT.get(), inpClass);
                // write more
                String colSpan = tagName.equalsIgnoreCase(InputControl.HTML_TAG_TD) ? helper.getTagAttributeStringEx("colspan", false) : null;
                if (colSpan!=null)
                    writer.writeAttribute("colspan", colSpan, null);
            }
            // encode children
            super.encodeChildren(context);
            // end of element
            if (writer!=null)
            {   // render tag
                writer.endElement(tagName);
            }
        }
    }

    @Override
    public void encodeEnd(FacesContext context)
        throws IOException
    {
        // encodeEnd
        if (this.controlVisible && renderInfo.CONTROL_TAG!=null)
        {   // control wrapper tag
            ResponseWriter writer = context.getResponseWriter();
            writer.endElement(renderInfo.CONTROL_TAG);
        }
        // call base
        super.encodeEnd(context);
    }

    public boolean isCustomInput()
    {
        // detect from attribute
        Object custom = getAttributes().get("custom");
        if (custom != null)
            return ObjectUtils.getBoolean(custom);
        return false;
    }
    
    /**
     * called from LabelSeparatorComponent
     * @param context the faces context
     * @param parent the LabelSeparatorComponent
     * @param customInput flag whether a custom input component is used
     * @throws IOException from ResponseWriter
     */
    protected void encodeLabel(FacesContext context, UIComponentBase parent, boolean customInput)
        throws IOException
    {
        UIComponent labelFacet = getFacet("label");
        if (labelFacet!=null)
        {   // label facet
            return; // Noting to do here
        }
        else
        {   // render components
            HtmlOutputLabel labelComponent = null;
            if (parent.getChildCount()==0)
            {   // Create components
                String forInput = (customInput ? helper.getTagAttributeString("for") : "*");
                // createLabelComponent 
                String styleClass = TagStyleClass.LABEL.get();
                labelComponent = helper.createLabelComponent(context, forInput, styleClass, null, getColon());
                parent.getChildren().add(0, labelComponent);
                helper.resetComponentId(labelComponent);
            }
            else if (this.controlVisible)
            {
                labelComponent = (HtmlOutputLabel) parent.getChildren().get(0);
                // update
                helper.updateLabelComponent(context, labelComponent, null);
            }
        }
    }

    /**
     * called from InputSeparatorComponent
     * @param context the faces context
     * @param parent the InputSeparatorComponent
     * @throws IOException from ResponseWriter
     */
    protected void encodeInput(FacesContext context, UIComponent parent)
        throws IOException
    {
        // check children
        int count = parent.getChildCount();
        UIComponent valueComp = (count>0 ? parent.getChildren().get(count-1) : null);
        boolean resetChildId = (count==0);
        // continue
        this.inpInfo = helper.getInputInfo(context);
        // set required
        super.setRequired(this.controlVisible && helper.isValueRequired());
        // create Input Controls
        // boolean recordReadOnly = helper.isRecordReadOnly();
        if (count==0)
        {   // Create components
            control.createInput(parent, inpInfo, context);
            // create Value Component
            if (valueComp == null)
            {   // create ValueOutputComponent
                valueComp = new ValueOutputComponent();
                parent.getChildren().add(valueComp);
            }
        }
        else if (this.controlVisible)
        {   // Update
            control.updateInputState(parent, inpInfo, context, context.getCurrentPhaseId());
        }
        // set rendered
        boolean renderValue = helper.isRenderValueComponent();
        List<UIComponent> children = parent.getChildren();
        for (UIComponent child : children)
        {   // reset child id
            if (resetChildId && child.getId()!=null)
                child.setId(child.getId());
            // set rendered
            boolean valueOutput = (child instanceof ValueOutputComponent);
            child.setRendered((valueOutput ? renderValue : !renderValue));
        }
    }
    
    public void renderInput(FacesContext context, UIComponent parent, boolean allowWrapper)
        throws IOException
    {
        if (allowWrapper)
        {   // render separate wrapper tag
            boolean renderValue = helper.isRenderValueComponent();
            // wrapperTag
            String wrapperTag = helper.writeWrapperTag(context, false, renderValue); 
            // render
            control.renderInput(parent, inpInfo, context);
            // wrapperTagEnd
            if (wrapperTag!=null)
            {   // control wrapper tag
                ResponseWriter writer = context.getResponseWriter();
                writer.endElement(wrapperTag);
            }
        }
        else
        {   // the InputSeparatorComponent acts as wrapper
            control.renderInput(parent, inpInfo, context);
        }
    }

    @Override
    public Object getValue()
    {
        // check for record
        if (helper.getRecord() != null)
            return helper.getDataValue(true);
        // default
        Object value = super.getValue();
        return value;
    }
    
    @Override
    public Object getSubmittedValue()
    {   // Already detected
        if (this.submittedValueDetected)
            return this.submittedValue; // already detected
        // Check children and custom input
        if (getChildCount() <= 1)
            return null; // No input available
        // Check state
        if (this.control == null || this.inpInfo == null || helper.isReadOnly())
            return null;
        // get Input Value
        ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
        // get Input Value
        this.submittedValue = this.control.getInputValue(inputSepTag, this.inpInfo, true);
        this.submittedValueDetected = true;
        return this.submittedValue; 
    }
    
    @Override
    public void setSubmittedValue(Object submittedValue)
    {
        // super.setSubmittedValue(submittedValue);
        this.submittedValue = submittedValue;
        this.submittedValueDetected = true;
    }
    
    @Override
    protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
        throws ConverterException
    { // Check state
        if (this.control == null || this.inpInfo == null || helper.isReadOnly())
            return null;
        // Get Input Tag
        if (getChildCount() <= 1)
            return null;
        // convert value
        try {
            // parse and convert value
            ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
            return this.control.getConvertedValue(inputSepTag, this.inpInfo, newSubmittedValue);
        } catch (Exception e) {
            // Add error message
            FacesMessage msg = helper.getFieldValueErrorMessage(context, e, newSubmittedValue);
            throw new ConverterException(msg);
        }
    }

    @Override
    public void processDecodes(FacesContext context) 
    {
        // check UI-Data
        if (getChildCount()>1 && helper.isInsideUIData())
        {   // update input state
            updateControlInputState(context);
            // Set Rendered for children (changed 2024-04-05)
            // boolean readOnly = helper.isRecordReadOnly();
            boolean renderValue = helper.isRenderValueComponent();
            setRenderInput(!renderValue);
        }
        // default
        super.processDecodes(context);
    }

    @Override
    public void processValidators(FacesContext context)
    {
        resetSubmittedValue();
        // check UI-Data
        if (getChildCount()>1 && helper.isInsideUIData())
        {   // update input state
            updateControlInputState(context);
            // Only if value was submitted
            boolean hasValue = (getSubmittedValue()!=null);
            setRenderInput(hasValue);
        }
        // process all validators (including children)
        super.processValidators(context);
    }

    @Override
    public void validate(FacesContext context)
    {   // init state
        if (initInputState(context) == false)
            return;
        // get submitted value and validate
        if (log.isDebugEnabled())
            log.debug("Validating input for {}.", this.inpInfo.getColumn().getName());
        // reset again (important!)
        resetSubmittedValue();
        // Will internally call getSubmittedValue() and validateValue()
        this.valueValidated = false;
        super.validate(context);
        // post check
        if (isValid() && !this.valueValidated && !helper.isReadOnly() && !helper.isDisabled())
        {   // New since 2024-04-04
            // Validate Record value
            Object value = helper.getDataValue(true);
            log.warn("No Submitted value for {}. Validating Record value of \"{}\" instead.", helper.getColumnName(), value);
            if (value!=null)
                validateValue(context, value);
        }
    }

    @Override
    public void validateValue(FacesContext context, Object value)
    {   // Validate value
        try
        {   // Check state
            if (this.inpInfo == null || !isValid())
                return;
            // validateValue
            if (helper.beginValidateValue(context, value))
                inpInfo.validate(value);
            setValid(true);
            // don't call base class!
            // super.validateValue(context, value);

        } catch (Exception e) {
            // Add error message
            helper.addFieldValueErrorMessage(context, e, value);
            setValid(false);

        } finally {
            this.valueValidated = true;
        }
    }
    
    @Override
    public void processUpdates(FacesContext context)
    {
        // Added for EMPIREDB-436 on 2024-09-27
        if (getChildCount() <= 1)
        {   // custom input
            super.processUpdates(context);
            return;
        }
        try
        {   
            // setCachedFacesContext(context);
            pushComponentToEL(context, this);
            if (!isRendered())
            {
                return;
            }
            try
            {
                updateModel(context);
            }
            catch (RuntimeException e)
            {
                context.renderResponse();
                throw e;
            }
            if (!isValid())
            {
                context.renderResponse();
            }
        }
        finally
        {
            // setCachedFacesContext(null);
            popComponentFromEL(context);
        }
    }

    @Override
    public void updateModel(FacesContext context)
    {
        if (initInputState(context) == false)
            return;
        // No Action
        if (!isValid() || !isLocalValueSet())
            return;
        // updateModel
        Object value = getLocalValue();
        if (helper.beginUpdateModel(context, value)) 
        {   // don't call base class!
            // super.updateModel(context);
            log.debug("Updating model input for {}.", this.inpInfo.getColumn().getName());
            this.inpInfo.setValue(value);
        }
        setValue(null);
        setLocalValueSet(false);
        // Post update
        if (getChildCount() <= 1)
            return; // should never come here
        ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
        this.control.postUpdateModel(inputSepTag, this.inpInfo, context);
    }

    public InputControl getInputControl()
    {
        return this.control;
    }

    public Column getInputColumn()
    {
        return helper.getColumn();
    }

    public boolean isInputReadOnly()
    {
        return helper.isRecordReadOnly();
    }

    public boolean isInputRequired()
    {
        return helper.isValueRequired();
    }
    
    protected void resetSubmittedValue()
    {
        submittedValueDetected = false;
        submittedValue = null;
    }
    
    protected void updateControlInputState(FacesContext context)
    {
        // custom input?
        if (getChildCount() <= 1)
            return; 
        // get control
        helper.prepareData();
        if (control==null)
            control = helper.getInputControl();
        if (inpInfo==null)
            inpInfo = helper.getInputInfo(context);
        // update control
        ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
        control.updateInputState(inputSepTag, inpInfo, context, context.getCurrentPhaseId());
    }

    protected void setRenderInput(boolean renderInput)
    {
        // custom input?
        if (getChildCount() <= 1)
            return; 
        if (getChildren().get(1) instanceof InputSeparatorComponent)
        {   // Make sure all inputs are rendered
            boolean hasChanged = false;
            InputSeparatorComponent parent = (InputSeparatorComponent)getChildren().get(1);
            // set rendered of children
            for (UIComponent child : parent.getChildren())
            {   // set rendered 
                boolean rendered = (child instanceof ValueOutputComponent) ? !renderInput : renderInput;
                if (child.isRendered()!=rendered)
                {   child.setRendered(rendered);
                    hasChanged = true;
                }    
            }
            // give control chance to update
            if (hasChanged && log.isDebugEnabled())
                log.debug("Changing UIInput readOnly state for {} to {}", helper.getColumnName(), renderInput);
        }
        else
        {   // Must have at least two children, for label and input
            log.warn("Control-Tag does not have separate Label and Input components");
        }
    }
    
    protected boolean getColon()
    {
        Object colon = getAttributes().get("colon");
        if (colon!=null)
            return ObjectUtils.getBoolean(colon);
        return true;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



