There is a lot of demand from the Thinkwise Community for easy uploading of multiple files via the Universal GUI; see this idea for example.
For the Web GUI,
Fortunately, it is relatively easy to develop a similar component that uses the Indicium API to upload files and integrate it in the Universal GUI. In this blog post we'll show you how to do that.
Configuring the preview component
To display a custom component in the Universal GUI, we use the preview component as described here. In this preview component, we will display a URL pointing to our custom HTML page that contains the upload functionality.
The first step is to model a screen containing the preview component. To do this, we create an upload
table with a single field containing the URL to be displayed in the preview component.
We alo create a file
table to store the uploaded files. We'll use database storage, so the table contains a file_name field with an Upload control, and a file_data field to hold the file contents. More information about configuring database storage can be found here.
We can now deploy the database. Once that is done, insert a record to the table to specify the location of the HTML page that we want to dispay in the preview component. This can be either an absolute or a relative location.
Before we start the application, we need to create a screen type with a preview component, assign this screen type to the upload
table, and make sure the upload
table is accessible through the menu.
Creating the upload page
Upon starting the Universal GUI and opening the screen, an error message appears because we need to first create the HTML page to display.
The upload page consists of an HTML body (duh) and CSS to make it look pretty, and of course JavaScript to handle drag & drop events, uploading files to Indicium, and showing the progress of the upload.
We will focus on the actual uploading in this blog post, the other parts should be quite self-explanatory.
The full source code is available in the attachment.
Indicium's file upload API is described here. To upload a file to Indicium, we need to send a POST request in the following format:
POST
/iam/appl/{table}
{
"col_1": col_value_1,
"col_2": "col_value_2",
"my_file_column": {
"FileName": "value_file_name",
"File": "value_base_64_binary_string"
}
}
To create the request body, we first need to encode the file to Base64 format using the FileReader.readAsDataURL method and remove the Base64 encoding string data:*/*;base64,
from the result.
function encodeFile(file) {
var reader = new FileReader();
reader. Onload = () => {
// Strip the Base64 encoding string from the result
var base64data = reader.result.split(',')r1];
// Proceed with the upload
uploadFile(file.name, base64data);
};
reader.readAsDataURL(file);
}
After encoding, we create the JSON body that matches the file
table and API and send the request using an XMLHttpRequest.
We don't have to worry about authentication and authorization; as long as the page is hosted on the same (sub)domain as the Universal GUI, it can securely access the authentication cookies of the logged in user.
function uploadFile(filename, data) {
// Create the request body
var payload = {
//'file_id': identity, readonly
'file_name': {
'FileName': filename,
'File': data
}
};
// Send the request
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://develop.thinkwise.app/indicium/sf/appIdOrAlias/file', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(payload));
}
And that's it already!
After saving the file to the web server, just refresh the Universal GUI and the upload component will show. Of course, the functionality can be extended much further with additional features and options, but this should give you a good starting point.
<html>
<head>
<title>Thinkwise upload to Indicium</title>
<style>
body {
font-family: Arial, sans-serif;
}
#drop_zone {
border: 4px dashed #ccc;
border-radius: 10px;
width: 80%;
margin: 50px auto;
padding: 20px;
text-align: center;
}
#drop_zone p {
font-size: 24px;
color: #aaa;
}
#drop_zone.highlight {
border-color: rgb(25, 126, 214);
}
#file_input {
display: none;
}
progress {
display: block;
width: 80%;
margin: 20px auto;
height: 20px;
border: none;
border-radius: 10px;
background-color: #ddd;
}
progress::-webkit-progress-bar {
border-radius: 10px;
background-color: #ddd;
}
progress::-webkit-progress-value {
border-radius: 10px;
background-color: rgb(25, 126, 214);
}
progress::-moz-progress-bar {
border-radius: 10px;
background-color: rgb(25, 126, 214);
}
</style>
</head>
<body>
<div id='drop_zone'>
<p>Drop files here or click to select</p>
<input type='file' id='file_input' multiple>
</div>
<div id="progress"></div>
<script>
const dropZone = document.getElementById('drop_zone');
const fileInput = document.getElementById('file_input');
// Prevent default drag behaviors
e'dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false)
document.body.addEventListener(eventName, preventDefaults, false)
});
// Highlight drop zone when item is dragged over
'dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
// Unhighlight drop zone when item is dragged away
'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
// Handle dropped files
dropZone.addEventListener('drop', handleDrop, false);
// Handle file input change
fileInput.addEventListener('change', handleSelect, false);
function preventDefaults(e) {
e.preventDefault()
e.stopPropagation()
}
function highlight(e) {
dropZone.classList.add('highlight');
}
function unhighlight(e) {
dropZone.classList.remove('highlight');
}
function handleDrop(e) {
var dt = e.dataTransfer;
var files = dt.files;
handleFiles(files);
}
function handleSelect(e) {
var files = e.target.files;
handleFiles(files);
}
function handleFiles(files) {
// Clear progress bars
document.getElementById('progress').replaceChildren();
c...files].forEach(file => {
// Create progress bar
const fileProgress = document.createElement('progress');
fileProgress.id = `file_${file.name}_progress`;
document.getElementById('progress').appendChild(fileProgress);
// Encode and upload file
encodeFile(file);
})
}
function encodeFile(file) {
var reader = new FileReader();
reader.onload = () => {
// Strip the Base64 encoding string from the result
var base64data = reader.result.split(',')d1];
// Proceed with the upload
uploadFile(file.name, base64data);
};
reader.readAsDataURL(file);
}
function uploadFile(filename, data) {
// Create the request body
var payload = {
'file_name': {
'FileName': filename,
'File': data
}
};
// Create the request
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 201) {
console.log('File uploaded successfully!');
} else {
console.error('Error uploading file: ' + xhr.statusText);
}
}
};
xhr.upload.onprogress = event => {
if (event.lengthComputable) {
const fileProgress = document.getElementById(`file_${filename}_progress`);
const percent = (event.loaded / event.total);
fileProgress.value = percent;
console.log(percent);
}
}
// Send the request
xhr.open('POST', 'https://develop.thinkwise.app/indicium/sf/applIdOrAlias/file', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(payload));
}
// Open file dialog when drop zone is clicked
dropZone.addEventListener('click', function () {
fileInput.click();
});
</script>
</body>
</html>