qFile: Download just part of ZIP file

translate qFile: ZIPファイルの一部のみをダウンロード

Unzips the Zip-file attached in FILE type data. User can download any file in any ZIP file. ZIP compatible file formats such as “docx”, “apk”, “qar” can also be decompressed.

Input / Output

  • ← qFile q_AttachedFiles
  • div#user_BigButtonsPanel
    • button.user_BigButton
    • div#user_SmallButtonsPanel
      • button.user_SmallButton
      • → (download to local device)

Code Example

HTML/JavaScript (click to close)
<div id="user_BigButtonsPanel">(WAITING...)</div>
<div id="user_SmallButtonsPanel">(waiting...)</div>
<script>
const user_fieldFILES  = 'q_AttachedFiles';        // ★★★ EDIT (FILE field name) ★★★
const user_selectorBBP = 'user_BigButtonsPanel';   // ★★★ EDIT (output DIV id) ★★★
const user_selectorSBP = 'user_SmallButtonsPanel'; // ★★★ EDIT (output DIV id) ★★★

qbpms.form.on ( 'ready',                   user_refreshBigButtonsPanel );
qbpms.form.on ( 'change', user_fieldFILES, user_refreshBigButtonsPanel );

function user_refreshBigButtonsPanel () {
  //// Remove all elements in BigButtonsPanel
  let elBigButtonsPanel = document.querySelector( "#" + user_selectorBBP );
  while ( elBigButtonsPanel.firstChild ){
    elBigButtonsPanel.removeChild ( elBigButtonsPanel.firstChild );
  }
  //// Remove all elements in SmallButtonsPanel
  let elSmallButtonsPanel = document.querySelector( "#" + user_selectorSBP );
  while ( elSmallButtonsPanel.firstChild ){
    elSmallButtonsPanel.removeChild ( elSmallButtonsPanel.firstChild );
  }
  /// Create Big Buttons
  let arrAttachedFiles  = qbpms.form.get( user_fieldFILES ); // `[]` in case EMPTY
  console.log ( '=== #of Files: ' + arrAttachedFiles.length + ' ===' );
  for ( let i = 0; i < arrAttachedFiles.length; i++ ) {  // each Big Button
    // Big Button refreshs SmallButtonsPanel
    let elButton = document.createElement( 'button' );
        elButton.type = 'button';
        elButton.title = "show files in " + arrAttachedFiles[i].name; // tooltip
        elButton.classList.add( "user_BigButton" );
        elButton.onclick = user_refreshSmallButtonsPanel;
        elButton.dataset.qfileId = arrAttachedFiles[i].id;
        elButton.dataset.pdiId = arrAttachedFiles[i].processDataInstanceId;
    let elButtonFace = document.createElement( 'span' );
        elButtonFace.classList.add( "material-icons" );
        elButtonFace.append( "expand_circle_down" ); // text node
    elButton.append( elButtonFace );
    // Text for Content-type
    let elSpanCtype = document.createElement( 'span' );
        elSpanCtype.style.fontStyle = 'italic';
    let elTextCtype = document.createTextNode( arrAttachedFiles[i].contentType );
    elSpanCtype.appendChild ( elTextCtype );
    // Text for Filename
    let elTextFilename = document.createTextNode( arrAttachedFiles[i].name );
    let elBr = document.createElement( 'br' );
    elBigButtonsPanel.append ( elBr, elButton, " ", elSpanCtype, " ", elTextFilename );
  }
  console.log( " Big Buttons, Created" );
}

function user_refreshSmallButtonsPanel () {
  //// Remove all elements in SmallButtonsPanel
  let elSmallButtonsPanel = document.querySelector( "#" + user_selectorSBP );
  while ( elSmallButtonsPanel.firstChild ){
    elSmallButtonsPanel.removeChild ( elSmallButtonsPanel.firstChild );
  }

  //// Create FetchURL for specified ZipFile
  let strQcontextPath = user_getQcontextPath();
  let strFetchUrl = "";
      strFetchUrl += `${strQcontextPath}/API/OR/ProcessInstance/File/download?`;
      strFetchUrl += `id=${this.dataset.qfileId}&`;
      strFetchUrl += `processDataInstanceId=${this.dataset.pdiId}`;
  console.log( " Small Buttons, Creating from: " + strFetchUrl );

  //// Fetch and Unzip
  let arrButtonNames = [];
  let arrButtonBlobs = [];
  console.log( " Qfile Fetch URL: " + strFetchUrl );
  user_getScript( 'https://cdn.jsdelivr.net/npm/zlibjs@0.3.1/bin/unzip.min.js',
                  // https://www.jsdelivr.com/package/npm/zlibjs
                  'sha256-pKobUkzPTKMnWX5yXGUt55Lp7W9pboaAhaXiI/hgIr4=',
                  // https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
                  function () {
    fetch( strFetchUrl )
    .then( response => {
      if ( !response.ok ) {
        elSmallButtonsPanel.innerHTML += " Network response was not ok";
        throw new Error( 'Network response was not ok' );
      }
      return response.arrayBuffer();
    })
  //// List files contained in the ZipFile
    .then( buf => {
      console.log( "ArrayBuffer byteLength: " + buf.byteLength );
      let byteArray = new Uint8Array( buf );
      // https://github.com/imaya/zlib.js/blob/develop/README.md#pkzip-1
      // https://github.com/imaya/zlib.js/blob/develop/README.en.md#pkzip-1
      let unzip = new Zlib.Unzip( byteArray );
      let arrFilenames = unzip.getFilenames();
      console.log( "File and Directory: " + arrFilenames.length );

  //// Create Small Buttons as many as files (directories skipped)
      for( let i = 0; i < arrFilenames.length; i++ ){
        if ( arrFilenames[i].slice(-1) === "/" ) { continue; } // directory
        arrButtonNames.push( arrFilenames[i] );
        let blob = new Blob( [unzip.decompress( arrFilenames[i] )],{type: "octet/stream"} );
        arrButtonBlobs.push( blob );
        console.log( "File: " + arrFilenames[i] + " " + blob.size );
      }
      elSmallButtonsPanel.innerHTML =
        "<br />== Downloadable Files: " + arrFilenames.length + " ==";
      for( let i = 0; i < arrButtonNames.length; i++ ){
      // br
        const elBr = document.createElement("br");
        elSmallButtonsPanel.appendChild( elBr );
      // Small Button (anchor) downloads file
        let elSmallButton = document.createElement( 'a' );
            elSmallButton.href = URL.createObjectURL( arrButtonBlobs[i] );
            elSmallButton.download = arrButtonNames[i].split('/').pop(); // Get only last value of split array
            elSmallButton.title = "download " + arrButtonNames[i]; // tooltip
            elSmallButton.classList.add( "user_SmallButton" );
        let elSmallButtonFace = document.createElement( 'span' );
            elSmallButtonFace.classList.add( "material-icons" );
            elSmallButtonFace.append( "cloud_download" ); // text node
        elSmallButton.append( elSmallButtonFace );
        elSmallButtonsPanel.appendChild( elSmallButton );
      // span text
        const elSpan = document.createElement("span");
        elSpan.innerHTML = " " + arrButtonNames[i] + " (" + arrButtonBlobs[i].size + " byte)";
        elSmallButtonsPanel.appendChild( elSpan );
      }
    })
    .catch( error => {
      console.error('refreshSmallButtonsPanel error: ', error);
      elSmallButtonsPanel.innerHTML =
        "<span style='color:red'>ERROR: ZIP file? (eg: .zip .pptx .qar ..)</span>";
    });
  }); // endof js-dynamic-loading
}

/**
 * Gets the context path of the current Questetra BPM Suite instance from the URL.
 * This function supports both new and legacy formats of the URL.
 *
 * @example
 * "https://foobar.questetra.net"/abcd/efg
 * "https://s.questetra.net/12345678"/abcd/efg (legacy)
 *
 * @returns {string|null} The context path of the current Questetra BPM Suite instance,
 *  or null if the URL is not for Questetra BPM Suite.
 */
function user_getQcontextPath () {
  // "https://foobar.questetra.net"/abcd/efg
  // "https://s.questetra.net/12345678"/abcd/efg (legacy)
  let regQcontextPath = /https:\/\/[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9].questetra.net|https:\/\/s.questetra.net\/(\d{8})/;
  let arrQcontextPath = location.href.match ( regQcontextPath );
  console.log( "--- Decoration 'QcontextPath': " + arrQcontextPath[0] );
  if ( arrQcontextPath === null ) {
    console.error( "\n DecorationError:" +
                   " This URI is not for Questetra BPM Suite \n" );
    return null;
  }
  return arrQcontextPath[0];
}

/**
 * Dynamically loads a script from a given URL and
 * calls a callback function when the script is loaded.
 *
 * @function user_getScript
 * @param {string} scriptUrl - The URL of the script to be loaded.
 * @param {string} integrity - The integrity attribute value for the script, used for Subresource Integrity (SRI) check.
 * @param {function} callback - The function to be executed once the script has been loaded.
 */
function user_getScript ( scriptUrl, integrity, callback ) { // for dynamic-loading
  const script = document.createElement ( 'script' );
  script.src = scriptUrl;
  // Subresource Integrity (SRI) is a security feature that enables browsers to verify that
  // resources they fetch (for example, from a CDN) are delivered without unexpected manipulation.
  // https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
  script.integrity = integrity;
  script.crossOrigin = "anonymous"; // note: property name is "crossOrigin", not "crossorigin"
  script.onload = callback;
  document.body.appendChild ( script );
}
</script>
warning Freely modifiable HTML/JavaScript code, MIT License. No warranty of any kind.
(Decoration using JavaScript is only available in the Professional edition: M213)

Capture

Unzips the Zip-file attached in FILE type data. User can download any file in any ZIP file. ZIP compatible file formats such as "docx", "apk", "qar" can also be decompressed.

See Also

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: