7

I am developing a lightning component to upload files in the contentVersion object. I used this site as a reference:

http://peterknolle.com/file-upload-lightning-component/

I was able to successfully upload files up to 4 MB without chunking but I am facing an issue with large files(Size > 4MB).

Whenever I upload the file , it gives me the following error :

System.StringException: Unrecognized base64 character: %

I am attaching the code for my helper class and apex controller.

Helper Class :

({
    MAX_FILE_SIZE: 4 500 000, /* 6 000 000 * 3/4 to account for base64 */
    CHUNK_SIZE: 950 000, /* Use a multiple of 4 */

    readFile: function(component, helper, file) {
        if (!file) return;
        var reader = new FileReader();
        self = this;
        reader.onload = function() {
            var dataURL = reader.result;
            component.set("v.pictureSrc", "https://s3-us-west-1.amazonaws.com/sfdc-demo/image-placeholder.png");
            self.upload(component, file, dataURL);
        };
        reader.readAsDataURL(file);
    },

    upload: function(component, file, dataURL) {
        console.log('uploading file ...');
         var fromPos = 0;
        var toPos = Math.min(dataURL.length, fromPos + this.CHUNK_SIZE);
        console.log('toPos  '+toPos);
        console.log(' fromPos '+fromPos);
        this.uploadChunk(component, file, dataURL, fromPos, toPos,'');

    },
        uploadChunk : function(component, file, dataURL, fromPos, toPos,contentDocumentId){
            console.log('uploading chunk ');
             var action = component.get("c.saveTheChunkChatterFile");
            var chunk = dataURL.substring(fromPos, toPos);
            console.log(chunk);
            action.setParams({
            parentId: component.get("v.recordId"),
            fileName: file.name,
            base64Data: encodeURIComponent(chunk), 
            contentType: file.type,
            contentDocumentId :contentDocumentId
            });
            var self = this;
             action.setCallback(this, function(a) {
            contentDocumentId = a.getReturnValue();
            console.log('return value '+contentDocumentId);
            fromPos = toPos;
            toPos = Math.min(dataURL.length, fromPos + self.CHUNK_SIZE);    
            if (fromPos < toPos) {
                self.uploadChunk(component, file, dataURL, fromPos, toPos, contentDocumentId);  
            }else{
                component.set("v.message", "File Uploaded");
            }
        });
               component.set("v.message", "Uploading...");

            $A.enqueueAction(action); 
       }
})

Apex Controller :

@AuraEnabled
public static Id saveChatterFiles(Id parentId, String fileName, String base64Data, String contentType)  { 
    system.debug('Saving chatter files '+fileName + ' '+ contentType);
    ContentVersion testContentInsert =new ContentVersion(); 
     testContentInsert.Title =fileName; 
    testContentInsert.VersionData=EncodingUtil.base64Decode(base64Data);
    testContentInsert.PathOnClient='/' + fileName ;
     insert testContentInsert; 
    system.debug('testContentInsert.id '+ testContentInsert.id);
    testContentInsert = [select id, ContentDocumentId from ContentVersion WHERE Id =: testContentInsert.Id];
    ContentDocumentLink cl = new ContentDocumentLink();
    cl.ContentDocumentId = testContentInsert.ContentDocumentId;
    cl.LinkedEntityId = parentId; 
    cl.ShareType = 'V';
     cl.Visibility = 'AllUsers';
    insert cl;
    system.debug('testContentInsert.id');
    return testContentInsert.id;

}


@AuraEnabled                            
public static Id saveTheChunkChatterFile(id parentId,String fileName, String base64Data, String contentType, String contentDocumentId){
        system.debug('saving chatter file');
    if (contentDocumentId == '' || contentDocumentId==null ) {
        system.debug('null id');
        contentDocumentId = saveChatterFiles(parentId, fileName, base64Data, contentType);
    } else {
        system.debug('not null id');
        system.debug('id '+contentDocumentId);
        appendToFileChatter(contentDocumentId, base64Data);
    }

    return Id.valueOf(contentDocumentId);
}

@AuraEnabled
public static void appendToFileChatter(Id contentDocumentId, String base64Data) {
 base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
    system.debug('appending');
    ContentVersion a = [
        SELECT Id, VersionData,ContentDocumentId
        FROM ContentVersion
        WHERE Id = :contentDocumentId
    ];

    String existingBody = EncodingUtil.base64Encode(a.VersionData);
    a.VersionData = EncodingUtil.base64Decode(existingBody + base64Data); 

    update a;
}

My aim is to upload large files(size > 10MB ) using a lightning component. Are there any alternative solutions?

9
  • I just want to upload file in contentVersion using lightning component. You can also suggest some alternative. Commented Jan 23, 2017 at 6:35
  • Hi Max, you can also look into other alternatives like ForceTK Javascript library to upload files. Here's the link : github.com/developerforce/Force.com-JavaScript-REST-Toolkit
    – Aditya M
    Commented Jan 23, 2017 at 10:25
  • But can I upload files more that 4.3 MB with that? I can upload the files upto 4 MB but now i want to upload larger files(size > 5MB) Commented Jan 23, 2017 at 10:38
  • Definitely Max, you can upload files upto 500 MB at a time. In the github link which I shared search for : 'Inserting or updating blob data' . A sample code is available for uploading
    – Aditya M
    Commented Jan 23, 2017 at 10:53
  • @AdityaM , we cannot use ForceTk in Lightning controller since the callout from Lightning controller is not allowed yet. Commented Jan 25, 2017 at 4:29

7 Answers 7

2

I suspect we can use the new fileUpload in Lightning.

https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/aura_compref_lightning_fileUpload.htm

1

This error could be resolved I am attaching helper and apex controller code. Though I have just tried code for uploading small file only. And I am successfully able to upload small files.

Helper:

({
    MAX_FILE_SIZE: 750000, 

    save : function(component) {
        var fileInput = component.find("file").getElement();
        var file = fileInput.files[0];

        if (file.size > this.MAX_FILE_SIZE) {
            alert('File size cannot exceed ' + this.MAX_FILE_SIZE + ' bytes.\n' +
                  'Selected file size: ' + file.size);
            return;
        }

        var fr = new FileReader();

        var self = this;
        fr.onload = function() {
            var fileContents = fr.result;
            var base64Mark = 'base64,';
            var dataStart = fileContents.indexOf(base64Mark) + base64Mark.length;

            fileContents = fileContents.substring(dataStart);

            self.upload(component, file, fileContents);
        };

        fr.readAsDataURL(file);
    },

    upload: function(component, file, fileContents) {
        var action = component.get("c.saveTheFiles"); 

        action.setParams({
            parentId: component.get("v.parentId"),
            fileName: file.name,
            base64Data: fileContents,
            contentType: file.type
        });

        action.setCallback(this, function(a) {
            attachId = a.getReturnValue();
            console.log(attachId);
        });

        $A.run(function() {
            $A.enqueueAction(action); 
        });

    } })

Apex Class:

public class FileController {

    @AuraEnabled
    public static Id saveTheFiles(Id parentId, String fileName, String base64Data, String contentType)  
    { 
    system.debug('Saving chatter files '+fileName + ' '+ contentType);
    ContentVersion testContentInsert =new ContentVersion(); 
    testContentInsert.Title =fileName; 
    testContentInsert.VersionData=EncodingUtil.base64Decode(base64Data);
    testContentInsert.PathOnClient='/' + fileName ;
    insert testContentInsert; 

    system.debug('testContentInsert.id '+ testContentInsert.id);
    testContentInsert = [select id, ContentDocumentId from ContentVersion WHERE Id =: testContentInsert.Id];
    ContentDocumentLink cl = new ContentDocumentLink();
    cl.ContentDocumentId = testContentInsert.ContentDocumentId;
    cl.LinkedEntityId = parentId; 
    cl.ShareType = 'V';
    cl.Visibility = 'AllUsers';
    insert cl;

    system.debug('testContentInsert.id');
    return testContentInsert.id;

    }      
}
2
  • Thanx for reply Megha But I am able to upload small files. I want to upload large files. Thats why I am doing chunking. Commented Feb 7, 2017 at 12:46
  • 2
    Hi, actually i was trying the code to upload 4mb file but I was unable to append the file using ContentVersion and gave error- Update failed. First exception on row 0 with id 068p0000000jZcdAAE; first error: INVALID_STATUS, You can't set the VersionData when IsMajorVersion is true.: [] Commented Feb 10, 2017 at 9:26
1

Just use Blob.valueOf() instead of EncodingUtil.base64Encode() in your APEX controller when you want to convert String into Blob because Base64 String can only be composed of characters 'A'..'Z', 'a'..'z', '0'..'9', '+', '/'.

0

We can use ForceTk explicitly in lightning component to upload documents/images greater than 10 MB. I have used it successfully in the past.

3
  • Hi Nikhil, I am stuck in this since last few weeks. It would be great if you can provide some sample code . Commented Feb 1, 2017 at 5:31
  • Can you edit and provide code demonstration about how to use forceTK to upload file that would help. Commented Feb 2, 2017 at 8:37
  • Do you folks have any update on this? I am looking for solutions for uploading AND passing the documents to external systems using Lightning Components and REST. Not able to figure out whether its even possible.
    – goabhigo
    Commented Nov 30, 2017 at 7:51
0

I am sharing the piece of code here.

// adding attachment
uploadImage : function(component, resId, files) {

    var sessionId = component.get("v.sessionId");
    var pathToDetail = component.get("v.pathToDetail");
    var sfInstanceUrl = component.get("v.sfInstanceUrl");
    var spinnerSubmit = component.find("spinnerSubmit");
    var client = new forcetk.Client();
    client.setSessionToken(sessionId ,'v36.0',sfInstanceUrl);
    client.proxyUrl = null;
    client.instanceUrl = sfInstanceUrl;
    console.log('----45----',file);

    var file = files[0];

    var self = this;

    client.createBlob('Attachment', {
                                    'ParentId': resId,
                                    'Name': file.name,
                                    'ContentType': file.type,
                                    }, 

           file.name, 'Body', file , function(response)
           {
               if(response.id !== null)
               {
                   $A.util.addClass(spinnerSubmit,"hide");
                   self.goToURL(pathToDetail + resId);
               }
               else
               {
                   $A.util.addClass(spinnerSubmit,"hide");
                   component.set("v.strAttachmentError",'Error : '+response.errors[0].message);
               }
           }, 
           function(request, status, response)
           {
               console.log('----46----',request);
               $A.util.addClass(spinnerSubmit,"hide");
               var res = JSON.parse(response);
               component.set("v.strAttachmentError",'Error : ' +res[0].message);

           });
},

Once this method is passed into the helper section in your Save method call the method as:

saveProjectAction : function(component, projectObj) {

    var sitePath = component.get("V.sitePath");
    var sitePrefix = component.get("v.sitePrefix");
    var fileInput = component.find("imgFileSelected").getElement();

    var pathToDetail = component.get("v.pathToDetail");

    // Apex method called
    var action = component.get("c.saveProjects");

    action.setParams({
        "projectObj": projectObj

    console.log("projectObj: ", projectObj);
    var self = this;

    action.setCallback(this, function(response) {
        var state = response.getState();
        console.log("state: ", state);
        if (component.isValid() && state === "SUCCESS") 
        {
            var resId = response.getReturnValue().Id;
            console.log('&&&&&',resId);
            if(fileInput.files.length > 0 && resId != null && resId.length > 0 )
            {
                self.uploadImage(component,resId,fileInput.files);
            }
            if(resId != null && resId.length > 0 )
            {
                self.goToURL(pathToDetail + resId);
            }

        }
        if (state === "ERROR") 
        {
            var errors = response.getError();
            console.log("errors: ", errors);
        }
    });
    $A.enqueueAction(action);

This is something I recently tried and it worked successfully for me.

Let me know if you have any questions.

Thanks

4
  • were you uploading in contentVersion object? Commented Feb 1, 2017 at 10:12
  • How do you fill this component.get("v.sessionId"); and this component.get("v.sfInstanceUrl"); in Lightning ?
    – Simonp
    Commented Feb 7, 2017 at 8:13
  • @SimonPlacentino, you can fetch sessionID using : sessionId = String.isEmpty(UserInfo.getSessionId()) ? '' UserInfo.getSessionId(); and for sfInstanceURL you can do as: var url = window.location.href; var splitUrl = url.split("/s"); component.set("v.sfInstanceUrl",splitUrl[0]); and use this attribute while uploading files. Commented Feb 8, 2017 at 9:18
  • I can use forcetk client to do query() because of CSP because it violates the following Content Security Policy directive: "connect-src 'self'. Does anyone else have the issue ? The remote is already added.
    – Simonp
    Commented Feb 13, 2017 at 8:20
0

@Max, I did that on Attachment but you have to just pass your parameters to here:

client.createBlob('Attachment', {
                                'ParentId': resId,
                                'Name': file.name,
                                'ContentType': file.type,
                                }, 

just use ContentVersion object instead of attachment.

0

Here is code for uploading ContentVersion:

var originType = 'C';

    client.createBlob('ContentVersion', {
        "Origin" : originType, 
        "PathOnClient" : file.name
    }, 
                      file.name, 'VersionData', file , function(response)
                      {
                          if(response.id == null)
                          {
                              console.log('response.id--------->>', response.id);
                              console.log('Error : ',response.errors[0].message);
                          }
                      }, 
                      function(request, status, response)
                      {
                          console.log('----46----',request);
                          var res = JSON.parse(response);
                      });
},

It works well for me. You can try and let me know.

Thanks!

3

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .