Follow-Up for the "drag & drop file upload in SBM"

There was once a post regarding this topic here: RE: Drag & Drop file uploading in SBM 

But this post doen't seem to have any attachments any more (or i didn't found them). So I created my own drag & drop upload feature for SBM. Basic idea: Create a HTML/Javascrpt widget on a form in the SBM Composer and add the code below into the "content" of the HTML/Javascript widget. Thats it. A file drop zone will be rendered on the form. Any files put into this drop zone will be added to the current ticket.

<style>
#filedropzone \{
	width: 100%;
	line-height: 32px;   
	transition: 0.2s;
	text-align: center;
	border-style: dashed;
	border-width: 2px;
	border-color: lightgray;
	margin-bottom: 5px;
}
#filedropzone:hover \{ background-color:#ddf; }
.filedropzone \{ background-color:#ddf; }
</style>
<script>
function fileselect(e) \{
	var tableId = {_TableId};
	var recId = {_RecordId};
	var graphicFileExtentions=['.gif', '.png','.jpg', '.jpeg', '.GIF', '.PNG','.JPG', '.JPEG'];
	var preview = e.target;	/* the area where we display some info for the user, could be any other div also */
	var reader = new FileReader();
	var file;	/* will store one file, which is later used as argument for the reader: reader.readAsArrayBuffer(file); */

	e.stopPropagation();
	e.preventDefault();
	e.target.classList.remove("filedropzone");	/* just in case that this is still there... */

	if ( e.type == "change" ) file = e.target.files[0];	/* support onchange eveont of input file html element */
	else if ( e.type == "drop" ) file = e.dataTransfer.files[0]; /* support for file drag and drop */
	else return;		/* unknown event --> abort */

	preview.textContent = file.name;  /* give some feedback to the user. this is not changed back to the original text in this script, instead the refresh will revert this */

    reader.onload = function(f) \{
		var view = new Uint8Array(f.target.result);
		/* Construct base64 from the view to the Uint8Array. Looks complicated and is indeed complicated due to the required conversion from u8 array to string. */
		var b64 = btoa( 	/* array to string in blocks with size of 10000 to avoid the memory/stack error with some browsers */
				Array.from(Array(Math.ceil(view.length/10000)), (_,i)=> i*10000)
				.map(i=>String.fromCharCode.apply(null, view.slice(i, i + 10000)))
				.join('') 
				);
		/* Construct masterObj for the SBM UpdateFileAttachments REST interface */
		var fileObj = \{ 'action':'NEW-ONLY', 'title':file.name, 'filename':file.name, 'accessType':'ATTACHACCESS-UNRESTRICTED', 'showAsImage':graphicFileExtentions.some(x=>file.name.endsWith(x)), 'contentsbase64':\{'data':b64} };
		var masterObj = \{ 'deleteAll':false, 'includeFile': false, 'fileAttachFilter':[''], 'fileAttachList':[fileObj] };
		/* Post the file to the SBM REST Endpoint */ 
		var xhr = new XMLHttpRequest();
		xhr.open("POST", 'tmtrack.dll?JSONPage&command=jsonapi&JSON_Func=UpdateFileAttachments&JSON_P1='+tableId+'&JSON_P2='+recId, true);
		xhr.setRequestHeader("Content-Type", "application/json");
		xhr.onload = function(event)  \{
			if ( e.type == "change" ) e.target.value = null;		// in case of onchange event, try to clear the file input, may not work on all browsers
			console.log("Transfer to SBM server done", file.name, xhr.responseText);
			ReloadItem();  /* Optional: SBM Javascript function to reload the current item. This will also restore the original message in the dropzone. This might cause a leaving page browser alert. */
			preview.textContent = file.name + " uploaded";  /* give some feedback to the user. this is not changed back to the original text in this script, instead the refresh will revert this */
		}	
		xhr.send(JSON.stringify(masterObj));
    }
	reader.readAsArrayBuffer(file);
}
function filedragover(e) \{
    e.stopPropagation();
    e.preventDefault();
    e.dataTransfer.dropEffect = 'copy'; 
}

function filedragenter(e) \{
	if ( e.target )
		if ( e.target.classList )
			e.target.classList.add("filedropzone");
}

function filedragleave(e) \{
	if ( e.relatedTarget )
		if ( e.relatedTarget.nodeName == "#text" )
			return;		/* ignore leave event if we enter the inner text */
	if ( e.target )
		if ( e.target.classList )
			e.target.classList.remove("filedropzone");
}
</script>
<label id="filedropzone" ondragover="filedragover(event)" ondrop="fileselect(event)" ondragenter="filedragenter(event)" ondragleave="filedragleave(event)">
Upload: Drag and drop any file here (or click to open file chooser)<input style="display:none" type="file" accept="*.*" onchange="fileselect(event)"></div>

Note: The { are already escaped for the HTML/Javascript widget whereever required.

  • 0  

    Wow! This is great. Thanks for sharing this code. We have such a wonders community of users who love to share their innovations. Slight smile

  • 0 in reply to   

    Hearts

  • 0

    Yea!  Oliver is still around!!!

  • 0 in reply to 

    :-) Still working with SBM since 2008. SBM: The best low-code development tool around!

  • 0  

     This is awesome!  I implemented without issue; it is plug 'n play.  I did add some code to parse the JSON response text and return errors to the user.  Here is the updated code.

    <style>
    #fdError \{
    	color: red;
    	font-weight: bold;
    	width: 100%;
    	line-height: 32px;   
    	text-align: center;
    	margin: 5px;
    }
    
    #filedropzone \{
    	width: 100%;
    	line-height: 32px;   
    	transition: 0.2s;
    	text-align: center;
    	border-style: dashed;
    	border-width: 2px;
    	border-color: lightgray;
    	margin-bottom: 5px;
    }
    #filedropzone:hover \{ background-color:#ddf; }
    .filedropzone \{ background-color:#ddf; }
    </style>
    <script>
    function fileselect(e) \{
    	var tableId = {_TableId};
    	var recId = {_RecordId};
    	var graphicFileExtentions=['.gif', '.png','.jpg', '.jpeg', '.GIF', '.PNG','.JPG', '.JPEG'];
    	var preview = e.target;	/* the area where we display some info for the user, could be any other div also */
    	var reader = new FileReader();
    	var file;	/* will store one file, which is later used as argument for the reader: reader.readAsArrayBuffer(file); */
    
    	e.stopPropagation();
    	e.preventDefault();
    	e.target.classList.remove("filedropzone");	/* just in case that this is still there... */
    
    	if ( e.type == "change" ) file = e.target.files[0];	/* support onchange eveont of input file html element */
    	else if ( e.type == "drop" ) file = e.dataTransfer.files[0]; /* support for file drag and drop */
    	else return;		/* unknown event --> abort */
    
    	preview.textContent = file.name;  /* give some feedback to the user. this is not changed back to the original text in this script, instead the refresh will revert this */
    
        reader.onload = function(f) \{
    		var view = new Uint8Array(f.target.result);
    		/* Construct base64 from the view to the Uint8Array. Looks complicated and is indeed complicated due to the required conversion from u8 array to string. */
    		var b64 = btoa( 	/* array to string in blocks with size of 10000 to avoid the memory/stack error with some browsers */
    				Array.from(Array(Math.ceil(view.length/10000)), (_,i)=> i*10000)
    				.map(i=>String.fromCharCode.apply(null, view.slice(i, i + 10000)))
    				.join('') 
    				);
    		/* Construct masterObj for the SBM UpdateFileAttachments REST interface */
    		var fileObj = \{ 'action':'NEW-ONLY', 'title':file.name, 'filename':file.name, 'accessType':'ATTACHACCESS-UNRESTRICTED', 'showAsImage':graphicFileExtentions.some(x=>file.name.endsWith(x)), 'contentsbase64':\{'data':b64} };
    		var masterObj = \{ 'deleteAll':false, 'includeFile': false, 'fileAttachFilter':[''], 'fileAttachList':[fileObj] };
    		/* Post the file to the SBM REST Endpoint */ 
    		var xhr = new XMLHttpRequest();
    		xhr.open("POST", 'tmtrack.dll?JSONPage&command=jsonapi&JSON_Func=UpdateFileAttachments&JSON_P1='+tableId+'&JSON_P2='+recId, true);
    		xhr.setRequestHeader("Content-Type", "application/json");
    		xhr.onload = function(event)  \{
    			if ( e.type == "change" ) e.target.value = null;		// in case of onchange event, try to clear the file input, may not work on all browsers
    			var responseObj = JSON.parse(xhr.responseText);
    			if ( responseObj && responseObj.result && responseObj.result.type =="ERROR"  ) \{
    				if ( responseObj.result.msgLoc ) \{
    					document.getElementById("fdError").innerHTML = responseObj.result.msgLoc;
    					console.log("Transfer to SBM server failed: ", file.name, responseObj.result.msgLoc);
    				}
    			    else \{
    				    	document.getElementById("fdError").innerHTML = responseObj.result.msg;
    					console.log("Transfer to SBM server failed: ", file.name, responseObj.result.msg);
    				}
    			preview.textContent = 'Upload: Drag and drop any file here';
    		    }
    			else \{
    				console.log("Transfer to SBM server done", file.name, xhr.responseText);
    				ReloadItem();  /* Optional: SBM Javascript function to reload the current item. This will also restore the original message in the dropzone. This might cause a leaving page browser alert. */
    				preview.textContent = file.name + " uploaded";  /* give some feedback to the user. this is not changed back to the original text in this script, instead the refresh will revert this */
    			}
    		}	
    		xhr.send(JSON.stringify(masterObj));
        }
    	reader.readAsArrayBuffer(file);
    }
    function filedragover(e) \{
        e.stopPropagation();
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy'; 
    }
    
    function filedragenter(e) \{
    	if ( e.target )
    		if ( e.target.classList )
    			e.target.classList.add("filedropzone");
    }
    
    function filedragleave(e) \{
    	if ( e.relatedTarget )
    		if ( e.relatedTarget.nodeName == "#text" )
    			return;		/* ignore leave event if we enter the inner text */
    	if ( e.target )
    		if ( e.target.classList )
    			e.target.classList.remove("filedropzone");
    }
    </script>
    
    <label id="filedropzone" ondragover="filedragover(event)" ondrop="fileselect(event)" ondragenter="filedragenter(event)" ondragleave="filedragleave(event)">
    Upload: Drag and drop any file here<input style="display:none" type="file" accept="*.*" onchange="fileselect(event)"></label>
    <div id="fdError"></div>

  • 0 in reply to   

    will this only work with the Attachment expander? or will it also work with the Files field? what would I need to do to make this work with a files field? 

  • 0 in reply to 

    Thanks David for the update!

    This code is for the Attachment expander, however the file field just uses a different REST endpoint. So you need to update lines 52 to 56.

    Documentation for the endpoint is here: UpdateFileField - Solutions Business Manager (microfocus.com)