qFile: Download just part of ZIP file
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>
Freely modifiable HTML/JavaScript code, MIT License. No warranty of any kind.
Capture

