How To Upload A File To Google Drive Using LWC

How To Upload A File To Google Drive Using LWC

In the realm of digital innovation, Salesforce Lightning Web Components (LWC) offer a plethora of opportunities for creating versatile, customized solutions. Among these, a standout is the ability to construct an advanced Google Drive File Uploader completely using LWC without Apex . 

In this blog, we’ll explore the nuts and bolts of this uploader, which is not only highly functional but also customizable to meet your specific requirements.

Understanding the File Uploader Component

The file uploader component is positioned within a `lightning-card`, a container component that groups related pieces of content. The card is equipped with a file input component and a submit button.

File Input

Users select their desired upload file via an input of type “file”. This input only accepts ‘zip’ and ‘pdf’ files as defined by the `SUPPORTED_FILES` constant. 

File Validation

Upon file selection, the component triggers the `validateDocument` method to verify the file’s compliance with the required conditions. Specifically, the file size must not exceed 10 MB, and the file type should be either ‘zip’ or ‘pdf’. These conditions can be effortlessly customized to suit your particular needs. If a file fails to meet these conditions, the component displays an error message, and the submit button is disabled.

File Submission

Once a file is successfully selected, the ‘Submit’ button becomes available. Clicking this button initiates the `processSelectedFile` method, which prepares the file for upload to Google Drive.

Uploading Files to Google Drive

To upload the selected files to Google Drive, they must first be converted into a suitable format. This involves reading the file data as a Data URL and extracting the base64 encoded string of the file’s content. 

After all selected files are converted and ready, they are transferred to Google Drive via the `sendFileToGoogleDrive` method.

 Retrieving the Google Access Token

Before the files can be sent to Google Drive, a Google Access Token must be obtained. This token authorizes the application to access Google Drive. The `getGoogleAccessToken` method manages this process, sending a POST request to a pre-configured endpoint to fetch the access token.

Dispatching Files to Google Drive

Upon securing the access token, the `sendFileToGoogleDrive` method dispatches a multipart POST request to the Google Drive API. This request is authorized using the previously obtained access token and attaches the file data along with its metadata. 

Prerequisites

It’s critical to mention that for this uploader to function correctly, the domain `https://www.googleapis.com` needs to be added to Salesforce’s Content Security Policy (CSP) Trusted Sites list. Adding this domain to the list is vital because it allows Salesforce to make secure HTTP requests to Google’s APIs.

Additionally, remember to store the required values in custom labels within your Salesforce org. These labels can include Google Drive API credentials such as ClientId, ClientSecret, and GoogleDriveRefreshToken, as well as other necessary parameters such as prejoining_folder_id, GoogleDriveApiResponse, and GoogleAccessTokenPrejoining. You may rename these labels as per your preference, but be sure to adjust their references in the code accordingly.

Lightning Web Component:

Html:
    <template>
    <lightning-card title="File Upload" icon-name="utility:upload">
      <div class="slds-m-around_medium">
        <!-- File Upload Component -->
        <div class="slds-m-top_large">
          <label class="custom-file-upload">
            <input class="input input-file document" required type="file" onchange={fileHandler} accept={acceptedFormats} />
          </label>
        </div>
        <!-- Display uploaded file name -->
        <div class="slds-text-align_left slds-m-top_small">
          <p style="font-size: 0.8rem; font-weight:normal; font-style:italic;color: navy;">{fileName}</p>
        </div>
        <!-- Error message display section -->
        <template if:true={errorMessage}>
          <div class="slds-text-align_left slds-m-top_small">
            <p style="font-size: 0.8rem; font-weight:normal; font-style:italic;color: red;">{errorMessage}</p>
          </div>
        </template>
        <!-- File upload requirements section -->
        <div class="slds-m-top_small">
          <small>Upload only zip file or pdf (less than 5MB) containing all the above documents</small>
        </div>
        <!-- Submit Button -->
        <div class="slds-m-top_medium">
          <lightning-button label="Submit" variant="brand" onclick={processSelectedFile} disabled={isSubmitDisabled}></lightning-button>
        </div>
      </div>
    </lightning-card>
  </template>

JS:

// Importing necessary modules
import { LightningElement, api, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import CLIENT_ID from '@salesforce/label/c.ClientId';
import CLIENT_SECRET from '@salesforce/label/c.Client_Srecret';
import GOOGLE_DRIVE_REFRESH_TOKEN from '@salesforce/label/c.GoogleDriveRefreshToken';
import DRIVE_FOLDER_ID from '@salesforce/label/c.drive_folder_id';
import GoogleDriveApiResponse from '@salesforce/label/c.GoogleDriveApiResponse';
import GoogleAccessToken from '@salesforce/label/c.GoogleAccessToken';

const SUPPORTED_FILES = ['zip', 'pdf']; // Supported file formats

export default class GoogleDriveFileUploader extends LightningElement {
  @track fileName = "No File Selected";
  @track errorMessage = "";
  @api recordId;
  @track isSubmitDisabled = true;
  @track selectedFiles = []; 

  // Method that returns accepted formats
  get acceptedFormats() {
    return SUPPORTED_FILES.map(format => `.${format.toLowerCase()}`);
  }

  // File handler method
  fileHandler(event) {
    console.log("File handler method triggered");
    const files = event.target.files;
    if (files.length > 0) {
      this.selectedFiles = [...files]; 
      this.validateDocument(files[0]) && this.buildFile();
    }
  }

  // Method for document validation
  validateDocument(file) {
    console.log("Validating document");
    let documentField = this.template.querySelector(".document");
    let fileSize = file.size; // size in bytes
    let maxSize = 10 * 1024 * 1024; // 10 MB in bytes
    let fileExtension = file.name.split(".").pop();

    // Check file size
    if (fileSize > maxSize) {
      documentField.setCustomValidity("File size exceeds 10 MB");
      this.isSubmitDisabled = true;
    } 
    // Check file type
    else if (!SUPPORTED_FILES.includes(fileExtension.toLowerCase())) {
      documentField.setCustomValidity("File format not supported");
      this.isSubmitDisabled = true;
    } 
    // Valid file
    else {
      documentField.setCustomValidity("");
      this.fileName = file.name;
      this.isSubmitDisabled = false;
      return true;
    }

    documentField.reportValidity();
    this.fileName = '';
    return false;
  }

  // Method for building the file
  buildFile() {
    console.log("Building file");
    let processedFiles = []; 
    let filesProcessed = 0;

    for (let i = 0; i < this.selectedFiles.length; i++) {
      let fileReader = new FileReader();
      fileReader.readAsDataURL(this.selectedFiles[i]);

      fileReader.onload = () => {
        let base64 = 'base64,';
        let content = fileReader.result.indexOf(base64) + base64.length;
        let fileContents = fileReader.result.substring(content);

        processedFiles.push({
          Title: this.selectedFiles[i].name,
          VersionData: fileContents
        });

        if (++filesProcessed === this.selectedFiles.length) {
          this.selectedFiles = processedFiles;
          console.log("File successfully built");
          
        }
      };
    }
  }

  processSelectedFile() {
    console.log("File method triggered");
    this.selectedFiles.forEach((fileObject) => {
      // Convert files from base64 to byte array and set file properties
      const byteArray = this.base64ToUint8Array(fileObject.VersionData);
      const fileExtension = fileObject.Title.split('.').pop().toLowerCase();
      const mimeType = fileExtension === 'pdf' ? 'application/pdf' : 'application/zip';
      this.file = new File([byteArray], fileObject.Title, { type: mimeType });
      console.log("Sending file to Google Drive");
      this.sendFileToGoogleDrive(); // Upload file     });
  }

  // Converts a base64 string into a Uint8Array
  base64ToUint8Array(base64) {
    const binaryString = atob(base64);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes;
  }

  // Sends a file to Google Drive
  async sendFileToGoogleDrive() {
    console.log("File reader is loading file");
    const fileReader = new FileReader();
    fileReader.onload = async () => {
      const base64FileData = fileReader.result.split(',')[1];
      const fileTitle = this.file.name;
      const fileContentType = this.file.type;

      // Upload file to Google Drive
      console.log("Uploading file to Google Drive");
      const fileId = await this.uploadFileToGoogleDrive(fileTitle, base64FileData, fileContentType);
      
      // Dispatch a success event
      console.log("Dispatching success event");
      this.dispatchEvent(
        new ShowToastEvent({
          title: 'Success',
          message: 'File uploaded Successfully',
          variant: 'success'
        })
      );
    };
    fileReader.readAsDataURL(this.file);
  }

  // Upload file to Google Drive
  async uploadFileToGoogleDrive(fileTitle, base64FileData, fileContentType) {
    console.log("Retrieving Google Access Token");
    const accessToken = await this.getGoogleAccessToken();
    const folderId = DRIVE_FOLDER_ID;
    const boundary = '----------9889464542212';
    const delimiter = `rn--${boundary}rn`;
    const close_delim = `rn--${boundary}--`;

    // Set metadata for the file
    const metadata = {
      title: fileTitle,
      mimeType: fileContentType,
      parents: [{ id: folderId }],
    };

    // Prepare the multipart request body
    const multipartRequestBody =
        delimiter +
        'Content-Type: application/jsonrnrn' +
        JSON.stringify(metadata) +
        delimiter +
        'Content-Type: ' +
        fileContentType +
        'rn' +
        'Content-Transfer-Encoding: base64rn' +
        'rn' +
        base64FileData +
        close_delim;

    // Set request options
    const requestOptions = {
      method: 'POST',
      headers: {
        'Content-Type': `multipart/mixed; boundary="${boundary}"`,
        Authorization: `Bearer ${accessToken}`,
      },
      body: multipartRequestBody,
    };

    // Send the request and process the response
    console.log("Sending request to Google Drive API");
    const response = await fetch(GoogleDriveApiResponse, requestOptions);
    if (!response.ok) {
      throw new Error('Error uploading file to Google Drive');
    }
    const responseData = await response.json();
    console.log("Response data received from Google Drive API", responseData);
    return responseData.id;
  }

  // Retrieves a Google Access Token
  async getGoogleAccessToken() {
    console.log("Retrieving Google Access Token");
    const clientId = CLIENT_ID;
    const clientSecret = CLIENT_SECRET;
    const refreshToken = GOOGLE_DRIVE_REFRESH_TOKEN;

    // Prepare the body of the request
    const bodyRequest = new URLSearchParams();
    bodyRequest.append('client_id', clientId);
    bodyRequest.append('client_secret', clientSecret);
    bodyRequest.append('refresh_token', refreshToken);
    bodyRequest.append('grant_type', 'refresh_token');

    // Set request options
    const requestOptions = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: bodyRequest,
    };

    // Send the request and process the response
    console.log("Sending request to retrieve access token");
    const response = await fetch(GoogleAccessToken, requestOptions);
    if (!response.ok) {
      throw new Error('Error retrieving Google Access Token');
    }
    const responseData = await response.json();
    console.log("Access token retrieved", responseData.access_token);
    return responseData.access_token;
  }
}

CSS:

/* fileUploadToGoogleDrive.css */

.THIS .custom-file-upload {
    padding: 10px;
    background-color: #e0e0e0;
    border-radius: 5px;
    display: inline-block;
    width: 100%;
    text-align: center;
    cursor: pointer;
    color: #5a5a5a;
}

.THIS .custom-file-upload:hover {
    background-color: #c2c2c2;
}

.THIS .input-file {
    display: none;
}

.THIS .input {
    width: 100%;
    padding: 10px;
    border: 1px solid #c2c2c2;
    border-radius: 5px;
}

.THIS p {
    margin: 0;
}

XML:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>57.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Conclusion

The LWC Google Drive File Uploader component effectively enables users to select a file, validates the chosen file, and uploads it directly to Google Drive from Salesforce. This streamlined process saves time and enhances productivity by integrating two separate platforms, facilitating a seamless data exchange.

The code is scalable and maintainable, meaning supported file types, file size restrictions, or the upload logic can be easily added or modified to meet specific business needs. By adjusting the API calls and authentication methods, the uploader could be modified to work with other cloud storage services like Dropbox or OneDrive. Remember to adhere to security best practices and add the necessary domain to your trusted sites list for secure and successful file uploads.

Leave a Comment

Your email address will not be published. Required fields are marked *

Recent Posts

future-of-ai-powered-business-solutions
Dreamforce 2024: Unveiling the Future of AI-Powered Business Solutions
gitex 2024 worlds largest tech show
GITEX 2024 |  World's Largest Tech Show - ABSYZ
inclusive practices in women leadership equitable future
Inclusive Practices in Women’s Leadership: Equitable Future
salesforce dreamfest 2024 the highlights of dreamforce 2024
Dreamfest 2024: The highlights of Dreamforce 2024
DocuSign CLM the ultimate tool for managing contracts
DocuSign CLM: The Ultimate Tool for Managing Contracts
Document

How can i help you? close button

powered     by   ABSYZ
Scroll to Top