function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
ca_peterson_oldca_peterson_old 

apex:inputText field blanks on rerender

Hi All,

 

I have a rather complex visualforce component that has a strange quirk in the search area, when the user types some text into an apex:inputText component and presses the search button next to it the value is passed properly into apex and acted upon, but when the componenet rerenders the field is blank.

 

Here's some screenshots showing the problem:

 

I click search:

 

At this point if somebody wants to refine their search they have to type the whole term again, and if they walk away and come back they have no way of telling their last search term, all around bad usability. Help?!

Component code:

 

<apex:component controller="ComponentCIPicker" selfClosing="true">
    <apex:attribute name="CIs" required="true" type="namespace__CI__c[]" assignTo="{!CIList}"
        description="A list of CIs to populate. If non-empty these CIs will start selected" />
    <apex:attribute name="initialSearch" required="false" type="String" assignTo="{!searchText}" default=""
        description="The initial search term for the CI search. If not set no CIs will be shown until the user enters a search term" />
    <apex:attribute name="includeLookup" required="false" type="boolean" default="false"
        description="Include a lookup field for manually selecting CIs, generally useful to using recent items" />
    <apex:attribute name="title" required="false" type="String"
        description="PageBlock title" />
    <!-- Features to add: button to show CIs from same Account/Contact specified-->
        
    
    
<apex:outputPanel id="wholeComponent">
<apex:actionRegion >
    <apex:pageBlock id="block" title="{!title}">
            <apex:commandButton value="startSearch" action="{!doSearch}" rerender="wholeComponent" status="LoadStatus" style="display: none"/>
            <apex:actionFunction name="startSearch" action="{!doSearch}" rerender="wholeComponent" status="LoadStatus"/>
            <apex:outputLabel value="Search for CIs: "/>
            <apex:inputText value="{!searchText}" id="sstext" />
            <apex:commandButton value="Search" action="{!doSearch}" rerender="wholeComponent" status="LoadStatus"/>
            <apex:actionStatus id="LoadStatus">
                <apex:facet name="start"><apex:image url="{!URLFOR($Resource.icons,'/loading.gif')}" /></apex:facet>
            </apex:actionStatus>
            <apex:actionRegion >
            <apex:outputPanel rendered="{!IF(includeLookup = true,true,false)}" style="align: right;" id="lookup">
                <apex:outputLabel value=" Browse Recent CIs " />
                <apex:inputField value="{!inputCI.namespace__CI__c}" required="false"/>
                <apex:commandButton value="Add" action="{!addInputCI}" rerender="wholeComponent" />
            </apex:outputPanel>
            </apex:actionRegion>
        
        <apex:outputPanel id="assetTable" >
                
                <apex:pageMessages />
                <apex:pageBlockTable value="{!currentSelection}" var="asset" rules="rows" cellpadding="5" cellspacing="2" width="1024"
                    columnsWidth="30px, 200px, 600px, 200px, 100px" >
                    <apex:column >
                        <apex:inputCheckbox value="{!asset.Selected}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Name" />
                        </apex:facet>
                        <apex:outputField value="{!asset.asset.Name}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Description" />
                        </apex:facet>
                        <apex:outputField value="{!asset.asset.namespace__Description__c}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Type" />
                        </apex:facet>
                        <apex:outputField value="{!asset.asset.namespace__Type__c}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Status" />
                        </apex:facet>
                        <apex:outputField value="{!asset.asset.namespace__Status__c}"/>
                    </apex:column>
                </apex:pageBlockTable>

                <apex:pageblockSection rendered="{!IF(currentSelection.size > 0, 'false', 'true')}">
                    <apex:outputText value="There are no CIs available for association" />
                </apex:pageblockSection>
                <apex:commandButton value="Add Selected" action="{!addSelected}" rerender="assetTable,selected"/>

            </apex:outputPanel>
        
            
            
            <apex:pageBlockButtons location="bottom">
                
            </apex:pageBlockButtons>
        </apex:pageBlock>
        
        <apex:pageBlock title="Selected CIs" id="selected">
            <apex:pageBlockTable value="{!CIList}" var="ci">
                <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Name" />
                        </apex:facet>
                        <apex:commandLink action="{!unselect}" immediate="true" rerender="selected">
                            <apex:param name="unselectId" value="{!ci.id}" assignTo="{!toUnselect}"/>
                            <apex:image value="{!URLFOR($Resource.icons,'/woo/close_16.png')}" title="Remove from selection"/>
                        </apex:commandLink><apex:outputField value="{!ci.Name}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Description" />
                        </apex:facet>
                        <apex:outputField value="{!ci.namespace__Description__c}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Type" />
                        </apex:facet>
                        <apex:outputField value="{!ci.namespace__Type__c}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:outputLabel value="Status" />
                        </apex:facet>
                        <apex:outputField value="{!ci.namespace__Status__c}"/>
                    </apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
        </apex:actionRegion>
    </apex:outputPanel>
</apex:component>

 

Relevant apex:

public with sharing class ComponentCIPicker {
public List<namespace.AssetWrapper> currentSelection {get; set;}
public List<namespace__CI__c> CIList {get; set;}
public namespace__CIRel__c inputCI {get; set;}
public id toUnselect {get; set;}

public String searchText {get; set{
        searchText = value;
        doSearch();
        }
    }
public ComponentCIPicker(){ currentSelection = new List<namespace.AssetWrapper>(); inputCI = new namespace__CIRel__c(); if(searchText != null){ doSearch(); //for the initialSearch attribute to function } } public void doSearch(){ try{ currentSelection = namespace.AssetWrapper.wrapAssets( doSearch(searchText) ); }catch(exception e){ System.debug('Invalid search: '+e); } } private static List<namespace__CI__c> doSearch(String searchText){ List<namespace__CI__c> Assets = new List<namespace__CI__c>(); if(searchText.length() <= 2){ //throw exception } List<List<SObject>> searchList = [FIND :searchText IN ALL FIELDS RETURNING namespace__CI__c(id, LastModifiedDate, name, namespace__Type__c, namespace__Asset_No__c, namespace__Status__c, namespace__Description__c, namespace__Serial_Number__c)]; Assets = ((List<namespace__CI__c>)searchList[0]); return Assets; }

 

 

Best Answer chosen by Admin (Salesforce Developers) 
SteveBowerSteveBower

Ok, now that we've gotten that out of the way,  :-)  I'm not sure why it's not working properly.   It seems visually "right" to me.   So, I'd start cleaning up some things (which probably won't be the problem... but....)

 

1. I wonder if things change if you change doSearch() from void to returning a pageReference of null.

 

2. Style thing.  You have an attribute with a default blank value being assigned to searchText.  I'd create a new property called "initialSearchText" and use that in the attribute, and then change the controller constructor to check if initialSearchText is not-blank, and if so, assign it to searchText and do the search.   This is just my style for readability sake.

 

3. I don't think invoking doSearch inside the searchText setter is a great idea.  After all, it's explicitly called by the Actions, why do it twice?

 

4. I'm not sure of the ramifications of nested ActionRegions although I doubt that this is the problem here.

 

5. I'm not sure why you're re-rendering the entire component instead of just the assetTable outputPanel.

 

6. You obviously have these extra buttons and actionFunction, unused actionStatus etc.  but I assume those are just things you're playing with (unless you're mucking with the searchText string in Javascript somewhere?)

 

So, in short I don't know, but these are things I'd look at from what I see now.  Best, I'm curious what you find, Steve.

All Answers

SteveBowerSteveBower

Perhaps I'm missing something, but don't you need a getter / setter for searchtext somewhere?  -Best, Steve

michaelforcemichaelforce

Yeah, I was going to say the same... you need to define searchText as a variable in your controller with {get;set;}... I'm very curious why it even let you save the visualforce markup without telling you this.  I haven't done enough with components to know why.

ca_peterson_oldca_peterson_old

Okay, stupid early-morning copy-paste error, this is my getter/setter:

public String searchText {get; set{
        searchText = value;
        doSearch();
        }
    }

SteveBowerSteveBower

Ok, now that we've gotten that out of the way,  :-)  I'm not sure why it's not working properly.   It seems visually "right" to me.   So, I'd start cleaning up some things (which probably won't be the problem... but....)

 

1. I wonder if things change if you change doSearch() from void to returning a pageReference of null.

 

2. Style thing.  You have an attribute with a default blank value being assigned to searchText.  I'd create a new property called "initialSearchText" and use that in the attribute, and then change the controller constructor to check if initialSearchText is not-blank, and if so, assign it to searchText and do the search.   This is just my style for readability sake.

 

3. I don't think invoking doSearch inside the searchText setter is a great idea.  After all, it's explicitly called by the Actions, why do it twice?

 

4. I'm not sure of the ramifications of nested ActionRegions although I doubt that this is the problem here.

 

5. I'm not sure why you're re-rendering the entire component instead of just the assetTable outputPanel.

 

6. You obviously have these extra buttons and actionFunction, unused actionStatus etc.  but I assume those are just things you're playing with (unless you're mucking with the searchText string in Javascript somewhere?)

 

So, in short I don't know, but these are things I'd look at from what I see now.  Best, I'm curious what you find, Steve.

This was selected as the best answer
ca_peterson_oldca_peterson_old

The first hidden commandButtion is to make doSearch the preferred action if somebody presses enter. I'm playing with jQuery to do it "right", but you make a good point, the first rule of troubleshooting is to simplify.

 

Let me work on those and report back if that fixes it somehow.

michaelforcemichaelforce

I have run into problems like this that were fixed by putting

 

immediate="true"

into the search button component.  This will force the search string into that variable before running the action method.

 

Also, if you want the action to be called when the user hits enter, I came up with a pretty good way to do that... have a look:

 

<apex:form >
        <apex:actionFunction action="{!findMatches}" name="GOfindMatches" reRender="resultBlock"/>
        <apex:inputText value="{!findString}" onkeydown="if(event.keyCode==13){this.blur();GOfindMatches();}"/>
        <apex:commandButton value="Find" action="{!findMatches}" reRender="resultBlock"/>
</apex:form>

 

 

No javascript needed.

ca_peterson_oldca_peterson_old

Well half of your advice worked, and if you're ever in the bay area I'll buy you a beer. The onclick javascript is working wonderfully, I've pulled out much hair over getting that to work properly.

 

The immediate="true" seems to not trigger the rerender properly, I see the action status swirl to life but the results never show, when I press enter in the search field (not immediate="true") it searches properly but wipes the field.

yvk431yvk431

Well did you checked your debug log whether the code returning any results or not.

 

Instead of rerendering the wholecomponent, did you tried rerender  in parts. 

WesNolte__cWesNolte__c

I've been trying to find the posts but I'm quite sure this is a bug that myself (I'm getting old, don't judge me) and others have had... something to do with the component controller losing parts of it's state.

 

I'm not sure if this is viable for you, but what if move the component controller functionality to another class, and pass that as an attribute to the component. Your new lightweight component controller would then serve the functionality through the assigned member class variable. (Lots of refactoring though, and no guarentee it would work)

 

Wes

ca_peterson_oldca_peterson_old

Wes, as always you seem to be right, when I don't rerender the inputText it works fine, and so I've now switched to more targeted rerendering. It does seem to be a bug, but it looks like I can work around it easilly enough for the moment.

 

Thanks to everybody for the awesome help.