Found below workaround example which seems to work,
But notably,
- Owner need to be assigned to internal user
- This workaround failed for any image files (pdf, doc, excel, ppt, txt seems to work)
Component markup:
<!-- configurable attribute -->
<aura:attribute name="parentId" type="String" required="true"/>
<aura:attribute name="fieldLabel" type="String" default="Upload Attachment"/>
<aura:attribute name="instantUpload" type="boolean" default="false" />
<aura:attribute name="buttonLabel" type="String" default="Upload"/>
<aura:attribute name="refreshAfterUpload" type="boolean" default="false"/>
<!-- internal use -->
<aura:attribute name="showLoadingSpinner" type="boolean" default="false" />
<aura:attribute name="fileName" type="String" default="No File Selected.." />
<b>Custom File upload</b>
<aura:if isTrue="{!v.showLoadingSpinner}">
<div class="slds-text-body_x-small">Uploading...
<lightning:spinner aura:id="spinner" alternativeText="Loading" size="medium" title="Uploading..."/>
</div>
</aura:if>
<div class="maincontent">
<lightning:input aura:id="fileId" onchange="{!c.handleFilesChange}" type="file" name="file" label="{!v.fieldLabel}" multiple="false" />
<div class="filenameholder slds-text-body_small slds-text-color_error">
{!v.fileName}
</div>
<aura:if isTrue="{!not(v.instantUpload)}">
<div class="buttonholder">
<lightning:button variant="brand" label="{!v.buttonLabel}" title="{!v.buttonLabel}" onclick="{!c.handleManualUpload}"></lightning:button>
</div>
</aura:if>
</div>
Javascript Controller:
({
handleFilesChange: function(cmp, event, helper) {
var fileName = 'No File Selected..';
if (event.getSource().get("v.files").length > 0) {
fileName = event.getSource().get("v.files")[0]['name'];
if(cmp.get("v.instantUpload")){
//we only invoke upload the file here if instant upload is enabled
helper.uploadFile(cmp, event);
}
}
cmp.set("v.fileName", fileName);
},
handleManualUpload: function(cmp, event, helper) {
if (cmp.get("v.fileName").length > 0 && cmp.find("fileId").get("v.files") != null && cmp.find("fileId").get("v.files").length > 0) {
helper.uploadFile(cmp, event);
} else {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
"title": "Warning",
"message": "Please select a valid file.",
"type": "error",
"mode": "dismissible"
});
toastEvent.fire();
}
},
handleUploadFinished : function(component, event, helper) {
alert("uploaded ....");
}
})
Javascript Helper:
({
MAX_FILE_SIZE: 4500000, //Max file size 4.5 MB
CHUNK_SIZE: 750000, //Chunk Max size 750Kb
uploadFile: function(cmp, event) {
this.toggleSpinner(cmp, true);
// get the selected files using aura:id [return array of files]
var fileInput = cmp.find("fileId").get("v.files");
// get the first file using array index[0]
var file = fileInput[0];
var self = this;
// check the selected file size, if select file size greter then MAX_FILE_SIZE,
// then show a alert msg to user,hide the loading spinner and return from function
if (file.size > self.MAX_FILE_SIZE) {
this.toggleSpinner(cmp, false);
cmp.set("v.fileName", 'File size cannot exceed ' + self.MAX_FILE_SIZE + ' bytes.\n' + ' Selected file size: ' + file.size);
return;
}
// create a FileReader object
var objFileReader = new FileReader();
// set onload function of FileReader object
objFileReader.onload = $A.getCallback(function() {
var fileContents = objFileReader.result;
var base64 = 'base64,';
var dataStart = fileContents.indexOf(base64) + base64.length;
fileContents = fileContents.substring(dataStart);
// call the uploadProcess method
self.uploadProcess(cmp, file, fileContents);
});
objFileReader.readAsDataURL(file);
},
uploadProcess: function(cmp, file, fileContents) {
// set a default size or startpostiton as 0
var startPosition = 0;
// calculate the end size or endPostion using Math.min() function which is return the min. value
var endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
// start with the initial chunk, and set the attachId(last parameter)is null in begin
this.uploadInChunk(cmp, file, fileContents, startPosition, endPosition, '');
},
uploadInChunk: function(cmp, file, fileContents, startPosition, endPosition, attachId) {
// call the apex method 'saveChunk'
var getchunk = fileContents.substring(startPosition, endPosition);
var action = cmp.get("c.saveChunk");
action.setParams({
parentId: this.getParentId(cmp),
fileName: file.name,
base64Data: encodeURIComponent(getchunk),
contentType: file.type,
fileId: attachId
});
action.setCallback(this, function(response) {
// store the response / Attachment Id
attachId = response.getReturnValue();
var state = response.getState();
console.log('state: '+state +' attachId '+attachId);
if (state === "SUCCESS") {
// update the start position with end postion
startPosition = endPosition;
endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
// check if the start postion is still less then end postion
// then call again 'uploadInChunk' method ,
// else, diaply alert msg and hide the loading spinner
if (startPosition < endPosition) {
this.uploadInChunk(cmp, file, fileContents, startPosition, endPosition, attachId);
} else {
cmp.find("fileId").set("v.files",[]);
cmp.set("v.fileName", cmp.get("v.fileName") + ' is uploaded.' );
var toastEvent = $A.get("e.force:showToast");
if(toastEvent){
toastEvent.setParams({
"title": "Success!",
"message": "File " + file.name + " was uploaded successfully.",
"type": "success",
"mode": "dismissible"
});
toastEvent.fire();
} else {
alert("File " + file.name + " was uploaded successfully.");
}
//refresh the page after uploading
if(cmp.get("v.refreshAfterUpload")){
var refreshEvent = $A.get('e.force:refreshView');
if(refreshEvent){
refreshEvent.fire();
}
}
}
} else if (state === "INCOMPLETE") {
var toastEvent = $A.get("e.force:showToast");
if(toastEvent){
toastEvent.setParams({
"title": "Fail!",
"message": "File " + file.name + " was not uploaded due to " + response.getReturnValue(),
"type": "error",
"mode": "dismissible"
});
toastEvent.fire();
} else {
alert("File " + file.name + " was not uploaded due to " + response.getReturnValue());
}
} else if (state === "ERROR") {
var error = "Unknwown error.";
var errors = response.getError();
console.log('errors '+errors);
if (errors) {
if (errors[0] && errors[0].message) {
error = errors[0].message;
}
}
var toastEvent = $A.get("e.force:showToast");
if(toastEvent){
toastEvent.setParams({
"title": "Fail!",
"message": "File " + file.name + " was not uploaded due to " + error,
"type": "error",
"mode": "dismissible",
mode: 'sticky'
});
toastEvent.fire();
} else {
alert("File " + file.name + " was not uploaded due to " + error);
}
}
this.toggleSpinner(cmp, false);
});
$A.enqueueAction(action);
},
toggleSpinner: function(cmp, isLoading){
if(isLoading){
cmp.set("v.showLoadingSpinner", true);
} else {
cmp.set("v.showLoadingSpinner", false);
}
},
getParentId: function(cmp){
var parentIdParam = cmp.get("v.parentId");
if(parentIdParam === "{record.Id}"){
return cmp.get("v.recordId");
} else {
return cmp.get("v.parentId");
}
}
})
Apex Controller:
public without sharing class FileUploader_LCTRL {
@AuraEnabled
public static Id saveChunk(Id parentId, String fileName, String base64Data, String contentType, String fileId) {
// check if fileId id ''(Always blank in first chunk), then call the saveTheFile method,
// which is save the check data and return the attachemnt Id after insert,
// next time (in else) we are call the appentTOFile() method
// for update the attachment with reamins chunks
System.debug('fileId--------------: '+fileId);
if (fileId == '') {
fileId = saveTheFile(parentId, fileName, base64Data, contentType);
} else {
appendToFile(fileId, base64Data); // small files used to test scenario
}
System.debug('file id-------------------- '+fileId);
return fileId;//Id.valueOf(fileId);
}
public static String saveTheFile(Id parentId, String fileName, String base64Data, String contentType) {
base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
/*Attachment newAtt = new Attachment();
newAtt.parentId = parentId;
newAtt.Body = EncodingUtil.base64Decode(base64Data);
newAtt.Name = fileName;
newAtt.ContentType = contentType;
insert newAtt;
return newAtt.Id;*/
try{
ContentVersion cv =new ContentVersion(Title = fileName);
cv.VersionData = EncodingUtil.base64Decode(base64Data);
cv.PathOnClient='/' + fileName ;
cv.OwnerId = '0057F0000010x2R'; // Admin user
cv.FirstPublishLocationId = parentId; // cv.OwnerId;
System.debug('Before insert cv ');
insert cv;
System.debug('after insert cv');
/*cv = [select ContentDocumentId from ContentVersion where id=:cv.Id];
ContentDocumentLink cDL = new ContentDocumentLink(ContentDocumentId = cv.ContentDocumentId, LinkedEntityId = parentId, ShareType = 'V'); //ShareType = 'V'
System.debug('Before insert cdl');
insert cDL;
System.debug('After insert cdl');
return cDL.Id;*/
System.debug('id------------------------------------------- '+cv.Id);
return String.valueOf(cv.Id);
}
catch(Exception ex){
System.debug('Error: '+ex.getLineNumber()+' - '+ex.getMessage());
System.debug('Stack: '+ex.getStackTraceString());
return null;
}
}
private static void appendToFile(Id fileId, String base64Data) {
System.debug(' Update: ' + '----------------Appending data to fileid: '+fileId);
/*base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
Attachment att = [select Id, Body from Attachment where Id =: fileId];
String existingBody = EncodingUtil.base64Encode(att.Body);
att.Body = EncodingUtil.base64Decode(existingBody + base64Data);
update att;*/
ContentVersion cvUploaded = [select VersionData from ContentVersion where Id = :fileId];
String existingBody = EncodingUtil.base64Encode(cvUploaded.VersionData);
cvUploaded.VersionData = EncodingUtil.base64Decode(existingBody + base64Data);
update cvUploaded;
}
}
Please leave any comments, better workarounds are welcome and much appreciated!
Cheers!
Using Apex Metadata API, one can create two metadata types: page layouts and the records of custom metadata types. You can create Custom Metadata Type records NOT the Custom Metadata Type Metadata.
Firstly, one has to go to Salesforce Setup to create the Custom Metadata Type with all its fields. Then using Apex MetaData API one can create the records of newly created Metadata Type. I created a Custom Metadata Type named Profile_Setting__mdt in my org before calling the Apex Metadata API.
To verify whether the Custom Metadata Type Records are created or not, Please go to Setup->Develop-> Custom Metadata Type. Then click on the Manage Records to see newly created records.
The Trailhead module https://trailhead.salesforce.com/content/learn/modules/apex_metadata_api explains the concept very clearly.
The problem with the original code provided by the user is not in the modular pattern defined by the Salesforce. The below code is easy to understand and modular. The user was not getting any output because he did not create the **Custom Metadata Type declaratively using Salesforce Setup
Here is the working pattern based on the Salesforce Trailhead module on Apex Metadata Type.
Class Name - createUpdateMetadata
public class createUpdateMetadata{
public Metadata.CustomMetadata createCustomMetadata(){
Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata();
customMetadata.fullName = 'Profile_Setting__mdt.TEST_SARA';
customMetadata.label = 'Master Label' ;
Metadata.CustomMetadataValue customField1 = new Metadata.CustomMetadataValue();
customField1.field = 'Is_Admin_c__c';
customField1.value = true;
customMetadata.values.add(customField1);
Metadata.CustomMetadataValue customField2 = new Metadata.CustomMetadataValue();
customField2.field = 'Profile_Full_Name_c__c' ;
customField2.value = 'Admin';
customMetadata.values.add(customField2);
Metadata.CustomMetadataValue customField3 = new Metadata.CustomMetadataValue();
customField3.field = 'Profile_Name_c__c' ;
customField3.value = 'Admin';
customMetadata.values.add(customField3);
Metadata.CustomMetadataValue customField4 = new Metadata.CustomMetadataValue();
customField4.field = 'Updated_c__c' ;
customField4.value = false;
customMetadata.values.add(customField4);
return customMetadata;
}
}
Class Name-DeployCustomMetadata
public class DeployCustomMetadata {
// Create metadata container
public Metadata.DeployContainer constructDeploymentRequest() {
Metadata.DeployContainer container = new Metadata.DeployContainer();
// Add components to container
Metadata.CustomMetadata cust = new createUpdateMetadata().createCustomMetadata();
container.addMetadata(cust);
return container;
}
// Deploy metadata
public void deploy(Metadata.DeployContainer container) {
// Create callback.
PostInstallCallback callback = new PostInstallCallback();
// Deploy the container with the new components.
Id asyncResultId = Metadata.Operations.enqueueDeployment(container, callback);
}
}
Execute Anonymous Code
DeployCustomMetadata cc = new DeployCustomMetadata();
Metadata.DeployContainer container =cc.constructDeploymentRequest();
cc.deploy(container);
Best Answer
There are a few blogs written about issues like this (here is a good example) where as @dzh correctly points out, integers cause issues.
Try recasting the variable before using it, so effectively add the line
at 367 and you should be good to go.