Saturday, 24 March 2012

Create Parent and Child Records in One Insert Call

If you're anything like me, creating parent and child records in Apex is something that has to be done on a pretty regular basis. This blog post is one I've had on the list for a while, but it came up on the discussion boards yesterday (March 2012) so it seemed like as good a time as any to write it up properly.

The simplest way to achieve this is to insert the parent, then set the lookup relationship id on the child to the parent record id, as follows:

Account acc=new Account(Name='Blog Acc1');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', AccountId=acc.id);
insert cont;

the downside to this approach is that it is unlikely to scale.  If I need to insert a large number of accounts and contacts, I'll have to manage the relationships myself in some way - probably using wrapper classes to  combine an account and its list of contacts, with multiple iterations to insert the accounts, then populate the lookup fields.  All in all quite a lot of code.

The next avenue I explored was setting the child relationship field to the parent record:

Account acc=new Account(Name='Blog Acc2');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc);
insert cont;

No dice on this I'm afraid - even though I've inserted the parent account first, the child contact is stored without a parent account.

One of the techniques that I came across while studying for the Technical Architect Certification was to specify the parent object via an external id.  So I created an external id field on my account named Master_Id__c and inserted an account:

Account acc=new Account(Name='Blog Acc3', Master_Id__c='Blog Acc3');
insert acc;

Once the account is in place, I can instantiate the parent record based on the external id and set the relationship field:

Account acc=new Account(Master_Id__c='Blog Acc3');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc);
insert cont;

Looking better, as I don't have to set ids, but I'm still inserting the parent first and then the child. Perhaps its possible to instantiate a new account and contact and then insert them later:

Account acc=new Account(Name='Blog Acc 4', Master_Id__c='Blog Acc 4');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc);
insert acc;
insert cont;

Once again, no dice. The same result as the second attempt - the account and contact are inserted, but the relationship is lost. Given that there's very little difference between this and the last attempt, it looks like its the name that is causing the problem. After trying a few permutations, the following code confirmed that this is the case:

Account acc=new Account(Name='Blog Acc 6', Master_Id__c='Blog Acc 6');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Name='Blog Acc 6', Master_Id__c='Blog Acc 6'));
insert cont;

This throws the following exception - System.DmlException: Insert failed. First exception on row 0; first error: INVALID_FIELD, More than 1 field provided in an external foreign key reference in entity: Account: [].
 I've no idea why this exception isn't thrown when I specify a previously instantiated account rather than instantiating as part of the contact record, but there it is.   Given this error message, it seemed possible to create a contact and identify the account by Name, but that didn't work either - a similar exception complaining that Name isn't an external id or indexed field.

 This did guide me to the preferred solution though - simply instantiating a new account as part of the contact and only specifying the external id:

Account acc=new Account(Name='Blog Acc 7', Master_Id__c='Blog Acc 7');
insert acc;

Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Master_Id__c='Blog Acc 7'));
insert cont;

Looking a lot better, but there are still two insert statements - luckily insert statements can take a list of generic sobjects to insert, so I can insert both objects in one go. As long as the parent record is inserted first, everything works as expected:
Account acc=new Account(Name='Blog Acc 8', Master_Id__c='Blog Acc 8');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Master_Id__c='Blog Acc 8'));

insert new List<Sobject>{acc, cont};

Monday, 19 March 2012

Salesforce Certified Advanced Administrator



This week saw the final piece in the Salesforce Certification puzzle, as I gained the Advanced Administrator Certification and now have the full set of 8.  Unfortunately, one of these (the original Consultant Certification) retires at the end of March, so I only have a couple of weeks before the total drops to 7.

As usual, please don’t ask for or post any actual exam questions.  Anything of this nature will be removed immediately.

The exam is 60 question with a time limit of 90 minutes and a pass mark of 67%.  In a refreshing change from the last few exams I’ve taken, the questions tend to be short and snappy and quite a lot of them fit onto a single line.  It was also unusual, in that I can’t remember any other exams where I had to know the difference between the editions, remember the permissions required for actions and identify the menu sections for various configuration items.  Make sure that you read the questions properly - its easy to overlook a ‘not’ or a ‘false’ if you skim read - if you find that there are two valid answers, but the question only requires one, check that you haven’t got the wrong end of the stick.

The study guide is the first port of call when preparing for this exam - everything that came up was mentioned.  Areas that you need know are:




  • Privacy (sharing) and security - these come up in every Salesforce Certification exam - if you don’t fully understand this side of things they are all going to be challenging.
  • Community setup - Ideas and Answers, along with their data categories.
  • Territory management - how it differs from the standard role hierarchy, pre-requisites for enabling
  • The various desktop tools - Connect for Office, Outlook Connector, Salesforce for Outlook, Offline Edition, Lotus Notes Connector, Apex Data Loader.
  • CRM Content - luckily I’ve trained a few customers on this so I had plenty of resources to fall back on.
  • Formula fields - best practice for efficient formulas, the various formula functions
  • Buttons and links, overriding standard and creating custom
  • Extending the platform using the web services API, Apex and Visualforce.  You don’t need to know the syntax or reference details, more the concepts of when its appropriate to use them and what the pros and cons are.
  • Capabilities conferred upon a delegated administrator.
  • Forensic investigations - I’d not come across this prior to reading the study guide - another good reason to read it!
  • How to author and deploy apex code and applications
  • Sorage usage - what does and doesn’t count against storage limits, plus strategies to manage storage effectively.
  • Workflow and approvals, how these can be used to automate business processes and where these fit into the order of execution along with triggers, validation rules, assignment rules etc
  • What you can and can't do with the page layout editor

I think this exam would be a struggle if you tried to memorise the configuration information as opposed to working with the system for some time.  Salesforce adds more and more functionality with each release, so there it would be a huge amount to remember.  Plus as an Advanced Administrator you will be expected to know the limits of the ‘clicks not code’ approach and how you’d go about customizing and extending via the APIs.  You will also need to know how to investigate problems and suspicious activity.



And finally a word for the blogger editor - this really excelled itself today. The contents of this post were deleted three times, HTML markup randomly embedded into text and continual failures to save.  This made what should have been a simple job extremely painful.

Saturday, 3 March 2012

Integrate Custom Date Picker with Visualforce

If you've used Visualforce input fields backed by a Date or DateTime sobject field, one thing you'll have noticed is the date range isn't that helpful.  For example, the screenshot below is from my developer edition and the starting year is 2011.

 
 
 
This is fine for opportunities, cases, campaigns etc., that have dates in the future, but less than useful when recording a date in the past - for example year of birth.  There's a couple of options here.  The first is to use some javascript on the page to tweak the setup of the date picker.  Not a solution I'd recommend, as a change to the underlying implementation would break the page.
 
The other option is to use a custom date picker.  I favour the Design2Develop implementation, as a lot of the others have style class names that overlap with the Salesforce implementation, which could cause problems if I wanted to combine both on the same page.
 
The first thing to do is get the zip file from the download page and then upload it as a static resource to your Salesforce organization.  I chose the name JSCalendar - if you choose a different one, change the $Resource entries in the sample code.
 
Next, create the Visualforce page and add following lines to include the core Javascript and styling:
 
(UPDATE 09/01/2014 - the latest zip needs the calendar folder specified as the location for the JavaScript and CSS - if you are having problems make sure these your imports match these lines)
<apex:includeScript value="{!URLFOR($Resource.JSCalendar,’calendar/calendar.js')}"/>
<apex:stylesheet value="{!URLFOR($Resource.JSCalendar,’calendar/calendar_blue.css')}" />

I've gone for the blue style, but there are several others in the zip file.   Next, define the date format that you'll be using - this is achieved by implementing the fnSetDateFormat function:

function fnSetDateFormat(oDateFormat)
{
 oDateFormat['FullYear'];  //Example = 2007
 oDateFormat['Year'];   //Example = 07
 oDateFormat['FullMonthName']; //Example = January
 oDateFormat['MonthName'];  //Example = Jan
 oDateFormat['Month'];   //Example = 01
 oDateFormat['Date'];   //Example = 01
 oDateFormat['FullDay'];   //Example = Sunday
 oDateFormat['Day'];    //Example = Sun
 oDateFormat['Hours'];   //Example = 01
 oDateFormat['Minutes'];   //Example = 01
 oDateFormat['Seconds'];   //Example = 01
 
 var sDateString;
 
 // Use dd/mm/yyyy format
 sDateString = oDateFormat['Date'] +"/"+ oDateFormat['Month'] +"/"+ oDateFormat['FullYear'];
 return sDateString;
}

Next, define the function that will manage the integration between the date picker and the input element. This takes two parameters - the object that fired the calendar popup (e.g. an image or button) and the id of the input element that contains the date.  This function will be called when the picker is displayed, and is responsible for extracting the current value from the input element and passing that to the library function.

function initialiseCalendar(obj, eleId)
{
 var element=document.getElementById(eleId);
 var params='close=true';
 if (null!=element)
 {
  if (element.value.length>0)
  {
   // date is formatted dd/mm/yyyy - pull out the month and year
   var month=element.value.substr(3,2);
   var year=element.value.substr(6,4);
   params+=',month='+month;
   params+=',year='+year;
  }
 }
 fnInitCalendar(obj, eleId, params);
}

Finally, add an input component.  Its possible to change the standard behaviour of the apex:inputField using javascript to tie to to the custom picker as opposed to the standard, but again this is a fragile solution and would preclude using standard and custom on the same, so I prefer use an apex:inputText instead.  As can be seen, the object that is passed to the initialiseCalendar function is the input field that is also identified by the id parameters - I could change the function to take a single parameter, but if I then wanted to have an image that is clicked on to open the calendar I'd be out of luck. I've made this the onmouseover event handler so that it opens in similar way to the standard picker.

<apex:inputText id="startdate" size="10" value="{!Campaign.StartDate}" onmouseover="initialiseCalendar(this, '{!$Component.startdate}')"/>

Here's a screen shot of the new picker:
There's a fair bit more you can do to customize this picker - check out the docs on the download page.

For the sake of completeness here is the full markup of the Visualforce page:

<apex:page standardController="Campaign">
<apex:includeScript value="{!URLFOR($Resource.JSCalendar,’calendar/calendar.js')}"/>
<apex:stylesheet value="{!URLFOR($Resource.JSCalendar,’calendar/calendar_blue.css')}" />
 <apex:pageMessages />
 <apex:form id="frm">
   <apex:pageblock id="pb">
     <apex:pageBlockButtons >
        <apex:commandButton value="Save" action="{!save}" />
        <apex:commandButton value="Cancel" action="{!Cancel}" />
     </apex:pageBlockButtons>
     <apex:pageBlockSection id="pbs">
     <apex:inputField value="{!Campaign.Name}" />
  <apex:inputText id="startdate" size="10" value="{!Campaign.StartDate}" onmouseover="initialiseCalendar(this, '{!$Component.startdate}')"/>
  <apex:inputText id="enddate" size="10" value="{!Campaign.EndDate}" onmouseover="initialiseCalendar(this, '{!$Component.enddate}')"/>
 </apex:pageBlockSection>
   </apex:pageblock>
 </apex:form>
 <script>
function fnSetDateFormat(oDateFormat)
{
 oDateFormat['FullYear'];  //Example = 2007
 oDateFormat['Year'];   //Example = 07
 oDateFormat['FullMonthName']; //Example = January
 oDateFormat['MonthName'];  //Example = Jan
 oDateFormat['Month'];   //Example = 01
 oDateFormat['Date'];   //Example = 01
 oDateFormat['FullDay'];   //Example = Sunday
 oDateFormat['Day'];    //Example = Sun
 oDateFormat['Hours'];   //Example = 01
 oDateFormat['Minutes'];   //Example = 01
 oDateFormat['Seconds'];   //Example = 01
 
 var sDateString;
 
 // Use dd/mm/yyyy format
 sDateString = oDateFormat['Date'] +"/"+ oDateFormat['Month'] +"/"+ oDateFormat['FullYear'];
 return sDateString;
}
  
    
function initialiseCalendar(obj, eleId)
{
 var element=document.getElementById(eleId);
 var params='close=true';
 if (null!=element)
 {
  if (element.value.length>0)
  {
   // date is formatted dd/mm/yyyy - pull out the month and year
   var month=element.value.substr(3,2);
   var year=element.value.substr(6,4);
   params+=',month='+month;
   params+=',year='+year;
  }
 }
 fnInitCalendar(obj, eleId, params);
}
 </script>
</apex:page>