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>

36 comments:

  1. This is awesome...not only was this a great tutorial, but even this date picker was pretty good and easy to implement...Thank you so much.

    ReplyDelete
  2. Good stuff as always. Thanks for sharing.

    ReplyDelete
  3. its very fabulous...thanks for your code.it really excellent...

    ReplyDelete
  4. This is just Awesome !! Thank you ..

    ReplyDelete
  5. If the locale of the logged in user is German, you get a 'Value '06/08/2012' cannot be converted from Text to Date' error on the VF Page. Any ideas?

    ReplyDelete
  6. German date formatting is apparently 'dd.mm.yyyy' - I guess you would need to change the fnSetDateFormat to build a format string appropriate to a user's locale.

    ReplyDelete
    Replies
    1. Indeed. To get Salesforce to ignore locale, use 'yyyy-mm-dd' format with Date.valueOf(String) - good article though. Thanks Bob!

      Delete
  7. Hi Bob..
    I Used Custom Date-picker in my Scenario..
    But,after selecting date from date-picker,if we click again on input field changing the year format to -200,-201,-202....
    i changed parameters from "var year=element.value.substr(6,4);" to "var year=element.value.substr(6,3);"
    Then it is working well...
    thanks very much...

    ReplyDelete
  8. Unable to download the zip file. Please suggest

    ReplyDelete
    Replies
    1. I've just clicked through that link and downloaded the file again without any problems. What problem are you seeing?

      Delete
  9. I think we have found easier way to do this. You can find our how-to here:
    http://blog.enxoo.com/en/2013/03/native-datepicker-calendar-pop-up-for-inputtext-on-your-visualforce-page/

    ReplyDelete
    Replies
    1. This hits the same problem that I have referred to in this post. If Salesforce change the underlying implementation it will stop working - this is too fragile for me. The only way to be absolutely sure that your solution is future proofed is to introduce your own date picker.

      Delete
  10. Very helpfully...good stuff..!!

    ReplyDelete
  11. This was exactly what I was looking for. Thank you

    ReplyDelete
  12. Hi Bob,

    I have done exactly as stated but the date picker is not showing on the VF page.

    Anything that could have possibly gone wrong?

    Thanks

    ReplyDelete
    Replies
    1. I've updated the post - I think the format of the zip file changed as I now need to specify the JS/CSS is in the calendar folder. Update the includes and it should all work.

      Delete
  13. Hi Anonymous (16 Dec), I have the same problem as you too.

    Bob, the calendar isn't showing. I put an alert right before "fnInitCalendar(obj, eleId, params);" so I know it's executing right up to that point, but no calendar shows. Any ideas?

    Thanks
    King

    ReplyDelete
    Replies
    1. Do you see any JavaScript errors?

      Delete
    2. I've updated the post - I think the format of the zip file changed as I now need to specify the JS/CSS is in the calendar folder. Update the includes and it should all work.

      Delete
  14. I have the same problem as King Koo, the VF page shows with no errors, but calendar is not showing!! i downloaded the calendar.zip file and uploaded it as a resource and called it JSCalendar.

    ReplyDelete
    Replies
    1. Do you see any JavaScript errors?

      Delete
    2. I've updated the post - I think the format of the zip file changed as I now need to specify the JS/CSS is in the calendar folder. Update the includes and it should all work.

      Delete
    3. Hi Bob Buzzard,
      I have facing problem of calender is not showed when i click on the input check box.
      I think the problem is because of zip file format.

      can you suggest me

      Delete
  15. Hello Bob,

    I followed the instructions you mentioned, but my VF page shows an error, as displayed below. It would be really helpful for me, if you could please bail me out of this problem. Thanks in advance.
    "Error Error: value="{!URLFOR($Resource.JSCalendar,’calendar/calendar.js')}" EL Expression Unbalanced: ... {!URLFOR($Resource.JSCalendar,’calendar/calendar.js')}
    Error Error: EL Expression Unbalanced: ... #{URLFOR($Resource.JSCalendar,’calendar/calendar.js')}"

    Best Regards,
    Pavan

    ReplyDelete
    Replies
    1. Had the same problem - caused by copy/paste.
      Use Eclipse to replace that first single quote, it doesn't copy across correctly

      Warlock

      Delete
  16. Thank You Bob...
    It's Really Good Stuff...

    ReplyDelete
  17. Thank You So much Bob for u r Help ... really good stuff and easy to follow
    And Keeping Posting will help for the newbie's like me

    ReplyDelete
  18. Hi Bob.

    Trying to extend the standard page Date Picker from the console component.
    Is it possible ? Any other ways?

    ReplyDelete
  19. Bob ! you're a Genius!

    ReplyDelete
  20. This comment has been removed by the author.

    ReplyDelete
  21. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  22. hi Bob, Your Post is amazing .. I tried using ur code but after the page refresh the format of the date is getting changed and i'm getting this error.
    ' cannot be converted from Text to com.force.swag.soap.DateOnlyWrapper '

    thanks.

    ReplyDelete
  23. I can't get to the download page for the calendar anymore. Did something change?

    ReplyDelete
  24. Hi Bob, thanks for this post. Is there a solution or work-around for displaying the date picker on a date field in a related list record, e.g. the service date on an opportunity line item (for example)?

    ReplyDelete