Download SharePoint library files in Power Pages with JavaScript and Cloud flows – Improved code using jQuery & option to open PDF file in a new tab

Last week I demoed an improved solution of my previous blog post: Using JavaScript and Cloud Flows to download files from a SharePoint document library in Power Pages in the Microsoft 365 & Power Platform community (PnP) call.

The Cloud flows are the same as on the previous post, but on the JavaScript side there were a few updates:

  • Using jQuery instead of Vanilla Js
  • Added more comments to the code
  • Added option to preview PDF files on a new tab (be aware that you need to allow pop-ups in your browser for the site address for it to work)

Important note for production use: This sample was meant to be used in cases where documents are meant to be public/and as a demonstration of the integration capabilities. When implementing features for production, it is crucial to ensure the security of user data. Please be diligent in adding security checks for user authentication and authorization to prevent any unauthorized access, document downloads or data breaches.

Source code

The updated source code for the Power Pages page follows below:

<div class="row" style="min-height: auto; padding: 8px;">
  <div class="container" style="display: flex; flex-wrap: wrap;">
    <div id="divFolderBreadcrumbContainer" class="spbreadcrumb" style="margin-bottom: 16px; width: 100%;"></div>
    <div class="spfoldercontent" style="min-width: 360px; width: 100%;">
      <div id="table_container"></div>
      <div id="divSpinner" class="spinner">
         <div class="spinner-image">
        </div>
        <span id="spnSpinnerMessage"></span>
      </div>
    </div>
  </div>
</div>
<style>
  .spinner {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.8);
    justify-content: center;
    align-items: center;
    flex-direction: column;
  }

  .spbreadcrumb {
    margin-bottom: 16px;
    width: 100%;
  }

  .spfoldercontent {
    min-width: 360px;
    width: 100%;
  }

  .spfoldercontent button {
    border: none;
    background-color: transparent;
    display: flex;
    gap: 8px;
    flex-direction: row;
    align-items: center;
  }

  .spfoldercontent td:first-child {
    min-width: 300px;
  }

  .spinner-image {
    width: 75px;
    height: 75px;
    animation: spinnerwheel 1.5s linear infinite;
    border: 4px solid #c4c0c0;
    border-top: 4px solid #0580ab;
    border-radius: 50%;
  }
  #table_container,  #divFolderBreadcrumbContainer a,  #divFolderBreadcrumbContainer span{
    font-size: large;
    padding: 0.5rem;
  }
 

  @keyframes spinnerwheel {
    0% {
      transform: rotate(0deg);
    }

    100% {
      transform: rotate(360deg);
    }
  }
</style>
<script>
  var _getFileAsBase64FlowUrl = "https://<your site>.powerappsportals.com/_api/cloudflow/v1.0/trigger/<your flow id>";
  var _getFolderContentFlowUrl = "https://<your site>.powerappsportals.com/_api/cloudflow/v1.0/trigger/<your flow id>";

  $(document).ready(function () {
    LoadSharePointFolderContents();
  });

  function GenerateBreadcrumb(folderPath) {
    const folders = folderPath.split('/').filter(folder => folder.trim() !== '');

    let breadcrumbHtml = '';
    let currentFolderPath = '';
    //Iterates over the folders in the path and generates the breadcrumb
    for (let i = 0; i < folders.length; i++) {
      const folder = folders[i];
      currentFolderPath += `/${folder}`;
      if (i < folders.length - 1) {
        breadcrumbHtml += `<a href="#" onclick="LoadSharePointFolderContents('${currentFolderPath}')">${folder}</a> /`;
      } else {
        //Last item in path should not be clickable - keeping as link just to use basic styles
        breadcrumbHtml += `<a style="text-decoration: none;">${folder}</a>`;
      }
    }
    $('#divFolderBreadcrumbContainer').html(breadcrumbHtml);
  }

  function LoadSharePointFolderContents(folder) {
    ShowSpinner("Loading folder contents...");
    $("#table_container").html("");
    var payload = {};
    var data = {};
    folder = folder ?? "";
    data["Folder"] = folder;
    //If no input parameter is defined in the trigger, we need pass an empty payload in the request.
    //On this case we have data, however if the flow trigger does not have any input parameter, we should pass an empty object.
    var payload = {};
    payload.eventData = JSON.stringify(data);

    shell.ajaxSafePost({
      type: "POST",
      contentType: "application/json",
      url: _getFolderContentFlowUrl,
      processData: false,
      data: JSON.stringify(payload),
      global: false,
    })
      .done(function (response) {
        let fileList = JSON.parse(JSON.parse(response).result);
        
        //Render HTML table with the file list and clickable buttons
        GenerateSharePointTable(fileList.value);
        if (!folder && fileList.value.length > 0) {
          folder = fileList.value[0]["{FullPath}"].split("/")[0];
        }
        //Generates clickable breadcrumb based on the current SharePoint folder path
        GenerateBreadcrumb(folder);
        HideSpinner();
      })
      .fail(function () {
        HideSpinner();
        ShowError("Error loading folder content");
      })
  }

  function OpenSharePointDocument(FileId, FileName, IsPreview) {
    ShowSpinner(`${IsPreview ? "Loading" : "Downloading"} file...`);

    var data = {};
    data["FileId"] = FileId;
    //If no input parameter is defined in the trigger, we need pass an empty payload in the request.
    //On this case we have data, however if the flow trigger does not have any input parameter, we should pass an empty object.
    var payload = {};
    payload.eventData = JSON.stringify(data);
    //Call the flow to get the file as base64 and then download it
    shell.ajaxSafePost({
      type: "POST",
      contentType: "application/json",
      url: _getFileAsBase64FlowUrl,
      processData: false,
      data: JSON.stringify(payload),
      global: false,
    })
      .done(function (response) {
        let fileData = JSON.parse(response);
        //If the file is a PDF, we can open it on a new tab to preview it
        if (!IsPreview) {
          DownloadBase64File(fileData.filecontents, FileName);
        } else {
          OpenSharePointPDFInNewTab(fileData);
        }
        HideSpinner();
      })
      .fail(function () {
        HideSpinner();
        ShowError("Error loading file content");
      })
  }

  function GenerateSharePointTable(jsonData) {
    $("#table_container").html("");
    let table = $('<table>');
    //If there are files in the folder, we generate the table with the file list
    if (jsonData.length > 0) {
      //Creates the table header
      table.append($('<thead>').html("<tr><th>Name</th></tr>"));
      //Creates the table body with the file list
      jsonData.forEach((item) => {
        //Creates a row for each file/folder
        let tr = $('<tr>');
        tr.append($('<td>').text(item["{FilenameWithExtension}"]));
        let td_open = $('<td>');
        var btn = $('<button>');

        if (item["{IsFolder}"] == true) {
          //for folders, the action is to reload the contents with files for this folder instead of the root folder
          btn.on("click", function () { LoadSharePointFolderContents(item["{FullPath}"]); });
          btn.append($("<span>").addClass("glyphicon glyphicon-folder-open"));
          btn.append($("<span>").text("Open Folder"));
        }
        else {
          //for files, the action is to download the file - by calling the flow to get the file as base64 and then download it
          btn.on("click", function () { OpenSharePointDocument(item["{Identifier}"], item["{FilenameWithExtension}"]) });
          btn.append($("<span>").addClass("glyphicon glyphicon-save-file"));
          btn.append($("<span>").text("Download"));
        }
        tr.append($("<td>").append(btn));

        //If the file is a PDF, we can add a button to preview the file (opening it on a new Tab)  
        if (item["{FilenameWithExtension}"].toLowerCase().endsWith(".pdf")) {
          var btnPreview = $('<button>');
          btnPreview.on("click", function () { OpenSharePointDocument(item["{Identifier}"], item["{FilenameWithExtension}"], true) });
          btnPreview.append($("<span>").addClass("glyphicon glyphicon-new-window"));
          btnPreview.append($("<span>").text("Preview"));
          tr.append($("<td>").append(btnPreview));
        }

        table.append(tr);
      });
      $("#table_container").append(table)
    }
    //If there are no files in the folder, we show a message
    else {
      $("#table_container").append($('<span>').text("No files found."));
    }
  }

  //Generates a dummy link auto downloading the file when the function is called
  function DownloadBase64File(base64FileContent, fileName) {    
    var base64String = "data:application/octet-stream;base64," + base64FileContent;
    const downloadLink = document.createElement("a");
    downloadLink.href = base64String;
    downloadLink.download = fileName;
    downloadLink.click();
  }

  function OpenSharePointPDFInNewTab(fileData) {
    //Converts the base64 file content to a Blob
    var byteCharacters = atob(fileData.filecontents);
    var byteNumbers = new Array(byteCharacters.length);
    for (var i = 0; i < byteCharacters.length; i++) {
      byteNumbers[i] = byteCharacters.charCodeAt(i);
    }
    var byteArray = new Uint8Array(byteNumbers);
    var file = new Blob([byteArray], { type: fileData.type ?? "application/pdf" });
    //Creates a URL for the Blob locally and opens it on a new tab
    var fileURL = URL.createObjectURL(file);
    window.open(fileURL, '_blank');
  }

  function ShowError(message) {
    alert(message);
  }

  function ShowSpinner(message) {
    $('#spnSpinnerMessage').text(message);
    $('#divSpinner').css('display', 'flex');
  }

  function HideSpinner() {
    $('#divSpinner').css('display', 'none');
  }
</script>

References

Configure Cloud Flows integration in Power Pages – Microsoft Learn

One comment

Leave a Reply

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