Saturday, 26 March 2011

Edit Parent and Child Records with Visualforce - Part 1

One area where, in my opinion, Visualforce can really improve the user experience is editing parent and child records in a single page.  Think about how many clicks are required to edit and save each contact associated with an account on a one by one basis.

To this end I've created a page and associated controller that allows a subset of details for all contacts (and the parent account) to be edited and saved in one go.  This also allows new contacts to be created and existing contacts to be deleted.  Below is a screen shot of the page:



The page itself is pretty clunky - clicking the Delete contact button fires the action method to carry out the delete without any user confirmation, while the New Contact button creates a new contact called "Change Me" and refreshes the page:


It does have one smarter feature though - if the page is accessed without specifying an ID, it goes into new account mode, and doesn't render any content or buttons related to contacts.

The Visualforce markup is shown below. If you are a regular reader of this blog you'll recognise it as heavily based on the sample from the Persisting List Edits in Visualforce post. Note that as usual you don't need a lot of markup to produce some pretty useful functionality.

<apex:page standardController="Account"
           extensions="AccountAndContactsEditExtensionV1"
           tabStyle="Account" title="Prototype Account Edit">
    <apex:pageMessages />
    <apex:form >
        <apex:pageBlock mode="mainDetail">
            <apex:pageBlockButtons >
                <apex:commandButton action="{!cancel}" value="Exit" />
                <apex:commandButton action="{!save}" value="Save" />
                <apex:commandButton action="{!newContact}" value="New Contact" rendered="{!NOT(ISBLANK(Account.id))}"/>
            </apex:pageBlockButtons>
            <apex:repeat value="{!$ObjectType.Account.fieldSets}">
            </apex:repeat>
            <apex:pageBlockSection title="Account Details" collapsible="true" id="mainRecord" columns="2" >          
                    <apex:inputField value="{!Account.Name}"/>
                    <apex:inputField value="{!Account.Type}"/>
                    <apex:inputField value="{!Account.BillingStreet}"/>
                    <apex:inputField value="{!Account.ShippingStreet}"/>
                    <apex:inputField value="{!Account.Industry}"/>
                    <apex:inputField value="{!Account.Phone}"/>
            </apex:pageBlockSection>
           <apex:outputPanel id="contactList"> 
                <apex:repeat value="{!contacts}" var="contact" >
                    <apex:pageBlockSection columns="1"  title="Contact {!contact.Name}" collapsible="true">
                        <apex:pageBlockSectionItem >
                              <apex:pageBlockSection columns="2">
                                <apex:inputField value="{!contact.title}"/>
                                <apex:inputField value="{!contact.phone}"/>
                                <apex:inputField value="{!contact.FirstName}"/>
                                <apex:inputField value="{!contact.LastName}"/>
                                <apex:inputField value="{!contact.email}"/>
                              </apex:pageBlockSection>
                           </apex:pageBlockSectionItem>
                            <apex:commandButton value="Delete Contact" action="{!deleteContact}" rerender="contactList">
                               <apex:param name="contactIdent" value="{!contact.id}" assignTo="{!chosenContactId}"/>
                            </apex:commandButton>
                        </apex:pageBlockSection>
                </apex:repeat>
            </apex:outputPanel>
            
       </apex:pageBlock>
    </apex:form>        

  
</apex:page>

Deleting a contact requires that the controller is notified of the ID to delete. This is accomplished via the nested param component for the "Delete Contact" button:

<apex:commandButton value="Delete Contact" action="{!deleteContact}" rerender="contactList">
   <apex:param name="contactIdent" value="{!contact.id}" assignTo="{!chosenContactId}"/>
</apex:commandButton>

The ID of the contact chosen to delete is propagated back to the controller via the the assignTo attribute. This means that by the time the deleteContact action method is invoked, the chosenContactId controller property will have been populated with the ID from the page. One important point to note about this - I've found that since Winter 11, you must have a rerender attribute on the commandButton for the parameter to be passed to the controller.

The controller is a little more complex, as it has action methods to support each of the buttons - Save and Exit, Save (and remain on page), Delete Contact and New Contact.

public class AccountAndContactsEditExtensionV1 {
    private ApexPages.StandardController std;
    
    // the associated contacts
   public List<Contact> contacts;
     
    // the chosen contact id - used when deleting a contact
    public Id chosenContactId {get; set;}
    
    public AccountAndContactsEditExtensionV1(ApexPages.StandardController stdCtrl)
    {
     std=stdCtrl;
    }
    
    public Account getAccount()
    {
     return (Account) std.getRecord();
    }

    private boolean updateContacts()
    {
        boolean result=true;
        if (null!=contacts)
           {
           List<Contact> updConts=new List<Contact>();
              try
              {
               update contacts;
              }
              catch (Exception e)
              {
                 String msg=e.getMessage();
                 integer pos;
                 
                 // if its field validation, this will be added to the messages by default
                 if (-1==(pos=msg.indexOf('FIELD_CUSTOM_VALIDATION_EXCEPTION, ')))
                 {
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, msg));
                 }
                 
                 result=false;
              }
           }
           
           return result;
    }
    
    public PageReference saveAndExit()
    {
     boolean result=true;
    result=updateContacts();
     
     if (result)
     {
        // call standard controller save
        return std.save();
     }
     else
     {
      return null;
     }
    }
    
    public PageReference save()
    {
     Boolean result=true;
     PageReference pr=Page.AccountAndContactsEditV1;
     if (null!=getAccount().id)
     {
      result=updateContacts();
     }
     else
     {
      pr.setRedirect(true);
     }
     
     if (result)
     {
        // call standard controller save, but don't capture the return value which will redirect to view page
        std.save();
           ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'Changes saved'));
     }
        pr.getParameters().put('id', getAccount().id);
     
     return pr;
    }

    public void newContact()
    {
       if (updateContacts())
       {
          Contact cont=new Contact(FirstName='Change', LastName='Me', AccountId=getAccount().id);
          insert cont;
        
          // null the contacts list so that it is rebuilt
          contacts=null;
       }
    }
    
    public void deleteContact()
    {
       if (updateContacts())
       {
          if (null!=chosenContactId)
          {
             Contact cont=new Contact(Id=chosenContactId);
              delete cont;
       
           // null the contacts list so that it is rebuilt
              contacts=null;
              chosenContactId=null;
          }
       }
    }
    
   public List<Contact> getContacts()
    {
       if ( (null!=getAccount().id) && (contacts == null) )
       {
           contacts=[SELECT Id, Name, Email, Phone, AccountId, Title,  
                        Salutation, OtherStreet, OtherState, OtherPostalCode, 
                        OtherPhone, OtherCountry, OtherCity, MobilePhone, MailingStreet, MailingState, 
                        MailingPostalCode, MailingCountry, MailingCity, LeadSource, LastName, 
                        HomePhone, FirstName, Fax, Description, Department
                         FROM Contact 
                         WHERE AccountId = : getAccount().ID
                         ORDER BY CreatedDate];
       }
                          
       return contacts;
    }
}

In part 2, I'll look at improving the user experience by adding confirmation of delete and allowing the user to specify fields when creating a new contact. This page also looks like it would benefit from fieldsets, so its likely those will be introduced as well.

Saturday, 19 March 2011

Persisting List Edits in Visualforce

A topic that appears semi-regularly on the Visualforce discussion boards is editing the contents of a list of sobjects and how to persist that information into the Salesforce database.  There's often confusion around how to submit the changes back to the Visualforce controller.

This is a good example of how easy it is to capture complex form data from a Visualforce page into the controller.  Essentially as long as the list is a controller property that is part of the viewstate, there's nothing that has to be done to submit the changes back to the page and very little to save the changes back to the database.

Here's the output of a small Visualforce page that allows basic information for an account and up to 5 associated contacts to be edited in one go.


The contacts information is stored in a list property from the controller, and output via an apex:repeat tag:

<apex:repeat value="{!contacts}" var="Contact">
   <apex:inputField value="{!Contact.FirstName}"/>
   <apex:inputField value="{!Contact.LastName}"/>
</apex:repeat>

As you can see, there's very little to this - simply iterate the list of contacts and for each element, render inputs for the first and last name.  From the controller perspective, there's also even less - the list of contacts is a property that is initialised when the controller is constructed:

// the associated contacts
   public List<Contact> contacts {get; set;}
   
   public AccountContactsListExample(ApexPages.StandardController stdCtrl)
   {
     std=stdCtrl;
     contacts=[select id, FirstName, LastName, Email from Contact where accountid=:std.getId() order by firstname asc limit 5];
    }

In terms of hooking up the page and controller, that's all there is to it.  Each input field on the page is bound to a field from a Contact sobject in the list.  Changes that are made on the page will be reflected in the cached contacts list and are then available to be processed server side.

Here's the full page, with the button to allow saving of the changes:

<apex:page standardController="Account" extensions="AccountContactsListExample">
  <apex:form >
  <apex:outputText value="{!message}" rendered="{!LEN(message)>0}"/>
   <apex:pageBlock title="Account Detail">
      <apex:pageBlockSection title="Account">
            <apex:inputField value="{!Account.Name}"/>
            <apex:inputField value="{!Account.Description}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Contacts">
         <apex:repeat value="{!contacts}" var="Contact">
            <apex:inputField value="{!Contact.FirstName}"/>
            <apex:inputField value="{!Contact.LastName}"/>
         </apex:repeat>
      </apex:pageBlockSection>
  </apex:pageBlock>
   <apex:commandButton value="Save" action="{!save}"/>
  </apex:form>
</apex:page>

and here's the full controller. The save method persists the changes to the database, simply by executing an update on the list of contacts.

public class AccountContactsListExample 
{
    private ApexPages.StandardController std;
    
    public String message{get;set;}
    
    // the associated contacts
   public List<Contact> contacts {get; set;}
   
    public AccountContactsListExample(ApexPages.StandardController stdCtrl)
    {
     std=stdCtrl;
     contacts=[select id, FirstName, LastName, Email from Contact where accountid=:std.getId() order by firstname asc limit 5];
    }

    public PageReference save()
    {
     // first save the account
     std.save();
     
     // then save the contacts
     update contacts;
     
     PageReference result=ApexPages.currentPage();
     result.setRedirect(true);
     
     return result;
    }
    
}

This also demonstrates one way to allow editing of a parent (account) record and its children (contacts).

Saturday, 12 March 2011

Deactivating Users that are Running Dashboard User

A persistent issue when deactivating users is finding out some time down the line that they were the running user for a dashboard and being presented with the familiar error message:


And yes, I know its possible to run a custom report on dashboards and specify filter criteria to check the running user, but that seems a little clunky and requires too many clicks for my liking.

The next option I considered was a before delete trigger on the User object that would block the deactivation if the user was a dashboard running user.  I've never been keen on these sorts of triggers as I don't think its the greatest experience - you complete all the work and then at the last minute the platform bounces the request.  Plus if a user needs to be deactivated quickly and is the running user for a number of dashboards, a trigger would slow the process down.

What I really wanted was a way to check if there were going to be problems before I deactivate.

The first part to the solution was to create a Visualforce page to display any dashboards that would be affected.  Its a pretty straightforward page to display either a message that the user isn't a dashboard running user, or to list the affected dashboards:

<apex:page extensions="UserDashboardController" standardcontroller="User">
   <apex:pageblock title="User Dashboard Information">
 <apex:outputpanel rendered="{!NOT(UserNoDashboards)}">
        <apex:outputtext>
           User {!User.username} is the running user for the following dashboards:


        </apex:outputtext>
  <apex:pageblocktable value="{!userDashboards}" var="db">
   <apex:column headervalue="Dashboard">
      <apex:outputlink target="_blank" value="/{!db.id}">{!db.title}</apex:outputlink>
   </apex:column>
  </apex:pageblocktable>
 </apex:outputpanel>
 <apex:outputpanel rendered="{!userNoDashboards}">
  User {!User.username} is not a running user of any dashboards.
 </apex:outputpanel>
  </apex:pageblock>
</apex:page>

The page is backed by the User standard controller and an extension controller that adds a couple of methods for retrieving dashboard information:

public class UserDashboardController 
{
 User theUser;
 
 public UserDashboardController(ApexPages.StandardController std)
 {
  theUser=(User) std.getRecord();
 }
 
 public List<Dashboard> getUserDashboards()
 {
  List<Dashboard> dbs=[select id, Title from Dashboard 
                       where Type='SpecifiedUser' 
                         and RunningUserId=:theUser.id];
                  
  
  return dbs;
 }
 
 public Boolean getUserNoDashboards()
 {
  return getUserDashboards().isEmpty();
 }

}

Accessing the page with the id of the user I'm considering deactivating shows that they are the running user for one dashboard:


Unfortunately, Visualforce pages can't be embedded into User page layouts, so I need another mechanism to gain access to the Visualforce page. The solution is to create a custom link (Setup -> Customize -> Users -> Custom Links) that opens the Visualforce page in a new window. The configuration for my Custom Link is shown below:



I can then add this custom link to my User page layout and the next time I'm considering deactivating a user, I simply navigate to the user's detail record, click the link and the details appear in a popup window.


It would be quite simple to extend this to output other information pertinent to deactivating a user - the accounts that the user is the owner of for example.

Saturday, 5 March 2011

Visualforce Dynamic Map Bindings

A new feature in Spring 11 is Dynamic Visualforce Bindings.  Most of the documentation on this is around determining the record fields to display at runtime rather than compile time.  There's also a small section about using this for Lists and Maps.  Lists have been usable in a variety of ways such as apex:repeat and apex:pageBlockTable tags, but being able to retrieve the value from a map based on a key in a Visualforce page is something I've been working around for a while.  The workarounds haven't been unwieldy or difficult, but they've always been a compromise.

I've created a small example to demonstrate the usefulness of this new feature.  This is a page that displays either all accounts in the system:



 or just those that begin with a particular letter,



depending on the option that the user selects from a list.  Prior to Spring 11, I'd have to generate the list of accounts based on the user's selection server side.  As I said earlier, not a lot of work but I'd still prefer not to have to do it.

With the new feature, I can simply set up all my data in a map when constructing the page controller and then dynamically render the appropriate value from the map, based on the key, which is the user's selection.

The page markup is shown below:

<apex:page controller="DynamicBindingsMapExample">
  <apex:form >
    <apex:actionFunction name="redraw_accounts" rerender="accs" status="status"/>
    <apex:pageBlock title="Criteria">
       <apex:outputLabel value="Starting Letter"/>
       <apex:selectList value="{!selectedKey}" size="1" onchange="redraw_accounts()">
          <apex:selectOptions value="{!keys}" />
       </apex:selectList>
    </apex:pageBlock>
    <apex:pageBlock title="Accounts">
       <apex:actionstatus id="status">
          <apex:facet name="start"/>
          <apex:facet name="stop">
             <apex:outputPanel id="accs">
                <apex:pageBlockTable value="{!accountsMap[selectedKey]}" var="acc">
                   <apex:column value="{!acc.name}"/>
                   <apex:column value="{!acc.BillingStreet}"/>
                   <apex:column value="{!acc.BillingCity}"/>
                   <apex:column value="{!acc.BillingPostalCode}"/>
                </apex:pageBlockTable>
             </apex:outputPanel>
          </apex:facet>
       </apex:actionstatus>
    </apex:pageBlock>
  </apex:form>
</apex:page>


The accounts are made available to the page via a map property on the controller that associates a letter (or the String 'All') with a list of Accounts starting with that letter (or every account in the case of 'All').

public Map<String, List<Account>> accountsMap {get; set;}

The user's selection is stored in the selectedKey controller property and this is used to extract the appropriate accounts from the map in the pageblocktable component:

<apex:pageBlockTable value="{!accountsMap[selectedKey]}" var="acc">

When the user's selection changes, the redraw actionfunction is executed to redraw the pageBlockTable containing the accounts. Note that there is no action attribute specified, as I haven't had to write any server side code to change the accounts displayed - everything is handled client side.

<apex:actionFunction name="redraw_accounts" rerender="accs" status="status"/>



Update 12/02/2012 - as requested by Raj in the comments, here is the controller class:

public class DynamicBindingsMapExample 
{
    public Map> accountsMap {get; set;}
    public List keys {get; set;}
    public String selectedKey {get; set;}
    public Map accsByName {get; set;}
    
    public Set getMapKeys()
    {
    	return accountsMap.keySet();
    }
    
    public DynamicBindingsMapExample()
    {
    	accsByName=new Map();
    	List sortedKeys=new List();
    	accountsMap=new Map>();
    	accountsMap.put('All', new List());
    	List accs=[select id, Name, BillingStreet, BillingCity, BillingPostalCode 
    	                    from Account
    	                    order by Name asc];
    	                    
    	                    
    	for (Account acc : accs)
    	{
    		accountsMap.get('All').add(acc);
    		String start=acc.Name.substring(0,1);
    		List accsFromMap=accountsMap.get(start);
    		if (null==accsFromMap)
    		{
    			accsFromMap=new List();
    			accountsMap.put(start, accsFromMap);
    		}
    		accsFromMap.add(acc);
    		accsByName.put(acc.name, acc);
    	}
    	
    	keys=new List();
    	for (String key : accountsMap.keySet())
    	{
    		if (key != 'All')
    		{
    			sortedKeys.add(key);
    		}
    	}
    	sortedKeys.sort();
    	sortedKeys.add(0, 'All');
    	
    	for (String key : sortedKeys)
    	{
    		keys.add(new SelectOption(key, key));
    	}
    	
    	selectedKey='All';
    }
}

Saturday, 26 February 2011

Visualforce Field Sets

Here at BrightGen, we've always tended to advise customers that replacing edit pages with Visualforce should be a last resort, as it means coding is required if additional fields are created on the sobject.  With the advent of Field Sets in Spring 11, this becomes much less of an issue.

Field sets are well documented in the Salesforce help, so I won't reproduce any of that here.  Instead, here's an example using field sets to create an edit page with additional Visualforce functionality.

Firstly, I've created two Field Sets on the Account standard object.  The first is for general fields that I'll show at the top of the record:


While the second is for Address-specific fields:


Next I create my Visualforce page. The key markup is as follows:

<apex:pageBlock mode="maindetail" title="Account Edit">
        <apex:pageBlockButtons >
           <apex:commandButton value="Cancel" action="{!cancel}"/>
           <apex:commandButton value="Save" action="{!save}"/>
        </apex:pageBlockButtons>
        <apex:pageBlockSection title="General">
           <apex:repeat value="{!$ObjectType.Account.FieldSets.General}" 
                    var="field">
              <apex:inputField value="{!Account[field]}" />
           </apex:repeat>
        </apex:pageBlockSection>
        <apex:pageBlockSection title="Address">
           <apex:repeat value="{!$ObjectType.Account.FieldSets.Address}" 
                    var="field">
              <apex:inputField value="{!Account[field]}" />
           </apex:repeat>
        </apex:pageBlockSection>
        <apex:pageBlockSection title="Bar Chart">
    <div id="barchart" style="width: 450px; height: 25px;"></div>
 </apex:pageBlockSection>
     </apex:pageBlock>

Using the field set is as simple as accessing it from the $ObjectType global variable and iterating the fields:

<apex:repeat value="{!$ObjectType.Account.FieldSets.Address}" 
           var="field">
      <apex:inputField value="{!Account[field]}" />
   </apex:repeat>

The additional Visualforce functionality is a simple Dojo barchart, which is drawn in by Javascript into the barchart div.

Here's the generated page:


As an Administrator, if I then decide that I'd like to add the Industry field to the page.  I simply edit my General Field Set to add the field to the end of the set, refresh the page, and the new field is present with zero coding effort:


I've already used this in one solution that combines record creation with embedded searching capabilities.

Wednesday, 23 February 2011

Scheduled Testing with Cruise Control

There are a number of posts in the blogosphere around using Cruise Control with Salesforce.  However, these generally have a pure Continuous Integration focus, in that the code is checked out from source control repository, deployed to a dedicated Salesforce instance, compiled and tested.  This is a useful mechanism when in the development phase of a project, but not always the best solution once a customer is live.

At BrightGen we have a Service Management offering for Salesforce (and others), which means that from time to time we look after a solution that we haven't developed.  Even if we did build the original system, we will often be second or third line of support after local power users and administrators.  In these situations, we are interested in detecting if changes have been made that may cause problems - for example, a validation rule being applied that may preclude programmatic object creation.  To that end, we have used Cruise Control to set up daily execution of production unit tests.

If you don't already have Java installed on your target machine, install the JDK from the Oracle Download page.

Installing Cruise Control is pretty straightforward - a link to the latest version is available at the top of the download page. If you are using windows you can simply download an executable file and run that - its a stripped down version of full Cruise Control, but I've found its ideal for my needs.  Follow the defaults and it will install into c:\Program Files\CruiseControl and set itself up as a service.

Cruise Control uses Apache Ant to execute builds, and this is included in the installation.  In my install its located at C:\Program Files\CruiseControl\apache-ant-1.7.0.

The next step is to add the Force.com migration tool to Ant.  You can access this by logging into your Salesforce instance, navigating to the Setup page and opening the App Setup -> Develop -> Tools menu.  Click the Force.com Migration Tools link to start the download.  Once the download is complete, extract to a temporary directory - I used C:\temp.    Navigate to the temporary directory and you will find a file named ant-salesforce.jar.  Copy this file to the lib directory under the Cruise Control Ant installation.  In my case the command was

> copy c:\temp\ant-salesforce.jar "c:\Program Files\CruiseControl\apache-ant-1.7.0\lib"

Note that if you already have Ant installed and you have set up your ANT_HOME environment variable, Cruise Control will use that, so you should copy ant-salesforce.jar to lib directory of your existing Ant installation.

The next step is to create a project - navigate to your CruiseControl\Projects folder, and create a new folder - I've called mine BobBuzzard.  This folder needs to contain a couple of files.  Firstly build.xml:

<project name="Bob Buzzard Salesforce Automated Testing" default="compTest" basedir="." xmlns:sf="antlib:com.salesforce">

    <property file="build.properties"/>
    <property environment="env"/>

    <target name="compTest">
      <echo message="Executing tests on Salesforce Server ....."/>
      <sf:compileAndTest username="${sf.username}" password="${sf.password}" serverurl="${sf.serverurl}">
       <runTests allTests="true"/>
      </sf:compileAndTest>
      <echo message="Tests completed" />
    </target>

</project>

This is the XML file that controls the Ant build for the project.  The compTest target is the key element - this connects to the Salesforce instance and executes all tests.

Note that the user id/password and server url are parameterized rather than harcoded.  These are populated from the second file that needs to be created, build.properties:

# build.properties
#

# Specify the login credentials for the desired Salesforce organization
sf.username = <your username>
sf.password = <your password here>

# Use 'https://www.salesforce.com' for production or developer edition (the default if not specified).
# Use 'https://test.salesforce.com for sandbox.
sf.serverurl = https://login.salesforce.com

# If your network requires an HTTP proxy, see http://ant.apache.org/manual/proxy.html for configuration.
#

Finally, Cruise Control must be configured to build the project, via the config.xml file present in the CruiseControl directory.  My sample build file is shown below.  Note the publishers section at the bottom of the file - this sends out an email success/failure notification to my google mail account.

<cruisecontrol>
    <property name="BuildTime" value="0005"/>
    <project name="BobBuzzard" requireModification="false">
        <listeners>
            <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>
        </listeners>

        <schedule>
            <ant time="${BuildTime}" anthome="apache-ant-1.7.0" buildfile="projects/${project.name}/build.xml" target="compTest"/>
        </schedule>

        <log>
            <merge dir="projects/${project.name}/target/test-results"/>
        </log>

   <publishers>
  <email mailhost="smtp.gmail.com"
                username="..."
                password="..."
                mailport="465"
                usessl="true"
  returnaddress="keir.bowden@googlemail.com"
                subjectprefix="[CruiseControl]"
  buildresultsurl="http://localhost:8080/cruisecontrol/buildresults/BobBuzzard">
     <always address="keir.bowden@googlemail.com" />
  </email>
      </publishers>    
   </project>
</cruisecontrol>

Once all this is done, you can then fire up the Cruise Control service via the Windows Control Panel. New projects are built as soon as the service starts, after which they will be built at the time specified in the config.xml file.

As the installation includes its own Apache Tomcat server, you can navigate to http://localhost:8080/dashboard and see the results of the build. If all has gone well, your dashboard will show a green block for a successful build, which can be hovered over to see a summary:

The Cruise Control Dashboard

In the event of problems, the block will be red.  You can see the details of the build by clicking the block, selecting the Errors and Warnings tab on the resulting page and expanding the Errors and Warnings section as shown below:



If any exceptions were thrown running tests, or indeed connecting to Salesforce, they will appear here.

One word of caution - if a build fails, always check why.  It may be that the Salesforce password has been changed, in which case Cruise Control will happily retry it every day which may cause user lockout.

Saturday, 19 February 2011

Visualforce Re-rendering Woes

A couple of days ago I was tripped up by re-rendering for what seemed like the hundredth time.  I'd created a page containing a conditionally rendered component based on a boolean field from the controller. This field was initially set to false, so the component didn't appear.  The value could then be changed on the page via a checkbox and clicking a button would refresh a section of the page whereupon the component would magically appear.  Here's a much reduced version of the Visualforce markup.

<apex:page controller="RerenderController">
<h1>Rerender Example</h1>
<apex:form >
  <apex:outputPanel id="datePanel" rendered="{!showdate}"> 
    <apex:outputText value="Date : {!todaysDate}"/>
  </apex:outputPanel>
  <div>
     Show Date? <apex:inputCheckbox value="{!showDate}"/>
  </div>
  <apex:commandButton value="Submit" rerender="datePanel"/>
</apex:form>
</apex:page>

I changed the value, clicked the button and hey presto - nothing changed!  I went the usual route of adding debug to the controller, which showed that the showDate value as being updated correctly.  Maybe there was an error that we being hidden due to the re-rendering, so out came the rerender attribute on the command button. No sign of any error and things started behaving as expected.  At this point I remembered why this is happening.

Adding the rerender attribute back in and viewing the source for the generated page shows the following:

<h1>Rerender Example</h1> 
<form id="j_id0:j_id2" name="j_id0:j_id2" method="post" action="https://kab-tutorial.na6.visual.force.com/apex/RerenderExample" enctype="application/x-www-form-urlencoded"> 
<input type="hidden" name="j_id0:j_id2" value="j_id0:j_id2" /> 
 
  <div> 
     Show Date?<input type="checkbox" name="j_id0:j_id2:j_id5" /> 
  </div><input class="btn" id="j_id0:j_id2:j_id7" name="j_id0:j_id2:j_id7" onclick="A4J.AJAX.Submit('j_id0:j_id2',event,{'similarityGroupingId':'j_id0:j_id2:j_id7','parameters':{'j_id0:j_id2:j_id7':'j_id0:j_id2:j_id7'} } );return false;" value="Submit" type="button" /><div id="j_id0:j_id2:j_id8"></div><input type="hidden"  id="com.salesforce.visualforce.ViewState" name="com.salesforce.visualforce.ViewState" value="..." />
<form>

Notice that there is no element with an id containing the text datePanel, as the output panel isn't rendered due to the showDate value being false. Thus even though the showDate value is set to true, when the Ajax request completes, the element that should be re-rendered doesn't exist and therefore nothing changes.

The solution is to nest the conditionally rendered output panel inside another output panel and change the command button to rerender that. The containing output panel will always be present on the page and thus the Ajax request will be able to update it on completion. The inner component may or may not be rendered depending on the value of showDate.

Below is the revised Visualforce markup that behaves as desired:

<apex:page controller="RerenderController">
<h1>Rerender Example</h1>
<apex:form >
  <apex:outputPanel id="datePanelContainer">
    <apex:outputPanel id="datePanel" 
        rendered="{!showdate}"> 
      <apex:outputText value="Date : {!todaysDate}"/>
    </apex:outputPanel>
  </apex:outputPanel>
  <div>
     Show Date? <apex:inputCheckbox value="{!showDate}"/>
  </div>
  <apex:commandButton value="Submit" 
    rerender="datePanelContainer"/>
</apex:form>
</apex:page>