Create a Lightning Component for Drag-and-Drop Profile Pictures

Upload a profile picture using drag-and-drop, and display it on the Record Home page.

Lightning Components and App Builder make it easy to customize Record Home Pages (pilot feature in Winter 16). Sometimes a simple component can add a lot of value to a page by streamlining a common task.

In this article I share a simple ProfilePicture component that allows you to upload a profile picture using drag-and-drop, and display it on the Record Home page. It can be used for Accounts, Contacts and other objects.

Watch the video below to see how to install and use the ProfilePicture component.

Source Code

Component

<aura:component controller="ProfilePictureController"
                implements="flexipage:availableForAllPageTypes,force:hasRecordId">
    
    <!-- Id of the Record the page hosting this component is showing -->
    <aura:attribute name="recordId" type="Id"/>

    <aura:attribute name="pictureSrc" type="String" 
            default="https://s3-us-west-1.amazonaws.com/sfdc-demo/image-placeholder.png"/>

    <aura:attribute name="message" type="String" default="Drag profile picture here"/>

    <aura:handler name="init" value="{!this}" action="{!c.onInit}" />
    
    <div ondragover="{!c.onDragOver}" ondrop="{!c.onDrop}">
        <img src="{!v.pictureSrc}"/>
        <p>{!v.message}</p>
    </div>
    
</aura:component>
  • The flexipage:availableForAllPageTypes interface indicates the component can be used in App Builder
  • The force:hasRecordId interface indicates the current record Id should be injected in the component’s recordId attribute

Controller

({  
    // Load current profile picture
    onInit: function(component) {
        var action = component.get("c.getProfilePicture"); 
        action.setParams({
            parentId: component.get("v.recordId"),
        });
        action.setCallback(this, function(a) {
            var attachment = a.getReturnValue();
            console.log(attachment);
            if (attachment && attachment.Id) {
	            component.set('v.pictureSrc', '/servlet/servlet.FileDownload?file=' 
                                                  + attachment.Id);
            }
        });
        $A.enqueueAction(action); 
    },
    
    onDragOver: function(component, event) {
        event.preventDefault();
    },

    onDrop: function(component, event, helper) {
		event.stopPropagation();
        event.preventDefault();
        event.dataTransfer.dropEffect = 'copy';
        var files = event.dataTransfer.files;
        if (files.length>1) {
            return alert("You can only upload one profile picture");
        }
        helper.readFile(component, helper, files[0]);
	}
    
})

Helper

({
    readFile: function(component, helper, file) {
        if (!file) return;
        if (!file.type.match(/(image.*)/)) {
  			return alert('Image file not supported');
		}
        var reader = new FileReader();
        reader.onloadend = function() {
            var dataURL = reader.result;
            console.log(dataURL);
            component.set("v.pictureSrc", dataURL);
            helper.upload(component, file, dataURL.match(/,(.*)$/)[1]);
        };
        reader.readAsDataURL(file);
	},
    
    upload: function(component, file, base64Data) {
        var action = component.get("c.saveAttachment"); 
        action.setParams({
            parentId: component.get("v.recordId"),
            fileName: file.name,
            base64Data: base64Data, 
            contentType: file.type
        });
        action.setCallback(this, function(a) {
            component.set("v.message", "Image uploaded");
        });
        component.set("v.message", "Uploading...");
        $A.enqueueAction(action); 
    }

})

Style

.THIS {
    text-align: center;
}

.THIS > p {
    font-size: 14px;
    color: #CCCCCC;
    margin: 4px 0;
}

.THIS img {
    min-height: 100px;
    max-height: 200px;
    max-width: 300px;
	width: auto;
    height: auto;
    border-radius: 4px;
}

Design

This part is required to use the component in App Builder. The attribute makes the initial message (“Drag profile picture here”) configurable in App Builder.

<design:component label="ProfilePicture">
	<design:attribute name="message" label="Message"/>
</design:component>

Apex Controller

public with sharing class ProfilePictureController {
    
    @AuraEnabled
    public static Attachment getProfilePicture(Id parentId) {

        // Attachment permissions are set in parent object (Contact)
        if (!Schema.sObjectType.Contact.isAccessible()) {
            throw new System.NoAccessException();
            return null;
        }
 
        return [SELECT Id, Name, LastModifiedDate, ContentType FROM Attachment 
            WHERE parentid=:ParentId AND ContentType IN ('image/png', 'image/jpeg', 'image/gif') 
            ORDER BY LastModifiedDate DESC LIMIT 1];
    }
    
    @AuraEnabled
    public static Id saveAttachment(Id parentId, String fileName, String base64Data, String contentType) { 

        // Edit permission on parent object (Contact) is required to add attachments
        if (!Schema.sObjectType.Contact.isUpdateable()) {
            throw new System.NoAccessException();
            return null;
        }

        Attachment attachment = new Attachment();
        attachment.parentId = parentId;
        attachment.body = EncodingUtil.base64Decode(base64Data);
        attachment.name = fileName;
		attachment.contentType = contentType;
        insert attachment;
        return attachment.id;
    }

}
  • Adjust the code to work with a specific object type. For example, replace Contact with Account if you want to use the component with Account objects. You could also write a generic version that detects the object type based on the id, and checks permissions on that object type.
  • getProfilePicture() retrieves the profile picture for a specific record. By convention, we use the latest image attached to the record. You could easily tweak that strategy. For example, you could add a custom field to the parent object to keep track of the Id of the specific attachment you want to use as the profile picture.
  • saveAttachment() creates the attachment for the picture you uploaded

Installation Instructions

See my previous post for detailed steps on how to enable the Lightning Experience and the pilot feature that allows you to customize Record Home Pages with App Builder.

Resources and Acknowledgement

Peter Knolle’s generic FileUpload component provided a great starting point to build ProfilePicture. Peter’s article also includes a strategy for dealing with large files.

Leave your comments...

Create a Lightning Component for Drag-and-Drop Profile Pictures