top of page

Upload file in Lightning Component without "parentId" before record Creation

  • Writer: Oliver Jones
    Oliver Jones
  • Aug 17, 2020
  • 3 min read

Updated: Oct 15, 2021

In Salesforce lightning, uploading files will require parentId in order to store the uploaded file directly under the parent record, but what if we face a situation when a user submits a form for the first time that requires a file to upload in the first place?



There is a workaround for this, you will need to use <lightning:input> tag replacing standard <lightning:fileupload> tag. This tag does not require a parentId to attach to, so we can just add the file to any parent record whenever and wherever we need to. But this solution comes up with a cost, we will need to write up the file reader code manually in our javascript controllers and helpers. Here is the working code,


Component code:


<div class="slds-modal__header">
    <div class="slds-text-heading_medium slds-hyphenate">File Upload</div>
</div>
<div class="slds-modal__content slds-p-around--medium">
    <lightning:layoutItem padding="around-small">
        <lightning:select name="Document Type" aura:id="inputDocType" label="Document Type" required="true" >
            <aura:iteration items="{! v.uploadSelector}" var="documentType">
                <option value="{! documentType.value}">{! documentType.text}</option>
            </aura:iteration>
        </lightning:select>
        <lightning:input aura:id="inputUploadFile" onchange="{! c.handleFileUpload}" type="file" label="" name="file" multiple="false" accept=".doc, .docx, .pdf, .png, .jpeg,.jpg,.JPG,.JPEG,.PNG,.DOCX,.DOC,.PDF"/>
        <div class="{!'slds-text-body_small slds-text-color_'+ (v.isValidFile ? 'weak' : 'error')}">{! v.fileName} </div>
        <div class="slds-text-body_small slds-text-color_error">{! v.showFileUploadError}</div>
    </lightning:layoutItem>
</div>
<div class="slds-modal__footer">
    <lightning:button variant="brand" label="Upload" onclick="{! c.saveFile }" disabled="{! !v.isValidFile}" />
    <lightning:button label="Back" onclick="{! c.cancelUpload }" />
</div>

Controller code:


//This method is called whenever a file upload occurs
handleFileUpload : function (component, event, helper) {
    var fileName = 'No File Selected..';
    var acceptFormat = event.getSource().get("v.accept").split(",");
    var filetype;
    var extension;
    if(event.getSource().get("v.files").length > 0) {
        fileName = event.getSource().get("v.files")[0]["name"];
        filetype = fileName.split(".");
        extension = "."+filetype[filetype.length-1];
        var match = false;
        for(var i in acceptFormat) {
            console.log('filetype: '+acceptFormat[i] +' == '+ extension);
            if(acceptFormat[i].trim() == extension.trim()) {
                console.log('Match');
                match = true;
                break;
            }
            if(!match) {
                console.log("You have uploaded an invalid format file");
                fileName = "You have uploaded an invalid format file. Please upload file in the format of .doc, .docx, .pdf, .png, .jpeg'";
                component.set("v.isValidFile", false);
            }
            else {
                component.set("v.fileName", fileName);
                component.set("v.isValidFile", true);
            }
        }
    }
}

//This method is called whenever user clicks on Upload (the final submit)
saveFile : function(component, event, helper) {
    var valid = true;
    if(component.find("docType").get("v.value") == null) {
        valid = false;
        component.find("inputDocType").set("v.errors", [{message:"Document Type is required"}]);
    }
    if(valid) {
        if(component.find("inputUploadFile").get("v.files") != null && component.find("inputUploadFile").get("v.files").length > 0) {
            helper.submitRequest(component);
        }
    }
}

Helper code:


//Call Apex method to insert a new Parent record
submitRequest : function(component) {
    var action = component.get("c.submitNewRequest");
    action.setParams({
        myRec : component.get("v.contactRec"),
    });
    action.setCallback(this, function(response) {
        if(response.getState() == "SUCCESS") {
            var parentId = response.getReturnValue();
            this.uploadMyFile(component,     
            component.find("inputUploadFile").get("v.files"), parentId);
        }
    });
    $A.enqueueAction(action);
}

MAX_FILE_SIZE: 4500000, //Max file size 4.5 MB (Customizable)
CHUNK_SIZE: 750000,     //Chunk Max size 750Kb

uploadHelper: function(component, fileInput, parentId) {
    var file = fileInput[0];
    if(file.size > self.MAX_FILE_SIZE) {
        component.set("v.showFileUploadError", 'Alert : File size cannot exceed ' + this.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(component, file, fileContents, parentId);     
    });     
    objFileReader.readAsDataURL(file);
},

uploadProcess: function(component, file, fileContents, parentId) {
    //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(component, file, fileContents, startPosition, endPosition, '', parentId);
}

//Method to upload each chunk
uploadInChunk: function(component, file, fileContents, startPosition, endPosition, attachId, parentId) {
    //call the apex method 'saveChunk'
    var getchunk = fileContents.substring(startPosition, endPosition);             
    //Call Apex method to insert the attachment
    var action = component.get("c.saveChunk");
    action.setParams({
        parentId: parentId,
        fileName: file.name,
        base64Data: encodeURIComponent(getchunk),
        contentType: file.type,
        fileId: attachId,
    });
    action.setCallback(this, function(response) {
        //store the response / Attachment Id
        attachId = response.getReturnValue();
        if(response.getState() === "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(component, file, fileContents, startPosition, endPosition, attachId);
            }
            else {
                console.log('File uploaded successfully.');
            }
        }
    });
    $A.enqueueAction(action);
}

Apex Controller code:


@AuraEnabled
public static Id submitNewRequest(Contact myRec) {
     insert myRec;
     return myRec.Id;
}
@AuraEnabled
public static Id saveChunk(Id parentId, String fileName, String base64Data, String contentType, String fileId) {
     if(fileId == '') {
          //Customizable can insert Content Document in place of attachment
          base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
          Attachment attachRec = new Attachment();          
          attachRec.parentId = parentId;          
          attachRec.Body = EncodingUtil.base64Decode(base64Data);
          attachRec.Name = fileName;
          attachRec.ContentType = contentType;
          insert attachRec;
          fileId = attachRec.Id;
     }
     else {
          base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
          Attachment attachToUpdate = [
               SELECT Id, Body
               FROM Attachment
               WHERE Id =: fileId
          ];
          String existingBody = EncodingUtil.base64Encode(attachToUpdate.Body);
          attachToUpdate.Body = EncodingUtil.base64Decode(existingBody + base64Data);
          update attachToUpdate;
     }
     return Id.valueOf(fileId);
}


Commentaires


bottom of page