[SalesForce] Rerender/rendered interaction in Visualforce

I have a Visualforce component with three fields- a dropdown of fields on a Salesforce object, a dropdown of options which depends upon the fieldType, and an input field which should not appear for some field types. I have a function, fieldAssign() that runs when the first dropdown changes, assigns some variables internally, and is supposed to clear the input field. Currently, the only time the input field is not supposed to appear is if the fieldType is Boolean.

What works: the options dropdown is correctly re-rendering based on the fieldType, and the input field (filterValue) is clearing in most cases when the field changes.

What doesn't work: when a Boolean field is selected, the input field should not be rendered. Not only is it rendered, but this is also the one case where the field is not cleared.

The relevant code should be below, let me know if you need more.

The component code:

        <apex:pageBlockTable id="rulesTable" columns="3" value="{!block.criteria}" var="row" width="100%">

    <apex:column headerValue="Field" width="30%">
        <apex:selectList value="{!row.xfield}" size="1">
            <apex:selectOptions value="{!row.FieldList}"/>
            <apex:actionSupport event="onchange" reRender="ops,val" action="{!row.fieldAssign}"/>
        </apex:selectList>
    </apex:column>
    <apex:column headerValue="Relation" width="20%">
    <apex:selectList id="ops" value="{!row.operator}" size="1">
        <apex:selectOptions value="{!row.OperandTypes}"/>
    </apex:selectList>
    </apex:column>
    <apex:column headerValue="Filter Value" width="30%">
        <apex:inputText id="val" value="{!row.filterValue}" rendered="{!row.showValue}"/> 
    </apex:column>
  </apex:pageBlockTable>

The custom class, which is the type of the 'block' variable should not be relevant. However, block.criteria is a list of FilterCriterion, which is below.

//Representation of a single requirement of the form
//field (operator) value, Name = Phil Coulson

public with sharing class FilterCriterion{
//Field info in the format fieldName:fieldType
public Schema.SObjectType obj{get; set;}
public String xfield{get; set;}
public Schema.SObjectField field{get; set;} //full field info
public String fieldName
{
get
{
return field.getDescribe().getName();
}
} //field name

    public String fieldType
    {
        get
        {
            return String.valueOf(field.getDescribe().getType()).toUpperCase();
        } 
        set;
    } //field type
    public String operator{get; set;}
    public String filterValue{get; set;}
    public Integer index{get; set;}
    private Map<String, Schema.SObjectField> fieldMap;

    //Do not show filterValue for Booleans
    public Boolean showValue{get{return (fieldType != 'BOOLEAN');} set;}


    //Default Constructor
    public FilterCriterion(Schema.SObjectType obj0, Integer ind)
    {
        obj = obj0;
        index = ind;
        fieldMap = obj.getDescribe().fields.getMap();
        xfield = getFieldList()[0].getValue();
        fieldAssign();
    }

    public String stringRep()
    {
        if(operator != null && (filterValue != null || fieldType == 'BOOLEAN'))
        {
            //Booleans don't check filterValue
            String filterString; 
            if(fieldType == 'BOOLEAN') filterString = 'irrelevant';
            else filterString = String.escapeSingleQuotes(filterValue);

            return staticParser.queryCondition(FilterRule.symbolMap().get(operator), fieldName, fieldType, filterString);
        }
        else return '';
    }

    //Called by ActionSupport when the field selected is changed
    public void fieldAssign()
    {
        field = fieldMap.get(xfield);

        //Clear filterValue
        filterValue = '';
    }



    public SelectOption[] getOperandTypes()
    {
        List < SelectOption > options = new List < SelectOption > ();
        //Booleans only have two potential values.
        if(fieldType == 'BOOLEAN')
        {
            options.add(new SelectOption('true', 'is true'));
            options.add(new SelectOption('false', 'is false'));
            return options;
        }

        options.add(new SelectOption('eq', 'equal to'));
        options.add(new SelectOption('ne', 'not equal to'));
        if(FilterRule.stringTypes.contains(fieldType))
        {
            options.add(new SelectOption('ct', 'contains'));
        }
        else if(FilterRule.numericTypes.contains(fieldType))
        {
            options.add(new SelectOption('lt', 'less than'));
            options.add(new SelectOption('le', 'less than or equal to'));
            options.add(new SelectOption('gt', 'greater than'));
            options.add(new SelectOption('ge', 'greater than or equal to'));                
        }
        return options;
    }


    public List<SelectOption> getFieldList() 
    {      
        SelectOption[] options = new SelectOption[]{};  
        for (String fieldName: fieldMap.keySet())
        {  
            Schema.DescribeFieldResult fieldObject = fieldMap.get(fieldName).getDescribe();
            options.add(new SelectOption(String.valueOf(fieldObject.getName()), fieldObject.getLabel())); 
        }
        options.sort();
        return options;
    }

}

I suspect something with the interaction of render/rerender, but am not sure what exactly is causing it. All help welcome.

Best Answer

The main thing to remember is to rerender something that is always present.

Don't rerender an element with a rendered attribute. In this case, try rerendering your whole rulesTable table, not the inputText.

The reason this is the case is that if the rerender target is not present in the HTML layout due to the rendered attribute, then you will not be able to make it reappear.

You could try rerendering your <apex:column headerValue="Filter Value" width="30%"> element also - although I'm not sure if this would work.