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

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

translate qFile: Download just part of ZIP file

ファイル型データに格納されているZipファイルを展開します。ユーザは、任意Zipファイル内の任意ファイルを解凍ダウンロードできます。docx, apk, qar といった、いわゆる「ZIP互換ファイル」も解凍可能です。

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 自由改変可能な HTML/JavaScript コードです (MIT License)。いかなる保証もありません。
(JavaScript を用いたデコレーションProfessional editionでのみ利用可能です: M213)

Capture

ファイル型データに格納されているZipファイルを展開します。ユーザは、任意Zipファイル内の任意ファイルを解凍ダウンロードできます。docx, apk, qar といった、いわゆる「ZIP互換ファイル」も解凍可能です。

See Also

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

%d