5

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?

  • I just want to upload file in contentVersion using lightning component. You can also suggest some alternative. – Muzammil Bajaria Jan 23 '17 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 Jan 23 '17 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) – Muzammil Bajaria Jan 23 '17 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 Jan 23 '17 at 10:53
  • @AdityaM , we cannot use ForceTk in Lightning controller since the callout from Lightning controller is not allowed yet. – Muzammil Bajaria Jan 25 '17 at 4:29
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;

    }      
}
  • Thanx for reply Megha But I am able to upload small files. I want to upload large files. Thats why I am doing chunking. – Muzammil Bajaria Feb 7 '17 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.: [] – Megha bagri Feb 10 '17 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.

  • Hi Nikhil, I am stuck in this since last few weeks. It would be great if you can provide some sample code . – Muzammil Bajaria Feb 1 '17 at 5:31
  • Can you edit and provide code demonstration about how to use forceTK to upload file that would help. – Pranay Jaiswal Feb 2 '17 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 Nov 30 '17 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

  • were you uploading in contentVersion object? – Muzammil Bajaria Feb 1 '17 at 10:12
  • How do you fill this component.get("v.sessionId"); and this component.get("v.sfInstanceUrl"); in Lightning ? – Simon Placentino Feb 7 '17 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. – Nikhil Chowdhury Feb 8 '17 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. – Simon Placentino Feb 13 '17 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!

  • Can you share your gmail id so that we can communicate there? – Muzammil Bajaria Feb 2 '17 at 8:42
  • Yeah.. but how to link that file with a particular record? – Muzammil Bajaria Feb 2 '17 at 8:46
  • @Max, my gmail id is 'nikhilc2012@gmail.com' – Nikhil Chowdhury Feb 3 '17 at 9:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.