Pages

Friday, 22 July 2011

Managing a list of New Records in Visualforce

This week's post concerns bulk creation of an unknown number of sobject records - Accounts, for example. In this situation I want to display a table containing a set number of new records (5 in this case), enter the various pieces of information and save to the database en-masse.  However, I also want to be able to add or remove rows prior to saving anything, so that if I need to I can enter 1, 3, 10 etc at once.  I promise this is the last post for a while around list handling!

As the records haven't been added to the database yet, they don't have a unique identifier that I can use to locate them, so the first task is to create a wrapper class that encapsulates an Account and an identifier - I decided on an Integer.

public class AccountWrapper
{
 public Account acc {get; private set;}
 public Integer ident {get; private set;}
  
 public AccountWrapper(Integer inIdent)
 {
  ident=inIdent;
  acc=new Account(Name='Bulk Acc ' + ident);
 }
}
Note that the wrapper class provides a useful constructor, that creates the wrapped record and automatically populates the Name of the Account based on the unique identifier. I added this as the Name is a required field when used as the value for an apex:inputfield in Visualforce, so this saved me some time when testing. I could just as easily have changed the component to an apex:inputText to work around this.

As I'm only concerned with the uniqueness of the AccountWrapper within the context of my page, generating a unique identifier is as simple as defining a property in my controller:

private Integer nextIdent=0;

and then post-incrementing this each time I create a new wrapper instance. In my constructor I create the initial five records:

wrappers=new List<AccountWrapper>();
for (Integer idx=0; idx<5; idx++)
{
 wrappers.add(new AccountWrapper(nextIdent++));
}

In my page, I have an apex:pageBlockTable backed by the wrappers list. Each row of the table has a Delete button that if clicked, removes the entry from the list:
<apex:column headerValue="Action">
    <apex:commandButton value="Delete" action="{!delWrapper}" rerender="wtable">
       <apex:param name="toDelIdent" value="{!wrapper.ident}" assignTo="{!toDelIdent}"/> 
    </apex:commandButton>
 </apex:column>

The unique identifier is passed to the controller via the apex:param component. If this is the first time that you've encountered this component, you may be interested in another blog post of mine explaining its use in detail .

The action method associated with the delete simply iterates the list of wrappers and deletes the entry associated with the supplied identifier:


public void delWrapper()
{
 Integer toDelPos=-1;
 for (Integer idx=0; idx<wrappers.size(); idx++)
 {
  if (wrappers[idx].ident==toDelIdent)
  {
   toDelPos=idx;
  }
 }

 if (-1!=toDelPos)
 {
  wrappers.remove(toDelPos);
 }
}
 

The page also contains a couple more buttons, for adding one row, adding 5 rows and saving to the database.

<apex:page controller="ManageListController" tabstyle="Account">
 <apex:form >
   <apex:pageBlock title="Bulk Account Create">
      <apex:pageBlockTable value="{!wrappers}" var="wrapper" id="wtable">
         <apex:column headerValue="Ident">
            <apex:outputText value="{!wrapper.ident}"/>
         </apex:column>
         <apex:column headerValue="Name">
            <apex:inputField value="{!wrapper.acc.Name}"/>
         </apex:column>
         <apex:column headerValue="Parent">
            <apex:inputField value="{!wrapper.acc.ParentId}"/>
         </apex:column>
         <apex:column headerValue="Industry">
            <apex:inputField value="{!wrapper.acc.Industry}"/>
         </apex:column>
         <apex:column headerValue="Type">
            <apex:inputField value="{!wrapper.acc.Type}"/>
         </apex:column>
         <apex:column headerValue="Action">
            <apex:commandButton value="Delete" action="{!delWrapper}" rerender="wtable">
               <apex:param name="toDelIdent" value="{!wrapper.ident}" assignTo="{!toDelIdent}"/> 
            </apex:commandButton>
         </apex:column>
      </apex:pageBlockTable>
      <apex:commandButton value="Add Row" action="{!addRows}" rerender="wtable">
         <apex:param name="addCount" value="1" assignTo="{!addCount}"/> 
      </apex:commandButton>
      <apex:commandButton value="Add 5 Rows" action="{!addRows}" rerender="wtable">
         <apex:param name="addCount" value="5" assignTo="{!addCount}"/> 
      </apex:commandButton>
      <apex:commandButton value="Save" action="{!save}"/>
   </apex:pageBlock>
 </apex:form>
</apex:page>

Note that I'm using the apex:param technique for both the Add Row and Add 5 Rows buttons - this means I can have additional buttons for any number of rows without having to change the Apex code.


The full controller is shown below:

public class ManageListController 
{
 public List<AccountWrapper> wrappers {get; set;}
 public static Integer toDelIdent {get; set;}
 public static Integer addCount {get; set;}
 private Integer nextIdent=0;
 
 public ManageListController()
 {
  wrappers=new List<AccountWrapper>();
  for (Integer idx=0; idx<5; idx++)
  {
   wrappers.add(new AccountWrapper(nextIdent++));
  }
 }
 
 public void delWrapper()
 {
  Integer toDelPos=-1;
  for (Integer idx=0; idx<wrappers.size(); idx++)
  {
   if (wrappers[idx].ident==toDelIdent)
   {
    toDelPos=idx;
   }
  }
  
  if (-1!=toDelPos)
  {
   wrappers.remove(toDelPos);
  }
 }
 
 public void addRows()
 {
  for (Integer idx=0; idx<addCount; idx++)
  {
   wrappers.add(new AccountWrapper(nextIdent++));
  }
 }
 
 public PageReference save()
 {
  List<Account> accs=new List<Account>();
  for (AccountWrapper wrap : wrappers)
  {
   accs.add(wrap.acc);
  }
  
  insert accs;
  
  return new PageReference('/' + Schema.getGlobalDescribe().get('Account').getDescribe().getKeyPrefix() + '/o');
 }
 
 public class AccountWrapper
 {
  public Account acc {get; private set;}
  public Integer ident {get; private set;}
  
  public AccountWrapper(Integer inIdent)
  {
   ident=inIdent;
   acc=new Account(Name='Bulk Acc ' + ident);
  }
 }
}

The only additional item of interest in the controller is the returned PageReference from the save method:
return new PageReference('/' + Schema.getGlobalDescribe().get('Account').getDescribe().getKeyPrefix() + '/o');

This sends the user to the Accounts tab. I originally had the three character key prefix hardcoded, but that always feels wrong. Thus, while its highly unlikely that Salesforce will change the prefix, if they do I'm covered.

Here's some screenshots of the page in action.  First the initial page:



After I've clicked the Add 5 Rows Button



After I've deleted some entries at random:



And finally, after saving the records they are displayed in the recent list on the Accounts tab:

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.

Friday, 1 July 2011

Trust Salesforce Notifications with Blogtrottr

In addition to being a Salesforce implementation and ISV partner, my employers (BrightGen) provide a managed service offering in the Salesforce space. As part of this, we are always looking to keep on top of the current Salesforce status so that we are aware of problems before our customers are affected.

A couple of releases ago, Salesforce added RSS feeds to the trust.salesforce.com site, which provides a feed of status updates on a per-instance basis. Useful information, but we needed a way to monitor this information in an unattended fashion and receive updates when something important happened.

A few googles later, I came across Blogtrottr.  This is an online service where you supply the URL of an RSS feed and an email address, and you are mailed feed updates as and when they are published.



This means that the next time an issue occurs on Salesforce, the information arrives in my mailbox without any effort on my part: