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.
https://youtu.be/YKteL4iLj-w
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.