Saturday 26 November 2011

Retrieve Related Object Fields

A short post this week, again based on a question from the DeveloperForce Visualforce Discussion Board regarding the retrieval of a related objects fields.  In this particular case, an sobject is being created/edited via Visualforce and once a lookup field is populated, the developer wanted to display some fields related to the lookup.

Unsurprisingly, simply adding some outputfields for the  related object as shown below doesn't do the trick:


<apex:page standardcontroller="Contact">
 <apex:form >
   <apex:pageBlock title="Contact Create/Edit">
      <apex:pageBlockSection title="Contact Information">
       <apex:inputField value="{!contact.FirstName}"/>
       <apex:inputField value="{!contact.LastName}"/>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Account Information">
       <apex:inputField value="{!contact.AccountId}"/>
       <apex:outputField value="{!contact.Account.AccountNumber}"/>
       <apex:outputField value="{!contact.Account.Site}"/>
      </apex:pageBlockSection>
   </apex:pageBlock>
  </apex:form>
</apex:page>

Once the lookup is selected, the outputfield values remain empty, as all that has been specified is the id of the account via the inputfield - the related object details aren't populated in the sobject graph, so attempting to traverse the account relationship doesn't bring back any data:



One way to get the information back would be to save the contact once the lookup is populated and carry out a client side redirect to the current page with the id of the newly created contact.  When the standard controller retrieves the new sobject, it will populate the related Account information automatically.  This isn't a great user experience though, as the save is likely to be well before the user is ready, and doesn't give them a chance to change their mind part way through.  Likely to lead to a lot of unwanted and half-populated contacts.

Therefore it looks like an extension controller is the route to go. The page has been updated with an actionsupport component that is attached to the account lookup. This invokes an action method on the extension controller which executes a SOQL query to populate the related account information.

Revised Page:

<apex:page standardcontroller="Contact" extensions="RelatedController">
 <apex:form >
   <apex:pageMessages id="msgs"/>
   <apex:pageBlock title="Contact Create/Edit">
      <apex:pageBlockSection title="Contact Information">
       <apex:inputField value="{!contact.FirstName}"/>
       <apex:inputField value="{!contact.LastName}"/>
      </apex:pageBlockSection>
       <apex:actionRegion >
          <apex:pageBlockSection id="accinfo" title="Account Information">
         <apex:inputField value="{!contact.AccountId}">
            <apex:actionSupport event="onchange" action="{!AccountPopulated}" rerender="accinfo, msgs"/> 
         </apex:inputField>
         <apex:outputField value="{!contact.Account.AccountNumber}"/>
         <apex:outputField value="{!contact.Account.Site}"/>
        </apex:pageBlockSection>
      </apex:actionRegion>
      <apex:pageBlockButtons >
        <apex:commandButton value="Cancel" action="{!cancel}"/>
        <apex:commandButton value="Save" action="{!save}"/>
      </apex:pageBlockButtons>
   </apex:pageBlock>
  </apex:form>
</apex:page>

Extension controller:

public with sharing class RelatedController 
{
 private ApexPages.StandardController stdCtrl;
 
 public RelatedController(ApexPages.StandardController std)
 {
  stdCtrl=std;
 }
 
 public void AccountPopulated()
 {
  Contact cont=(Contact) stdCtrl.getRecord();
  cont.Account=[select AccountNumber, Site from Account where id=:cont.AccountId];
 }
}
Choosing the account now carries out an Ajax request and rerenders the account information with the fields populated.


Also, as I've been able to write the information into the object relationship, its the same markup that renders the account information regardless of whether it was retrieved via my extension controller or via the standard controller "reflection" when the page is opened with a specified contact id.

Finally, a word of advice - if you are writing an extension controller for this purpose, make sure that the related object has some data present in the fields, otherwise you'll be convinced your code is failing when in fact its working perfectly, just rendering empty fields!

Saturday 19 November 2011

Bypass Validation Rules from Apex

Today's post concerns a topic that crops up pretty frequently on the Developerforce Discussion Boards - how to allow apex code to bypass validation rules.

In  my example scenario, I've created a validation rule against the Contact standard object that checks either the contact's email address or phone number has been provided:

Thus when I attempt to create a Contact via the UI, if I don't provide one of these pieces of information I receive an error message:


All well and good.  However, what about the situation where I am creating a Contact programmatically? In this case the validation rule is still applied:


Obviously creating via the system log is a contrived example, but in practice I may be receiving the details from an external system as part of an integration (via an apex web service) or data migration (via the Apex Data Loader), and the information may simply not be available in that system.  If its a data migration, I could  disable the validation rule for the duration, but if its a real-time integration that isn't an option.

The solution I came up with was to provide a mechanism for the record itself to indicate how it was being created/edited and to take that into account for in the validation rule.

The first step in the solution is to create a new custom field on the Contact object - a checkbox that if set to true indicates the record is being processed in an apex context.  This checkbox is not made available on any page layouts, as we don't want regular UI users to be able to check it.


Next, change the validation rule to allow both the email and phone fields to be empty if the apex context field is set to true:


Its tempting at this time to think that the problem is now solved - the Apex Context field can simply be set to true and the validation rule will allow the record through.  However, the record will then be saved to the database with the Apex Content field set to true, and the next time that a user edits the record in the UI, the validation rule will be bypassed - not the behaviour I am looking for.  Therefore I've created a workflow rule and associated field update to unset the checkbox whenever it is found to be set.

Workflow rule:


Field update:


and that's all there is to it.  Now I can create a contact via the system log without providing the email or phone field:


But if I then navigate to that contact via the UI and attempt to edit the record without providing these fields, the validation rule blocks this:



Saturday 12 November 2011

Automatically Add User to Chatter Group

Following on from an earlier blog post that dealt with user autofollowing, I was recently asked about automatically adding users to a chatter group.  Here at BrightGen we have a chatter group that everyone gets added to when a user is created for them on our Salesforce instance.  Its not too arduous to add these users to the group, but every now and then one gets overlooked.

Chatter groups are stored as CollaborationGroup records, with the members of the group stored in CollaborationGroupMember records related via the CollaborationGroupId field.

The first step is to create a trigger that is executed wheh a user record is added.  I've reused the trigger I created for my autofollow user post:


trigger ai_user on User (after insert) 
{
 ChatterAutofollow.AutofollowUsers(trigger.newMap.keySet());
 ChatterAutoFollow.AddToGroups(trigger.newMap.keySet());
}

The second line, containing the call to the AddToGroups is the new part - this is the method that does the heavy lifting.

@future
public static void AddToGroups(Set<Id> userIds)
{
 List<User> users=[select id, Username from User where id in :userIds];
 
 // set up the groups that the user should be added to
 List<String> groups=new List<String>{'All Users'};
 
    List<CollaborationGroup> chatterGroups=[select id, Name from CollaborationGroup where name in :groups];

 List<CollaborationGroupMember> chatterGroupMembers=new List<CollaborationGroupMember>();
 List<FeedPost> feedPosts=new List<FeedPost>();
      
     // loop the users that have been created
     for (User user : users)
     {
                // loop the groups
      for (CollaborationGroup chatterGroup : chatterGroups)
      {
                        // add the user to the group
   CollaborationGroupMember cand = 
       new CollaborationGroupMember(
                      CollaborationGroupId=chatterGroup.id,
                            MemberId = user.Id);
       chatterGroupMembers.add(cand);
                        // announce this!
          FeedPost fpost = new FeedPost(ParentId=chatterGroup.id,
                Body = user.username + ' added to group via Bob Buzzard trigger');
                    feedPosts.add(fpost);
          fpost = new FeedPost(ParentId=user.id,
                    Body = 'You have been added to the ' + chatterGroup.name + ' group via Bob Buzzard trigger');
                  feedPosts.add(fpost);
  }
 }
    
 insert chatterGroupMembers;
 insert feedPosts;
}

as before, the method has to be declared with the @future annotation, as the trigger is invoked when a User is inserted and I'll otherwise hit mixed mode DML problems.

The group name is hardcoded for the purposes of this blog - not a good idea in practice and for a production system I'd store this information in a custom setting.  Once the groups have been retrieved, the inserted users are iterated and added to each of the groups.  As an added flourish I've added a post to the group to notify members the new user has joined, and a post to the new user's feed to let them know this has happened.  As I'm keen to grab all the credit that is going, I've also mentioned in each post that this was done via my trigger!

Now I can add a user via the UI:


Once the user is created, they are automatically added to the All Users group and a post announces this:


And navigating to the new users profile, shows a post on their chatter feed informing them this has taken place:


And finally, a shout out to my BrightGen colleage Zak Crammond who asked the question that led to this post.