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>

Wednesday 9 February 2011

Layered Visualforce Lookup

Following on from an earlier post that explained how to replicate the standard Salesforce lookup in Visualforce, I now present the new and improved Layered Lookup. If you haven't read the original, I'd recommend you read that first as it covers how the lookup actually gets populated, which I haven't reproduced here.

This new lookup provides the same functionality as the original, but displays the lookup in a layer in the existing browser window, rather than opening a new popup.

The image below shows the new lookup in action:



Diving into the page, here are the key aspects to providing a layer.  First the styling that will display it correctly:

<style>

#popupcontent{
   position: fixed;
   top: 10%;
   left: 25%;
   width: 50%;
   height: 80%;
   display: none;
   overflow: auto;
   border:3px solid #585858;
   background-color:white;
   //border:1px solid #333;
   z-index:100;
   padding:5px;
   line-height:20px;
}
#opaque {
    position: fixed;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
    z-index: 1;
    display: none;
    background-color: gray;
    filter: alpha(opacity=80);
    opacity: 0.8;
    -moz-opacity:0.8;
    -khtml-opacity:0.8
}
* html #opaque {
    position: absolute;
}
</style>

the popupcontent style specifies the styling for my layer that replaces the popup, while the opaque style is used to turn the rest of the page grey so that the new layer stands out. Note that the both of these styles have a display setting of "none" - this means they will be hidden when the page is displayed.

Next there is the actual content that will be popped up:

<div id="opaque"/>
   <div id="popupcontent">
  <apex:form id="form" >  
        
     <div style="width 100%">
        <apex:pageBlock title="Lookup" id="block">
           
          <apex:pageBlockSection id="section">
              Enter search text and click Go<br/>
              <apex:inputText value="{!query}" id="query"/> 
              <apex:commandButton value="Go" action="{!runQuery}" rerender="results"/>
          </apex:pageBlockSection>
        </apex:pageBlock>

        <apex:pageBlock id="results">
          <apex:pageBlockSection columns="1">
              <apex:pageBlockTable value="{!accounts}" var="account">
                <apex:column headerValue="Name">
                  <apex:outputLink value="#" onclick="fillIn('{!account.Name}', '{!account.id}')">{!account.Name}</apex:outputLink>       
                </apex:column>
                <apex:column headerValue="Street" value="{!account.BillingStreet}"/>
                <apex:column headerValue="City" value="{!account.BillingCity}"/>
                <apex:column headerValue="Postcode" value="{!account.BillingPostalCode}"/>
              </apex:pageBlockTable>    
          </apex:pageBlockSection>
        </apex:pageBlock>
        <button type="button" onclick="hidepopup();">Close Window</button>
     </div>
   </apex:form>
 </div>

and finally there are a couple of javascript methods that handle switching the display of the opaque and popup elements to make them visible and invisible on demand:

function showpopup()
   {
      document.getElementById('opaque').style.display='block';
      var popUp = document.getElementById("popupcontent");
      
      popUp.style.display = "block";
      
   } 
   
   function hidepopup()
   {
      var popUp = document.getElementById("popupcontent");
      popUp.style.display = "none";
      document.getElementById('opaque').style.display='none';
   }
   

When the user clicks the Lookup button, the showpopup() javascript is invoked to grey the page out and render the lookup "popup". If the user searches, finds results and clicks on the name, a javascript method is invoked to fill in the account name and id and execute the closepopup() method. Clicking the Close Window button simply executes the closepopup() method.

The code and page can be downloaded here - note that there's now just a single page and controller!