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
dmchengdmcheng 

View state and system log show different results

Hello.  I'm troubleshooting a custom grid editing VF page that was built by someone else.  I'm getting conflicting results when I look at View State vs. the system log.  Also I'm wondering what commandLink and commandButton do behind the scenes.
 
The grid displays custom related records to a Case record.  There is a delete commandLink on each row.  Clicking the delete link does not immediately delete the record - you need to click a Save commandButton to commit all changes and deletions.  The Save button is a pageBlockButton.
 
I have found that if there are 3 or more rows in the grid and I delete one row at or near the top and click Save to commit the changes, one field in all the rows below the deletion is updated with wrong information.  However, if I don't click Save, the data looks correct.
 
The field is Product and it is a lookup field to the Product object.
 
Example:
Step one:
Row 1, Product is A
Row 2, Product is B
Row 3, Product is C
Row 4, Product is D
Row 5, Product is E
 
Step two:
I delete Row 2 BUT I DO NOT CLICK SAVE.  The screen refreshes and I see, correctly:
Row 1, Product is A
Row 2, Product is C
Row 3, Product is D
Row 4, Product is E
 
Step three:
Now I click Save and the screen refreshes:
Row 1, Product is A
Row 2, Product is C
Row 3, Product is C
Row 4, Product is D
 
(Of course the controller is maintaining two lists - one of rows to display, and one of rows for DML deletion.)
 
Here are things I don't understand:
 
* In step 2, the view state show 5 items in the related record list, even though the system log shows only 4 items.  The system log show each item has the correct Product.  Why isn't the view state updated also, especially since I saw the page refresh?
 
* Why is the delete commandLink rerendering the screen in step 2?  Is that typical operation for commandLink?  The way it is built, it is calling a public void method in the controller, not a public PageReference, so there is no return statement in it.  I have not been able to find any explicit rerender in the controller method.
 
* I have created a dummy Save button that does nothing.  When I click the dummy button immediately after clicking the delete commandLink, I see in the system log that the Product are incorrect as in step 3 above.  I can't understand how that happens, since I see the correct values on the screen just before I click the dummy button, and the system log confirms it.
 
 
Best Answer chosen by Admin (Salesforce Developers) 
dmchengdmcheng

After several hours of removing code sections and testing, I think I found the issue -- when I removed the immediate="true" from the delete commandLink, the problem went away.  Makes me curious as to what exactly "immediate" is doing in addition to avoiding field validation.

 

@sfdcfox - thanks for your sample code, I'll be referring to it when I begin revamping this thing.

All Answers

arizonaarizona

Can you post the VF and the controller code?

dmchengdmcheng

Well the code is a monster.  I didn't post it initially because I didn't think anybody would wade through it.

 

Here is the grid portion of the Visualforce page:

 

            <!-- DEFECTIVE PRODUCTS SECTION (Record Type = 'Dealer/Distributor' or Returns or Logistics) -->
            <apex:pageBlockSection title="Defective or Returned Products" columns="1" collapsible="false" id="productOwnedPageBlockSectionDD"
                    rendered="{!OR(caseRecordTypeName = RECORD_TYPE_DEALER_DISTRIBUTOR, caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}" >
                <apex:commandButton action="{!addCaseProductLink}" immediate="true" value="Add" rerender="productOwnedPageBlockSectionDD" />
                
				<!-- This is a row counter used as a List index. -->
                <apex:variable value="{!0}" var="rowNbr" />
                <apex:pageBlockTable title="Defective Products" value="{!caseProducts}" var="caseProduct" id="caseProductsPageBlockTable" >
                    <apex:column >
                        <apex:commandLink action="{!deleteCaseProductLink}" immediate="true" value="Delete" onclick="return confirmProductDelete()">
                            <apex:param name="{!ROW_NBR}" value="{!rowNbr}" />
                        </apex:commandLink>
                    </apex:column>

                    <apex:column headerValue="Row" width="60px">
                        <apex:outputText value="{!rowNbr}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="Product ID" width="60px">
                        <apex:outputField value="{!caseProduct.Product__c}" style="width:60px"/>
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Document_Number__c.label}" width="60px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductDocumentNumber" value="{!caseProduct.Document_Number__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Product__c.label}" width="60px" >
                        <apex:inputField value="{!caseProduct.Product__c}" style="width:60px" id="caseProduct">
                            <apex:actionSupport event="onchange"
                            	immediate="true" 
                            	action="{!rerenderCaseProductDescription}" 
                            	rerender="productOwnedPageBlockSectionDD"
                                focus="{!IF(OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS), 'caseProductGrillPurchasedStore', 'caseProductQuantity')}">
                                <apex:param name="{!ROW_NBR}" value="{!rowNbr}" />
                            </apex:actionSupport>
                        </apex:inputField>
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Product_Description__c.label}" >
                        <apex:outputText value="{!caseProduct.Product_Description__c}"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Grill_Purchased_Store__c.label}" width="100px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductGrillPurchasedStore" value="{!caseProduct.Grill_Purchased_Store__c}" style="width:100px">
                            <apex:actionSupport event="onchange"
                            	immediate="true" 
                            	action="{!rerenderGrillPurchasedStore}" 
                            	rerender="productOwnedPageBlockSectionDD"
                                focus="caseProductQuantity">
                                <apex:param name="{!ROW_NBR}" value="{!rowNbr}" />
                            </apex:actionSupport>
                        </apex:inputField>
                    </apex:column>

                    <apex:column headerValue="Qty" width="20px" >
                        <apex:inputField id="caseProductQuantity" value="{!caseProduct.Quantity__c}" style="width:20px"/>
                    </apex:column>
        
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Return_Store__c.label}" width="100px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductReturnStore" value="{!caseProduct.Return_Store__c}" style="width:100px">
                            <apex:actionSupport event="onchange"
                            	immediate="true" 
                            	action="{!rerenderReturnStore}" 
                            	rerender="productOwnedPageBlockSectionDD"
                                focus="caseProductSerial">
                                <apex:param name="{!ROW_NBR}" value="{!rowNbr}" />
                            </apex:actionSupport>
                        </apex:inputField>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Model__c.label}" width="60px" rendered="{!caseRecordTypeName = RECORD_TYPE_DEALER_DISTRIBUTOR}">
                        <apex:inputField id="caseProductModel" value="{!caseProduct.Model__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Serial__c.label} (or not required)" width="90px">
                        <apex:inputField id="caseProductSerial" value="{!caseProduct.Serial__c}" style="width:90px"/>
                        <apex:inputField id="caseProductSerialNotRequired" value="{!caseProduct.Serial_Not_Required__c}"/>
						<!-- Row counter incremented here so it is visible to all record types. -->
						<apex:variable var="rowNbr" value="{!rowNbr + 1}" />
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Symptom__c.label}" rendered="{!NOT(OR(caseRecordTypeName = RECORD_TYPE_LOGISTICS, caseRecordTypeName = RECORD_TYPE_RETURNS))}">
                        <apex:inputField id="caseProductSymptom" value="{!caseProduct.Symptom__c}" required="true"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Sub_Symptom__c.label}" rendered="{!NOT(OR(caseRecordTypeName = RECORD_TYPE_LOGISTICS, caseRecordTypeName = RECORD_TYPE_RETURNS))}">
                        <apex:inputField id="caseProductSubSymptom" value="{!caseProduct.Sub_Symptom__c}" required="true"/>
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Return_Reason__c.label}" width="100px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_LOGISTICS, caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductReturnReason" value="{!caseProduct.Return_Reason__c}" style="width:100px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Resolution__c.label}" width="100px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductResolution" value="{!caseProduct.Resolution__c}" style="width:100px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Grill_Purchase_Date__c.label}" width="60px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductGrillPurchaseDate" value="{!caseProduct.Grill_Purchase_Date__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Costco_Member_Number__c.label}" width="60px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductCostcoMemberNumber" value="{!caseProduct.Costco_Member_Number__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Return_Date__c.label}" width="60px" rendered="{!(caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductReturnDate" value="{!caseProduct.Return_Date__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Grill_Packaging__c.label}" width="60px" rendered="{!caseRecordTypeName = RECORD_TYPE_RETURNS}">
                        <apex:inputField id="caseProductGrillPackaging" value="{!caseProduct.Grill_Packaging__c}" style="width:60px"/>
                    </apex:column>
<!-- 
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Grill_Condition__c.label}" width="60px" rendered="{!caseRecordTypeName = RECORD_TYPE_RETURNS}">
                        <apex:inputField id="caseProductGrillCondition" value="{!caseProduct.Grill_Condition__c}" style="width:60px"/>
                    </apex:column>
-->					
                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Ship_Via__c.label}" width="100px" rendered="{!caseRecordTypeName = RECORD_TYPE_LOGISTICS}">
                        <apex:inputField id="caseProductShipVia" value="{!caseProduct.Ship_Via__c}" style="width:100px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Shipping_Cost__c.label}" width="60px" rendered="{!caseRecordTypeName = RECORD_TYPE_LOGISTICS}">
                        <apex:inputField id="caseProductShippingCost" value="{!caseProduct.Shipping_Cost__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Received__c.label}" width="60px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductReceived" value="{!caseProduct.Received__c}" style="width:60px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Detected_Problem__c.label}" width="180px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductDetectedProblem" value="{!caseProduct.Detected_Problem__c}" style="width:180px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Notes__c.label}" width="120px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductOtherProblem" value="{!caseProduct.Notes__c}" style="width:120px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Final_Resolution__c.label}" width="100px" rendered="{!OR(caseRecordTypeName = RECORD_TYPE_RETURNS, caseRecordTypeName = RECORD_TYPE_LOGISTICS)}">
                        <apex:inputField id="caseProductFinalResolution" value="{!caseProduct.Final_Resolution__c}" style="width:100px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Sales_Channel__c.label}" width="100px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductSalesChannel" value="{!caseProduct.Sales_Channel__c}" style="width:100px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Other_Sales_Channel__c.label}" width="100px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductOtherSalesChannel" value="{!caseProduct.Other_Sales_Channel__c}" style="width:100px"/>
                    </apex:column>

                    <apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Sales_Price__c.label}" width="80px" rendered="{!(caseRecordTypeName = RECORD_TYPE_RETURNS)}">
                        <apex:inputField id="caseProductSalesPrice" value="{!caseProduct.Sales_Price__c}" style="width:80px"/>
                    </apex:column>
                </apex:pageBlockTable>

            </apex:pageBlockSection>

 

dmchengdmcheng

I can't post the whole controller because it exceeds the character limit for a message.  Here are the getters/setters, the constructor, and the methods I've been studying -- saveTEST and deleteCaseProductLink.  caseProducts is the list of items in the grid.

 

//Last modified 8/15/2012  david cheng
public without sharing class CaseProductController {

	//temporary code for troubleshooting the item overwrite issue after a delete.
	//2/8/2013
	public void saveTEST() {
		system.debug('*** saveTEST-caseProducts: ' + caseProducts);
//		return null;        
	}
		
	public static String RECORD_TYPE_DEALER_DISTRIBUTOR {get{return 'Dealer/Distributor';} private set;}
	public static String RECORD_TYPE_RESIDENTIAL {get{return 'Residential';} private set;}
	public static String RECORD_TYPE_RETURNS {get{return 'Returns';} private set;}
	public static String RECORD_TYPE_LOGISTICS {get{return 'Logistics';} private set;}
	public static String ROW_NBR {get{return 'rowNbr';} private set;}

	// 'case' is a researved word so I used caseMR 'MR' stands for 'Master Record'.
	public Case caseMR {get; private set;}
	public List<CaseProduct__c> caseProducts {get; private set;}
	public List<CaseReplacementProduct__c> caseReplacementProducts {get; private set;}
	public CaseComment caseComment {get; private set;}
	
	public String caseRecordTypeName {get; private set;}
	public Product_Owned__c productOwned {get; private set;}
	public Decimal subTotal {get; private set;}
	public Decimal grandTotal {get; private set;}
	
	private List<CaseProduct__c> caseProductsToDelete;
	private List<CaseReplacementProduct__c> caseReplacementProductsToDelete;
	
    
    // Constructor
    public CaseProductController(ApexPages.StandardController stdController) {
        this.caseMR = (Case)stdController.getRecord();
        System.debug('MikeP-CaseProductController()-caseMR=' + caseMR);

        // Check if we have a Case record
        if (this.caseMR.Id == null) {
        	// Must be a NEW Case
        	
        	// Setup the new CaseProduct__c record
        	initCaseProductRow();
        	// Setup the new CaseReplacementProduct__c record
        	initCaseReplacementProductRow();
        	// Setup the CaseComment record
        	initCaseCommentRow();
        	
	    	setContact();
	    	
        } else {
        	// Must be an EXISTING Case
        	
        	// Select the Case record with all the fields we need to edit
        	selectCaseMR(this.caseMR.Id);
	        // select the related CaseProduct__c Records
        	selectCaseProducts(this.caseMR.Id);
	        // select the related CaseReplacementProduct__c Records
        	selectCaseReplacementProducts(this.caseMR.Id);
	        // select the related CaseComment Record
        	selectCaseComment(this.caseMR.Id);
        	
        }

    	setProductOwned(this.caseMR.Product_Owned_ID__c);

        System.debug('MikeP-CaseProductController()-this.caseMR=' + this.caseMR);
        System.debug('MikeP-CaseProductController()-this.caseProducts=' + this.caseProducts);
        System.debug('MikeP-CaseProductController()-this.caseReplacementProducts=' + this.caseReplacementProducts);
        System.debug('MikeP-CaseProductController()-this.caseComment=' + this.caseComment);

        selectCaseRecordTypeName();
    }
	// Use this from a page to delete a CaseProduct__c record from the list of CaseProduct__c records.
	public void deleteCaseProductLink() {
system.debug('*** deleteCaseProductLink-caseProducts before: ' + caseProducts);
		System.debug('MikeP-deleteCaseProductLink()-this.caseProducts.size()=' + this.caseProducts.size());
		Integer rowNbr = Decimal.valueOf(Apexpages.currentPage().getParameters().get(ROW_NBR)).intValue();
		System.debug('MikeP-deleteCaseProductLink()-rowNbr=' + rowNbr);
		if (rowNbr != null) {
			if (this.caseProductsToDelete == null) {
				this.caseProductsToDelete = new List<CaseProduct__c>();
			}
			this.caseProductsToDelete.add(this.caseProducts[rowNbr]);
			this.caseProducts.remove(rowNbr);
		}
system.debug('*** deleteCaseProductLink-caseProducts after: ' + caseProducts);
	}

 

 

arizonaarizona

I think the problem is this:

 

<apex:column headerValue="{!$ObjectType.CaseProduct__c.fields.Serial__c.label} (or not required)" width="90px">
                        <apex:inputField id="caseProductSerial" value="{!caseProduct.Serial__c}" style="width:90px"/>
                        <apex:inputField id="caseProductSerialNotRequired" value="{!caseProduct.Serial_Not_Required__c}"/>
                        <!-- Row counter incremented here so it is visible to all record types. -->
                        <apex:variable var="rowNbr" value="{!rowNbr + 1}" />
                    </apex:column>
                   

You cannot update an apex:variable inside of an iteration component like a pageBlockTable

 

One way of doing this would be to change this to

 

<apex:commandLink action="{!deleteCaseProductLink}" immediate="true" value="Delete" onclick="return confirmProductDelete()">
                            <apex:param name="{!ROW_NBR}" value="{!rowNbr}" />
                        </apex:commandLink>

<apex:commandLink action="{!deleteCaseProductLink}" immediate="true" value="Delete" onclick="return confirmProductDelete()">
                            <apex:param name="id" value="{!caseProduct.Product__c}" />
                        </apex:commandLink>

In the controller

 

	public void deleteCaseProductLink() {
system.debug('*** deleteCaseProductLink-caseProducts before: ' + caseProducts);
		System.debug('MikeP-deleteCaseProductLink()-this.caseProducts.size()=' + this.caseProducts.size());
		Integer id = Apexpages.currentPage().getParameters().get(id);
		System.debug('MikeP-deleteCaseProductLink()-rowNbr=' + rowNbr);
                Integer counter = 0;
		if (id != null) {
			if (this.caseProductsToDelete == null) {
				this.caseProductsToDelete = new List<CaseProduct__c>();
			}
                        counter = 0;
                        for(Product2 p : caseProducts){
                           if(p.product__c = id){
			      this.caseProductsToDelete.add(this.caseProducts[rowNbr]);
			      this.caseProducts.remove(counter);
                           }
                           counter = counter + 1;
                        }
		}
system.debug('*** deleteCaseProductLink-caseProducts after: ' + caseProducts);
	}

 There is a better way of doing this using Map<Id, Product2> but the above should work.

 

Arizona

Rahul SharmaRahul Sharma

Hi dmcheng,

 

Your logic seems perfectly ok to me. I had got stuck in similar problem some time back and it took hours and hours of debugging & resolving it.

 

So, I think there could be two approached for resolving this:

 

1. Delete the record on click of delete link:

     - Actually delete the record on click of delete link, and refresh the product list displayed in UI by querying the records.

 

2. Mark the records for which delete link were clicked and delete them on click of save button:

     a) Delete Link action - 

         - On click of delete link remove the records from that list(which you are already doing.).

         Store the Id's of all the deleted(Not actually deleted) records in a listToDelete.

     b) Save button action - 

         perform delete operation on the listToDelete.

         Save buttons's last action would be to refresh the product list displayed in UI by querying the records.

 

Hope this helps.

dmchengdmcheng
Thanks for your replies, I appreciate both of you taking the time to plow through this code.
 
@Arizona - Yeah the parameter is cumbersome to use and a map would be better.  However, the parameter is working - when I display the parameter values in a column, they are correct for each row, even after the delete.
 
@Rahul - The code is using a toDelete list to collect the deleted records.  The Save button deletes the toDelete list and updates changes to the other records.  I'm reluctant to make the delete link an immediate action because record field changes are not immediate, they still require the Save button.
 
I'm going to try create a map in the existing code and use that to manage the grid list.
 
What I still don't understand is why the view state shows the record list to have 5 items while the system log shows 4 items after the delete.
sfdcfoxsfdcfox

Just be wary of the Visualforce Map bug when you go about it.

 

For reference, here's one way how I "delete" items on a page, deferred until save:

 

public with sharing class Controller {
  public class LineItem {
    public Sobject record { get; set; }
    Controller con;
    public void delete() {
      con.delete(this);
    }
    public lineitem(controller c, sobject r) {
      con = c;
      record = r;
    }
  }

  public lineitem[] mylines { get; set; }
  lineitem[] deletecache;

  public controller(apexpages.standardcontroller controller) {
    mylines = new lineitem[0];
    deletecache = new lineitem[0];
    buildlist();
  }
  // just a demo here... 
  public void buildlist() {
    for(account a:[select id,name from account limit 10]) {
      mylines.add(new lineitem(this,a));
    }
  }
  public void delete(lineitem item) {
    for(integer i = 0; i < mylines.size(); i++) {
      if(mylines[i] == item) {
        deletecache.add(mylines.remove(i));
      }
    }
  }
  public pagereference saveAll() {
    // process deletecache
    // process mylines
  }
}

You could use map or set as well (thanks to the new features in Apex Code for custom wrappers), which saves the iteration in "delete(lineitem)", but since the cycle count is usually small, I don't see much point in that optimization.

dmchengdmcheng

After several hours of removing code sections and testing, I think I found the issue -- when I removed the immediate="true" from the delete commandLink, the problem went away.  Makes me curious as to what exactly "immediate" is doing in addition to avoiding field validation.

 

@sfdcfox - thanks for your sample code, I'll be referring to it when I begin revamping this thing.

This was selected as the best answer
dmchengdmcheng

@sfdcfox - btw, which map bug are you referring to?

 

"Unpredicatable ordering of map keys"?  Looks like this was fixed in Winter 12:

http://success.salesforce.com/issues_view?id=a1p30000000RmuRAAS

 

or referencing a map field with outputField or outputLabel?

http://forceguru.blogspot.com/2011/07/binding-values-of-map-on-visualforce.html

Rahul SharmaRahul Sharma
Perfect, I had thought on immediate=true , but didn't strike at that time.
Nice that it got resolved.
Thanks for sharing the solution, was curious a bit. :)
sfdcfoxsfdcfox

Based on recent experiences, I'd say that maps are still broken. For example, see:

 

http://boards.developerforce.com/t5/Visualforce-Development/How-can-I-call-innerclass-method-in-visualforce-page/m-p/565827#M60291

 

Until they fix it, you'll probably just want to stick to using lists when you can.