Is there a method to render PDFs in modal views, without the PDF Preview Native Kintone Plug-in?

Question / Problem

I am trying to use a plugin to show attachments under their respective modals as a preview, as such I am using the Excel example that is ubiquitous.

However, if I try to use it to load a PDF file, I get an authorization denied request.
I am assuming this is because I am referencing the file via a plug-in and not via an API token.

I tried adding authorization headers. But, it seems that the issue is not so much accessing the file as it is displaying the file on the modal.

Error Message

{"code":"CB_JH01","id":"6Qyv9HItzZvqRbjGAow3","message":"Authentication failed. X-Requested-With header is required for session authentication."}

:zap: Note: I have tried sending the headers manually, but they seem to be lost between the file download and the modal loading.

Current Situation

How is your Kintone App configured?

Developer account

What are you trying to do?

Previewing a PDF file inside a modal, that is button triggered, rather than clicked attachment.

Code / Attempts

Here is the code:

// Check File Extension (Excel or PDF)

function checkFileExtension(file) {
  let reg = /\.xl(s[xmb]|t[xm]|am|s)$/g; // Excel file extension regex
  let pdfReg = /\.pdf$/g; // PDF file extension regex
  return reg.test(file) || pdfReg.test(file);
}

// Check if the file is an Excel file (cut before finishing)

function checkXls(file) {
  let reg = /\.xl(s[xmb]|t[xm]|am|s)$/g;
  return reg.test(file);
}

// Load Preview

function loadModal(fileInfo) {
  let previewElement;
  if (window.innerWidth > 768) {
    // Desktop preview
    jQuery(".file-image-container-gaia").each(function (i, e) {
      let fileName = jQuery(e).children("a:eq(0)").text();
      if (
        fileName == fileInfo.name &&
        jQuery(e).children("button").length == 0
      ) {
        previewElement = jQuery(e);
        return false;
      }
    });
  } else {
    // Mobile preview
    jQuery(".control-showlayout-file-gaia .control-value-gaia div").each(
      function (index, e) {
        let fileName = jQuery(e).find("a:eq(0)").text();
        if (fileName == fileInfo.name && jQuery(e).find("button").length == 0) {
          previewElement = jQuery(e);
          return false;
        }
      }
    );
  }

  if (!previewElement) return;

  let modalId = "myModal" + fileInfo.fileKey;
  let tabId = "myTab" + fileInfo.fileKey;
  let tabContentId = "tab-content" + fileInfo.fileKey;
  let $button = $(
    '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#' +
      modalId +
      '"><span class="fa fa-search"></span></button>'
  );
  let myModal =
    '<style type="text/css">td{word-break: keep-all;white-space:nowrap;}</style>' +
    '<div class="modal fade tab-pane active" id="' +
    modalId +
    '" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">' +
    '<div class="modal-dialog ' +
    (window.innerWidth > 768 ? "modal-xl" : "modal-dialog-scrollable") +
    '" style="border-radius:5px" role="document">' +
    '<div class="modal-content">' +
    '<div class="modal-header">' +
    '<h5 class="modal-title">' +
    fileInfo.name +
    "</h5>" +
    '<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
    '<span aria-hidden="true">&times;</span>' +
    "</button>" +
    "</div>" +
    '<ul class="nav nav-tabs" id=' +
    tabId +
    ">" +
    "</ul>" +
    "<div id=" +
    tabContentId +
    ' class="tab-content">' +
    '<div class="d-flex justify-content-center">' +
    '<div class="spinner-border" role="status">' +
    '<span class="sr-only">Loading...</span>' +
    "</div>" +
    "</div>" +
    "</div>" +
    '<div class="modal-footer">' +
    '<button type="button" class="btn btn-secondary" data-dismiss="modal">CLOSE</button>' +
    "</div>" +
    "</div>" +
    "</div>" +
    "</div>";

  previewElement.append($button);
  $("body").prepend(myModal);

  $("#" + modalId).on("shown.bs.modal", function (e) {
    if (checkXls(fileInfo.name)) {
      loadExcelFile(fileInfo);
    } else {
      loadPDFFile(fileInfo);
    }
  });
}

// Download and load Excel file into modal
function loadExcelFile(fileInfo) {
  let fileUrl = "/k/v1/file.json?fileKey=" + fileInfo.fileKey;
  readWorkbookFromRemoteFile(fileUrl, function (workbook) {
    readWorkbook(workbook, fileInfo);
  });
}

// Load PDF file into modal
function loadPDFFile(fileInfo) {
  let fileUrl = "/k/v1/file.json?fileKey=" + fileInfo.fileKey;
  let loginName = "***";
  let password = "***";
  let authString = btoa(loginName + ":" + password); // Base64 encode login name and password

  fileUrl += "&X-Cybozu-Authorization=" + authString;

  let xhr = new XMLHttpRequest();
  xhr.open("GET", fileUrl, true);
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  xhr.setRequestHeader("X-Cybozu-Authorization", authString); // Set the X-Cybozu-Authorization header again
  xhr.onload = function () {
    if (xhr.status === 200) {
      let pdfHtml =
        '<embed src="' +
        fileUrl +
        '" type="application/pdf" width="100%" height="600px" />';
      let tabContentId = "tab-content" + fileInfo.fileKey;
      jQuery("#" + tabContentId).html(pdfHtml);
      console.log("PDF file loaded successfully.");
    } else {
      console.log("Failed to load PDF file. Status code: " + xhr.status);
    }
  };

  // Log the secondary authorization header
  console.log("Secondary Authorization Header: " + xhr.getAllResponseHeaders());

  xhr.send();
}

// Read workbook from remote file (Excel)
function readWorkbookFromRemoteFile(url, callback) {
  let xhr = new XMLHttpRequest();
  xhr.open("get", url, true);
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  xhr.responseType = "arraybuffer";
  xhr.onload = function (e) {
    if (xhr.status == 200) {
      let data = new Uint8Array(xhr.response);
      let workbook = XLSX.read(data, { type: "array" });
      if (callback) callback(workbook);
    }
  };
  xhr.send();
}

// Read workbook (Excel)
function readWorkbook(workbook, fileInfo) {
  let sheetNames = workbook.SheetNames;
  let navHtml = "";
  let tabHtml = "";
  let myTabId = "myTab" + fileInfo.fileKey;
  let tabContentId = "tab-content" + fileInfo.fileKey;

  for (let index = 0; index < sheetNames.length; index++) {
    let sheetName = sheetNames[index];
    let worksheet = workbook.Sheets[sheetName];
    let sheetHtml = XLSX.utils.sheet_to_html(worksheet);

    let tabid = "tab" + fileInfo.fileKey + "-" + index;
    let xlsid = "xlsid" + fileInfo.fileKey + "-" + index;
    let active = index == 0 ? "active" : "";

    navHtml +=
      '<li class="nav-item"><a class="nav-link ' +
      active +
      '" href="#" data-target="#' +
      tabid +
      '" data-toggle="tab">' +
      sheetName +
      "</a></li>";

    tabHtml +=
      '<div id="' +
      tabid +
      '" class="tab-pane ' +
      active +
      '" style="padding:10px;overflow:auto;height:600px" >' +
      '<div id="' +
      xlsid +
      '">' +
      sheetHtml +
      " </div></div>";
  }

  jQuery("#" + myTabId).html(navHtml);
  jQuery("#" + tabContentId).html(tabHtml);
  jQuery("#" + tabContentId + " table").addClass(
    "table table-bordered table-hover"
  );
}

// Build Kintone
kintone.events.on("app.record.detail.show", function (event) {
  let record = event.record;
  for (let index in record) {
    let field = record[index];
    if (field.type === "FILE") {
      let fieldValue = field.value;
      fieldValue.forEach(function (file) {
        if (checkFileExtension(file.name)) {
          loadModal(file);
        }
      });
    }
  }
});

Error Message

Screenshot of the Kintone App:

  • A pop-up modal is opened to a PDF file
  • Instead of displaying the PDF preview, the Authentication failed error is displayed

Screenshot of the browser console:

Secondary Authorization Header:
PDF file loaded successfully.
GET https://***.kintone.com/k/v1/file.json?fileKey=***&X-Cybozu-Authorization=***== 520

Desired Outcome / Expected Behavior

For the PDF to be loaded into the modal.

Referenced Resources

Hello @Lumipallo_Studio

Clarification - Does it work for an Excel file?

To clarify, does this mean you successfully opened an Excel file in the modal preview, but a PDF file fails?

Hello @Genji

Yes, that seems to be the case.

The Excel file opens and displays correctly in the modal. I can change sheets and use all functionality.

However, when attempting to open a PDF file, I get the following error in the modal:

{"code":"CB_JH01","id":"6Qyv9HItzZvqRbjGAow3","message":"Authentication failed. X-Requested-With header is required for session authentication."}

I attempted to use authentication headers without success.

Hello @Lumipallo_Studio ,

Upon checking the script, it appears that when setting the value for let pdfHtml, it directly refers to the path "/k/v1/file.json?fileKey={file key}".

As a result, it seems that the CB_JH01 error you reported is occurring because the necessary authentication information header is not included during the execution of the Kintone REST API.

Furthermore, it's not possible to append authentication information to the path "/k/v1/file.json?fileKey={file key}" to retrieve file information.

Therefore, it is necessary to change the information you're retrieving, as well as the method of displaying the PDF file itself.

As a workaround, you may want to specify a blob in the object tag, as suggested in the following article:
(The article is in Japanese):

Eg.

(function() {
    'use strict';

    function getFile(url) {
        var df = new $.Deferred();
        var xhr = new XMLHttpRequest();

        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.responseType = 'blob';

        xhr.onload = function(e) {
            if (this.status === 200) {
                df.resolve(this.response);
            }
        };

        xhr.send();
        return df.promise();
    }

    kintone.events.on('app.record.detail.show', function(event) {
        var record = event.record;

        var space = kintone.app.record.getSpaceElement('space');
        var fileKey = record.file.value[0].fileKey;
        var fileUrl = '/k/v1/file.json?fileKey=' + fileKey;

        var promise = getFile(fileUrl);
        promise.done(function(pdfData) {
            var url = window.URL || window.webkitURL;
            var imageUrl = url.createObjectURL(pdfData);
            var preview = '<object data="' + imageUrl + '" type="application/pdf" width="100%" height="100%">';
            preview += '</object>';
            $(space).append(preview).css('height', '500');
        });
    });
})();

So please revise the information you are retrieving and the method of displaying the PDF file.

1 Like