Thursday, 24 July 2014

Adding a picture to a record in Salesforce

The concept of "Profile pictures" is pretty common these days, and default pictures of products or locations are standard fare for most CMS systems and websites. Getting a default picture displayed directly on a Salesforce record can be a little tricky. In this blog post I am going to talk through how I added my own, user manageable contact picture directly on the Contact record page (but you can re-purpose it however you like to other object types).

[Incidentally I am aware of Social-Contact settings, but more often than not this doesn't provide a "suitable" photo of a contact - my facebook picture certainly isn't! - and it only works for Accounts and Contacts of course]

Here is the goal we are after then:


You can see the cool picture associated with the Contact there, and a button to "Upload new photo". Lets walk through the other cool functions of this simple feature, and then I'll tell you how I did it (copy-pastas can just scroll down for the code snippets).

When you first create/arrive at a contact page without a picture, you are presented with this:


Clicking the "Upload photo" button presents this standard form:


Which in turn allows a user to select a local file and "Save" it to the Salesforce record. Now whenever they view the record, they will be presented with this photo, and a button to overwrite this file with a new photo, which takes them via the same form as above.

The process actually taken here is that the form uploads a file to the "Notes and Attachments" of the record, with a specific filename (set in the Controller). The controller is careful to make sure there is only ever one of these files (in the obscure case another file is added manually, the first is retrieved by the page, and next time the profile picture is updated, it will clear out all spurious files)


By doing this, we don't need any new "rich text fields" on our record, or indeed anything more complicated than a Visualforce snippet and Apex Controller extension.

The visualforce page:


<apex:page standardController="Contact" extensions="ContactPhotoUploadController" showHeader="false" standardStyleSheets="true" sidebar="false">

<apex:form id="contentForm">
<div style="height:170px;">
    <apex:pageBlock mode="maindetail">
    
        <apex:pageblocksection columns="1" rendered="{!displaying}">
            <apex:image height="150" value="{!URLFOR($Action.Attachment.Download, currentPicture)}" rendered="{!currentPicture != null}"/>
            <apex:outputPanel rendered="{!currentPicture == null}"><em>No picture currently available</em></apex:outputPanel>
        </apex:pageblocksection>
        
        <apex:pageblocksection columns="1" rendered="{! !displaying}">
            <p>Use the button to below to select a new file and then press "Save"</p>
            <apex:inputFile value="{!profilePicFile}" />
            <p>Or press Cancel to return.</p>
        </apex:pageBlockSection>
        
    </apex:pageBlock>
</div>
    <apex:commandButton value="Upload new photo" action="{!toggle}" rerender="contentForm" rendered="{!displaying && currentPicture!=null}"/>
    <apex:commandButton value="Upload photo" action="{!toggle}" rerender="contentForm" rendered="{!displaying && currentPicture==null}"/>
    <apex:commandButton value="Cancel" action="{!toggle}" rendered="{! !displaying}"/>
    <apex:commandButton value="Save" action="{!saveFile}" rendered="{! !displaying}"/>
</apex:form>
  
</apex:page>

This handles the display (or not) of the picture, form and relevant buttons.. and also sets the height that we will be working with (which is relevant when we come to add this to the page layout in a second).

Here is the controller extension that facilitates the uploading of the file and retrieving it when the page is loaded:
public with sharing class ContactPhotoUploadController {

    Private Static FINAL String fixedFileName = 'profilePhoto.jpg';

    public boolean displaying { get; set; }
    public Contact pageContact;
    public Blob profilePicFile { get; set; }
    public Id currentPicture { get; set; }
    
    /** Constructor, grab record, and check/load an existing photo */
    public ContactPhotoUploadController(ApexPages.StandardController controller) {
        pageContact = (Contact)controller.getRecord();
        
        List<attachment> currentPictures = [SELECT Id FROM Attachment WHERE parentId = :pageContact.Id AND name = :fixedFileName LIMIT 1];
        if(currentPictures.size() != 0) {
            currentPicture = currentPictures.get(0).Id;
        }
        
        displaying = true;
    }

    /** toggle switches between the photo display and photo upload form */
    public void toggle() {
        displaying = !displaying;
    }
    
    /** saveFile clears any existing profile picture, retrieves the data from the form, and saves it under the relevant filename*/
    Public Pagereference saveFile() {

        // first, we cannot have any conflicting files
        List<attachment> savedPicture = [SELECT Id, name, body FROM Attachment WHERE parentId = :pageContact.Id AND name = :fixedFileName];
        if(savedPicture.size() > 0) {
            delete savedPicture;
        }
       
        // Now, we save the new blob
        Attachment a = new Attachment(parentId = pageContact.Id, name = fixedFileName, body = profilePicFile);
        insert a;
        
        currentPicture = a.Id;
        
        displaying = true;
        return null;
    }
    

}

Once you have this basic Visualforce page up and running, all you need to do is go over and configure your Contact (in this case) Page Layout, and add the Visualforce page to the screen, and set it to a height of 30px greater than the style height of the div - this is to fit the buttons in underneath.



Boom, there you go, now users can add and edit a default photo on any kind of record page (you just need to tweak the controller to use the standard controller of another object).

Please note, in this blog post example I have neglected to address any kind of permissions or security considerations.. so remember if you decide to implement this to think about which users should be able to load, or upload photos to records. That's the kind of rich, useful developer love you would get from a Desynit based implementation ;-)

If you have any queries about this, or would like a complete implementation of this brought to your Salesforce org. Please don't hesitate to get in touch with me directly, or give Desynit a call and we can sort you out!

14 comments:

  1. Hi Simon

    I'm need some help with the above code. Instead of this standard contact object. I tried this code with my custom object. So far things are good up to the uploading and I can see the picture in the attachments. Also it displayed the picture on my visualforce page after uploading. The problem is it isn't showing the picture from the attachments. Please help me how can I modify this part of the code to get it work with my custom object?
    /** Constructor, grab record, and check/load an existing photo */
    Also there is no option in the custom object's page layout known as Visualforce. I can't show this picture on the page layout as you explained above. I'm looking for some generous help to learn these things. I'm just a beginner. Thanks.

    ReplyDelete
  2. Hi Simon

    Good news is this that I sort it out the first part. I'm able to retrieve the saved picture from the attachments. But now I'm curious to know that why do we are using the ApexPages.StandardController for the contact object? What is the actual purpose of this line?
    pageContact = (Contact)controller.getRecord();
    Moreover the use of toggle and displaying is confusing can you please explain how it is actually working in between the view states? Or give me a link to some article to read about this particular topic. I'll be very grateful to you. Please don't mind I chose you as my Teacher :)

    Thanks for sharing this wonderful blog post.

    ReplyDelete
    Replies
    1. When you write an "extension" to a page controller (like we have) it is initiated in the order of execution by passing the standard controller into the constructor. All pages that deal with a specific object type (such as a contact, or account) have a Standard controller - which provides the basic methods such as save() etc.etc. as well as managing the getting and setting of the record fields.

      In this scenario, our controller extension does need to know some stuff about the record in question, so when we construct our class, we use the line:

      pageContact = (Contact)controller.getRecord();

      to pass the "record" within the scope of the "parent" standard controller to an instance variable in our extension, this means when we need to use the ID for the record on line

      List savedPicture = [SELECT Id, name, body FROM Attachment WHERE parentId = :pageContact.Id AND name = :fixedFileName];

      we have access to it.


      The toggling is just using a boolean flag to determine whether we are rendering the picture, or the form. One of the standard visualforce tag attributes is "rendered" - in to which we can tokenise a boolean, or put formula equations to determine whether an element is included in the page. All we really do here is switch the boolean "displaying" back and forth when the buttons are clicked (in conjunction with "rerender" to paint the page again). It's slightly more complicated because I've been all posh and added another condition to the "upload" button to say "new photo" - when a replacement is being made rather than the first time upload... but using Rendered and ReRender is very simple, if you have a play with it in a sandbox.

      I hope that helps.

      Delete
    2. Hi Simon, thanks for this!

      Wondering - does this workaround allow you to pull the photos to a report and print? Thanks!

      Delete
  3. What a great post here i have got about Application Development i really like it and happy to get it.

    ReplyDelete
  4. Hey Simon, thanks for that code I managed to get it to work with my custom object.

    Only one question - how can I center the image on that page? I'm using a 1 column section in the page layout and placing the pic at the bottom after all the fields but it ends up on the extreme left of page, to the left of the fields above it and looks out of place. How can I easily center the pic?

    ReplyDelete
    Replies
    1. Hi Daryl, glad you found this useful. They still make jokes at my office about my choice of screenshots in this post..!

      If you have put the visualforce snippet in a one column block (or anywhere in fact) - I would imagine you can alter it's appearance using some standard CSS in the visualforce page.

      I'm afraid I can't actually trial a solution for you right now, but where the visualforce has the "<apex:image ..." tag - I would start by wrapping that in an HTML div with some style on it.. so it becomes something like this (I have removed the angle brackets because I have a horrible feeling the comments box will strip the code out otherwise)


      div style="text-align:center; width: 100%"

      apex:image height="150" value="{!URLFOR($Action.Attachment.Download, currentPicture)}" rendered="{!currentPicture != null}"/

      /div


      Does that help? If it makes no difference, its probably due to being nested in the "apex:pageblocksection" - so you could try fiddling some "style=" stuff on there too?

      Positions always comes down to the "style" tags - and one way or another almost anything is eventually possible! Good luck.

      Delete
  5. Thanks Simon,

    I tried the div and style and even center in lots of places and it made no difference so I finally got limited success with this [kludge] with the padding-left style:



    No picture currently available


    At least the image is across further on the page, not centered but it looks better.

    ReplyDelete
  6. apex:pageblocksection columns="1" rendered="{!displaying}"

    apex:image height="150" value="{!URLFOR($Action.Attachment.Download, currentPicture)}" rendered="{!currentPicture != null}" style="padding-left:200px"/

    ReplyDelete
  7. This is good stuff brother. Will need to try this for sure! Don't worry about the pictures. They are a-okay USA!

    ReplyDelete
  8. Hi Simon,

    This is a great post! Thank you so much for sharing. I have utilized this for a pro-bono project for an art studio I am working with. Our use case is uploading a picture of the artwork into a custom object called Art Inventory.

    I have completed the visualforce page and controller in the sandbox, but unfortunately have ran into some issues uploading into production. The code coverage is only at 71% and SF needs at least 75%. Is there something that I am missing in order to deploy to Production?

    Thanks!

    Jessica

    ReplyDelete
  9. Hi Jessica,

    If ever you want to have the ultimate way to manage images within Salesforce, you may be interested into Sharinpix. It's pretty easy to install and use and dedicated tool from salesforce. You can join me about this on twitter if needed @jmmougeolle.

    Feel free to ask directly for any information about this on my twitter : http://twitter.com/jmmougeolle

    ReplyDelete
  10. Any thoughts on adapting this to add images to the account object as a field instead of an attachment?

    ReplyDelete
  11. Thanks Simon. Got it working straight away. Good stuff.

    ReplyDelete