Saturday, 9 July 2011

Passing Parameters to Apex Method from Visualforce Page

A question that often crops up when developers are introduced to Visualforce is "how can I pass a parameter to a controller method?".


For example, consider the following page. This outputs the details of up to 10 contacts associated with an account:

<apex:page standardController="Account" extensions="ParamBlogController">
 <apex:outputPanel id="all">
  <apex:form>
    <apex:pageBlock title="Account Detail">
      <apex:pageBlockSection title="Account">
        <apex:outputField value="{!Account.Name}"/>
        <apex:outputField value="{!Account.Description}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Contacts" columns="1">
        <apex:pageBlockTable value="{!conts}" var="cont" rows="10">
          <apex:column value="{!cont.Id}"/>
          <apex:column value="{!cont.Name}"/>
          <apex:column value="{!cont.Email}"/>
          <apex:column value="{!cont.Phone}"/>
        </apex:pageBlockTable>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
 </apex:outputPanel>
</apex:page>

The controller is as follows - note that I retrieve the contacts and store them in a list rather than relying on the Account.Contacts related list - there is a good reason for this and it will (hopefully) become clear later on:

public class ParamBlogController 
{
 private ApexPages.StandardController stdCtrl {get; set;}
 
 public List<Contact> conts {get; set;}
 
 public ParamBlogController(ApexPages.StandardController std)
 {
  stdCtrl=std;
  setupContacts();
 }
 
 private void setupContacts()
 {
  conts=[select id, Name, Email, Phone from Contact where AccountId=:stdCtrl.getId()];
 }
}

The output of the page is shown below:


Next up I want to add a column that allows various actions to be performed on the particular contact instance.  Its tempting to think that I can just add the following to the markup (assuming I have an appropriate method in the controller) and all will be well:

<apex:column headerValue="Action">
   <apex:commandButton value="Del" action="{!delCont(cont.id)}"/>
</apex:column>

However, this refuses to compile with an error that delCont is an unknown function. As controller action methods can't take parameters in this way, the compiler assumes that this is a formula function, but no function with that name exists, hence the error.

So, how can we pass the id of the contact to the controller? The answer is straightforward, but requires a slight mindshift.

The <apex:param> standard component allows parameters to be defined for a parent component, which can be assigned to a controller property. I can therefore change my markup to the following to specify the parameter:

<apex:commandButton value="Del" action="{!delCont}" rerender="all">
   <apex:param name="contIdParam" value="{!cont.id}" assignTo="{!contIdChosen}"/>
</apex:commandButton>

Note the rerender attribute of the commandbutton - if this attribute is not present, the parameter does not get sent back to the controller. I simply have an outputpanel around the entire page contents.


I then add the following property to my controller:
public String contIdChosen {get; set;}

The key to this is the behaviour when the button is clicked - the id of the contact instance is assigned to my controller property before the action method is invoked. Thus the method that is actually carrying out the delete can simply access the contIdChosen property secure in the knowledge that it will contain the id of the contact associated with the button:

public PageReference delCont()
{
 Contact toDel=new Contact(id=contIdChosen);
 
 delete todel;
 
 setupContacts();
  
 return null;
}

The fact that I have to rerender in order to get the parameter passing to work is the reason why I maintain the list of Contacts independent of the Account record from the standard controller - I can't make the browser refresh the page and thus rebuild the related contacts from scratch, so I have to rebuild the list server side.

25 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Thank you. This post was of great help.

    ReplyDelete
  3. Thanks for this explanation, Explained what I couldn't find in the tutorials.

    ReplyDelete
  4. So the contIdChosen variable's scope isn't limited to the function you've called, correct?

    ReplyDelete
    Replies
    1. Correct. Its a class level property of the controller and accessible to any methods in the class.

      Delete
  5. I have used exactly the same code for deletion of a row in the pageblocktable, but I get the error that the variable toDelIdent does not exist. Why is it so ? I have passed it the same way using apex:param in the page.

    ReplyDelete
    Replies
    1. This usually means that the property can't be found. Is toDelIdent public with a public getter and setter?

      Delete
  6. I am getting an Error: Unknown property 'AccountStandardController.conts'
    In the pageblocktable.,,
    when I

    Please find this out?

    ReplyDelete
    Replies
    1. That sounds like the extension controller hasn't been picked up properly, or the 'conts' property hasn't been correctly declared.

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. How would this work when trying to update a (boolean) variable in a wrapper class? I have a list of wrapper classes being displayed in a pageblocktable (data coming from wrapperClass.Product__c if that makes sense) . In a column I have a button where the use can click a commandButton to show List of OTHER records in a nested pageblocktable, the list is in the same wrapper class (wrapperClass.List(Pricing__c)). The control over each display of the nested table is done using a third item in the wrapper class wrapperClass.showPricing (boolean) which is what the rendered attribute on the nested table is set to.

    On click I need to update the showPricing variable to "true" for THAT SPECIFIC wrapperClass. Does that make sense? Do you need more info?

    Also, as a bonus, is there a way for the update to happen 'smoothly', i.e. the page doesn't reload and the user is brought back to the top of the page, etc.

    Thanks,
    Jeremy Stender

    ReplyDelete
    Replies
    1. To clarify, the updating of the variable to 'true' would cause the nested table of "Pricing__c" records to display. The display of these is what I want to happen 'smoothly'.

      Delete
    2. Don't you hate it when you ask a question and then figure it out immediately after?

      I was able to do this by using the above and putting the path to the boolean variable in the assignto="", using the pageblocktable's variable for the current wrapperClass.

      Thanks for the help!

      Delete
  9. I have an "apex:inputFile" on the same page, so using rerender="all" causes the following error:
    "apex:inputFile can not be used in conjunction with an action component, apex:commandButton or apex:commandLink that specifies a rerender or oncomplete attribute."

    Any way around this problem? Other than getting rid of the inputFile.

    ReplyDelete
    Replies
    1. The way around this is to have a hidden input field and set the value of the parameter into that prior to executing the action method. I've been asked this a few times recently so I think I'll write another blog post explaining how it works. Stay tuned.

      Delete
    2. I'd be interested in hearing what you do in this situation as well.

      Delete
  10. you have my eternal gratitude. your graciousness is appreciated

    ReplyDelete
  11. How to create horizontal scrollbar in the inline visualforce page

    ReplyDelete
  12. Hi Bob,

    I need to get values form vf page what ever i entered on vf component example if i enter name="xxx" i should get "xxx" value to my controller.

    ReplyDelete
    Replies
    1. That sounds like regular visualforce forms. Have you looked at the developer's guide?

      Delete
  13. This comment has been removed by the author.

    ReplyDelete
  14. Hi Bob,

    first of all, thanks you for what you are doing - I am relatively new to Salesforce and your posts have helped me on numerous occasion.

    My question is following - in the post above you say:
    "Note the rerender attribute of the commandbutton - if this attribute is not present, the parameter does not get sent back to the controller."

    I'm having trouble wrapping my mind around it. From what I understand about rerender attribute it is used to indicate page elements that should be refreshed upon AJAX request. So why does pressence/absence of this attribute affect parameters being passed to the controller? I would really appreciate if you could shed some light on this matter.

    Cheers
    Ivan

    ReplyDelete
    Replies
    1. Certainly - its a bug in Visualforce where if you don't use a rerender attribute, the parameter doesn't get passed to the controller.

      My fellow MVP Wes Nolte wrote this up on his blog at:

      http://th3silverlining.com/2009/06/12/salesforce-bugs-you/

      Delete
  15. I have a List (in an Apex extension) of a custom object called Question Assignments (QA) populated via a SOQL query - we use this to dynamically populate the question text on a visualforce page.

    In this query, I am also pulling back the Question ID, which we will need to go execute a separate query (at least the way I'm attempting at this point) to pull back a list of select options used in a selectRadio component.

    I have been attempting to use the apex:param option (currently tied to an output text field used as a label for the radio button group), but have failed at doing this just about every way I've found online.

    Is what I'm trying to do even possible? Any pointers would be appreciated! Thanks!

    ReplyDelete
  16. Hi Bob Buzzard,
    Using wrapper class i displayed all custom objects in my organization through custom controller and i gave delete command link of each object.when i click delete command link,how to delete custom object from database?

    please help me............

    ReplyDelete