diff --git a/src/staticresources/FileUploadJS.resource b/src/staticresources/FileUploadJS.resource index 2d793bc..1786ebe 100644 --- a/src/staticresources/FileUploadJS.resource +++ b/src/staticresources/FileUploadJS.resource @@ -1,194 +1,196 @@ - var j$ = jQuery.noConflict(); - j$(document).ready(function() { - //Event listener for click of Upload button - j$("#uploadButton").click(function(){ - prepareFileUploads(); - }); - - //Event listener to clear upload details/status bars once upload is complete - j$("#clear").on('click',function(){ - j$(".upload").remove(); - }); - }); +var j$ = jQuery.noConflict(); +j$(document).ready(function() { +//Event listener for click of Upload button +j$("#uploadButton").click(function(){ + prepareFileUploads(); +}); + +//Event listener to clear upload details/status bars once upload is complete +j$("#clear").on('click',function(){ + j$(".upload").remove(); +}); +}); + +var byteChunkArray; +var files; +var currentFile; +var $upload; +var CHUNK_SIZE = 180000; //Must be evenly divisible by 3, if not, data corruption will occur +var VIEW_URL = '/servlet/servlet.FileDownload?file='; +//var parentId, you will see this variable used below but it is set in the component as this is a dynamic value passed in by component attribute + +//Executes when start Upload button is selected +function prepareFileUploads(){ +//Get the file(s) from the input field +files = document.getElementById('filesInput').files; + +//Only proceed if there are files selected +if(files.length == 0){ + alert('Please select a file!'); + return; //end function +} + +//Disable inputs and buttons during the upload process +j$(".uploadBox input").attr("disabled", "disabled"); +j$(".uploadBox button").attr({ + disabled: "disabled", + class: "btnDisabled" +}); + +//Build out the upload divs for each file selected +var uploadMarkup = ''; +for(i = 0; i < files.length; i++){ + //Determine file display size + if(files[i].size < 1000000){ + var displaySize = Math.floor(files[i].size/1000) + 'K'; + }else{ + var displaySize = Math.round((files[i].size / 1000000)*10)/10 + 'MB'; + } - var byteChunkArray; - var files; - var currentFile; - var $upload; - var CHUNK_SIZE = 180000; //Must be evenly divisible by 3, if not, data corruption will occur - var VIEW_URL = '/servlet/servlet.FileDownload?file='; - //var parentId, you will see this variable used below but it is set in the component as this is a dynamic value passed in by component attribute + //For each file being uploaded create a div to represent that file, includes file size, status bar, etc. data-Status tracks status of upload + uploadMarkup += '
'; //index used to correspond these upload boxes to records in the files array + uploadMarkup += '
'+ files[i].name + ' - '+ displaySize+ '
'; + uploadMarkup += '
0%
' + uploadMarkup += '
'; + uploadMarkup += '
'; + uploadMarkup += '
'; + uploadMarkup += '
'; + uploadMarkup += '
'; +} + +//Add markup to the upload box +j$('.uploadBox').append(uploadMarkup); + +//Once elements have been added to the page representing the uploads, start the actual upload process +checkForUploads(); +} + +function checkForUploads(){ +//Get div of the first matching upload element that is 'pending', if none, all uploads are complete +$upload = j$(".upload:first[data-status='pending']"); + +if($upload.length != 0){ + //Based on index of the div, get correct file from files array + currentFile = files[$upload.attr('data-index')]; - //Executes when start Upload button is selected - function prepareFileUploads(){ - //Get the file(s) from the input field - files = document.getElementById('filesInput').files; - - //Only proceed if there are files selected - if(files.length == 0){ - alert('Please select a file!'); - return; //end function - } + /*Build the byteChunkArray array for the current file we are processing. This array is formatted as: + ['0-179999','180000-359999',etc] and represents the chunks of bytes that will be uploaded individually.*/ + byteChunkArray = new Array(); + + //First check to see if file size is less than the chunk size, if so first and only chunk is entire size of file + if(currentFile.size <= CHUNK_SIZE){ + byteChunkArray[0] = '0-' + (currentFile.size - 1); + }else{ + //Determine how many whole byte chunks make up the file, + var numOfFullChunks = Math.floor(currentFile.size / CHUNK_SIZE); //i.e. 1.2MB file would be 1000000 / CHUNK_SIZE + var remainderBytes = currentFile.size % CHUNK_SIZE; // would determine remainder of 1200000 bytes that is not a full chunk + var startByte = 0; + var endByte = CHUNK_SIZE - 1; - //Disable inputs and buttons during the upload process - j$(".uploadBox input").attr("disabled", "disabled"); - j$(".uploadBox button").attr({ - disabled: "disabled", - class: "btnDisabled" - }); - - //Build out the upload divs for each file selected - var uploadMarkup = ''; - for(i = 0; i < files.length; i++){ - //Determine file display size - if(files[i].size < 1000000){ - var displaySize = Math.floor(files[i].size/1000) + 'K'; - }else{ - var displaySize = Math.round((files[i].size / 1000000)*10)/10 + 'MB'; - } + //Loop through the number of full chunks and build the byteChunkArray array + for(i = 0; i < numOfFullChunks; i++){ + byteChunkArray[i] = startByte+'-'+endByte; - //For each file being uploaded create a div to represent that file, includes file size, status bar, etc. data-Status tracks status of upload - uploadMarkup += '
'; //index used to correspond these upload boxes to records in the files array - uploadMarkup += '
'+ files[i].name + ' - '+ displaySize+ '
'; - uploadMarkup += '
0%
' - uploadMarkup += '
'; - uploadMarkup += '
'; - uploadMarkup += '
'; - uploadMarkup += '
'; - uploadMarkup += '
'; + //Set new start and stop bytes for next iteration of loop + startByte = endByte + 1; + endByte += CHUNK_SIZE; } - //Add markup to the upload box - j$('.uploadBox').append(uploadMarkup); - - //Once elements have been added to the page representing the uploads, start the actual upload process - checkForUploads(); + //Add the last chunk of remaining bytes to the byteChunkArray + startByte = currentFile.size - remainderBytes; + endByte = currentFile.size; + byteChunkArray.push(startByte+'-'+endByte); } - function checkForUploads(){ - //Get div of the first matching upload element that is 'pending', if none, all uploads are complete - $upload = j$(".upload:first[data-status='pending']"); - - if($upload.length != 0){ - //Based on index of the div, get correct file from files array - currentFile = files[$upload.attr('data-index')]; - - /*Build the byteChunkArray array for the current file we are processing. This array is formatted as: - ['0-179999','180000-359999',etc] and represents the chunks of bytes that will be uploaded individually.*/ - byteChunkArray = new Array(); - - //First check to see if file size is less than the chunk size, if so first and only chunk is entire size of file - if(currentFile.size <= CHUNK_SIZE){ - byteChunkArray[0] = '0-' + (currentFile.size - 1); - }else{ - //Determine how many whole byte chunks make up the file, - var numOfFullChunks = Math.floor(currentFile.size / CHUNK_SIZE); //i.e. 1.2MB file would be 1000000 / CHUNK_SIZE - var remainderBytes = currentFile.size % CHUNK_SIZE; // would determine remainder of 1200000 bytes that is not a full chunk - var startByte = 0; - var endByte = CHUNK_SIZE - 1; - - //Loop through the number of full chunks and build the byteChunkArray array - for(i = 0; i < numOfFullChunks; i++){ - byteChunkArray[i] = startByte+'-'+endByte; - - //Set new start and stop bytes for next iteration of loop - startByte = endByte + 1; - endByte += CHUNK_SIZE; - } - - //Add the last chunk of remaining bytes to the byteChunkArray - startByte = currentFile.size - remainderBytes; - endByte = currentFile.size; - byteChunkArray.push(startByte+'-'+endByte); - } - - //Start processing the byteChunkArray for the current file, parameter is '' because this is the first chunk being uploaded and there is no attachment Id - processByteChunkArray(''); - - }else{ - //All uploads completed, enable the input and buttons - j$(".uploadBox input").removeAttr("disabled"); - j$(".uploadBox button").removeAttr("disabled").attr("class","btn"); - - /*Remove the browse input element and replace it, this essentially removes - the selected files and helps prevent duplicate uploads*/ - j$("#filesInput").replaceWith(''); - } - } + //Start processing the byteChunkArray for the current file, parameter is '' because this is the first chunk being uploaded and there is no attachment Id + processByteChunkArray(''); + +}else{ + //All uploads completed, enable the input and buttons + j$(".uploadBox input").removeAttr("disabled"); + j$(".uploadBox button").removeAttr("disabled").attr("class","btn"); - //Uploads a chunk of bytes, if attachmentId is passed in it will attach the bytes to an existing attachment record - function processByteChunkArray(attachmentId){ - //Proceed if there are still values in the byteChunkArray, if none, all piece of the file have been uploaded - if(byteChunkArray.length > 0){ - //Determine the byte range that needs to uploaded, if byteChunkArray is like... ['0-179999','180000-359999'] - var indexes = byteChunkArray[0].split('-'); //... get the first index range '0-179999' -> ['0','179999'] - var startByte = parseInt(indexes[0]); //0 - var stopByte = parseInt(indexes[1]); //179999 - - //Slice the part of the file we want to upload, currentFile variable is set in checkForUploads() method that is called before this method - if(currentFile.webkitSlice){ - var blobChunk = currentFile.webkitSlice(startByte , stopByte + 1); - }else if (currentFile.mozSlice) { - var blobChunk = currentFile.mozSlice(startByte , stopByte + 1); - } - - //Create a new reader object, part of HTML5 File API - var reader = new FileReader(); - - //Read the blobChunk as a binary string, reader.onloadend function below is automatically called after this line - reader.readAsBinaryString(blobChunk); + /*Remove the browse input element and replace it, this essentially removes + the selected files and helps prevent duplicate uploads*/ + j$("#filesInput").replaceWith(''); +} +} + +//Uploads a chunk of bytes, if attachmentId is passed in it will attach the bytes to an existing attachment record +function processByteChunkArray(attachmentId){ +//Proceed if there are still values in the byteChunkArray, if none, all piece of the file have been uploaded +if(byteChunkArray.length > 0){ + //Determine the byte range that needs to uploaded, if byteChunkArray is like... ['0-179999','180000-359999'] + var indexes = byteChunkArray[0].split('-'); //... get the first index range '0-179999' -> ['0','179999'] + var startByte = parseInt(indexes[0]); //0 + var stopByte = parseInt(indexes[1]); //179999 + + //Slice the part of the file we want to upload, currentFile variable is set in checkForUploads() method that is called before this method + if(currentFile.webkitSlice){ + var blobChunk = currentFile.webkitSlice(startByte , stopByte + 1); + } else if (currentFile.mozSlice){ + var blobChunk = currentFile.mozSlice(startByte , stopByte + 1); + } else { + var blobChunk = currentFile.slice(startByte , stopByte + 1); + } + + //Create a new reader object, part of HTML5 File API + var reader = new FileReader(); + + //Read the blobChunk as a binary string, reader.onloadend function below is automatically called after this line + reader.readAsBinaryString(blobChunk); + + //Create a reader.onload function, this will execute immediately after reader.readAsBinaryString() function above; + reader.onloadend = function(evt){ + if(evt.target.readyState == FileReader.DONE){ //Make sure read was successful, DONE == 2 + //Base 64 encode the data for transmission to the server with JS remoting, window.btoa currently on support by some browsers + var base64value = window.btoa(evt.target.result); - //Create a reader.onload function, this will execute immediately after reader.readAsBinaryString() function above; - reader.onloadend = function(evt){ - if(evt.target.readyState == FileReader.DONE){ //Make sure read was successful, DONE == 2 - //Base 64 encode the data for transmission to the server with JS remoting, window.btoa currently on support by some browsers - var base64value = window.btoa(evt.target.result); + //Use js remoting to send the base64 encoded chunk for uploading + FileUploadController.attachBlob(parentId,attachmentId,currentFile.name,currentFile.type,base64value,function(result,event){ + + //Proceed if there were no errors with the remoting call + if(event.status == true){ + //Update the percent of the status bar and percent, first determine percent complete + var percentComplete = Math.round((stopByte / currentFile.size) * 100); + $upload.find(".percentComplete").text(percentComplete + '%'); + $upload.find(".statusBarPercent").css('width',percentComplete + '%'); - //Use js remoting to send the base64 encoded chunk for uploading - FileUploadController.attachBlob(parentId,attachmentId,currentFile.name,currentFile.type,base64value,function(result,event){ - - //Proceed if there were no errors with the remoting call - if(event.status == true){ - //Update the percent of the status bar and percent, first determine percent complete - var percentComplete = Math.round((stopByte / currentFile.size) * 100); - $upload.find(".percentComplete").text(percentComplete + '%'); - $upload.find(".statusBarPercent").css('width',percentComplete + '%'); - - //Remove the index information from the byteChunkArray array for the piece just uploaded. - byteChunkArray.shift(); //removes 0 index - - //Set the attachmentId of the file we are now processing - attachmentId = result; - - //Call process byteChunkArray to upload the next piece of the file - processByteChunkArray(attachmentId); - - }else{ - //If script is here something broke on the JavasSript remoting call - //Add classes to reflect error - $upload.attr('data-status','complete'); - $upload.addClass('uploadError'); - $upload.find(".statusPercent").addClass('statusPercentError'); - $upload.attr('title',event.message); - - //Check and continue the next file to upload - checkForUploads(); - } - }); + //Remove the index information from the byteChunkArray array for the piece just uploaded. + byteChunkArray.shift(); //removes 0 index + + //Set the attachmentId of the file we are now processing + attachmentId = result; + + //Call process byteChunkArray to upload the next piece of the file + processByteChunkArray(attachmentId); + }else{ - //Error handling for bad read - alert('Could not read file'); + //If script is here something broke on the JavasSript remoting call + //Add classes to reflect error + $upload.attr('data-status','complete'); + $upload.addClass('uploadError'); + $upload.find(".statusPercent").addClass('statusPercentError'); + $upload.attr('title',event.message); + + //Check and continue the next file to upload + checkForUploads(); } - }; - + }); }else{ - //This file has completed, all byte chunks have been uploaded, set status on the div to complete - $upload.attr('data-status','complete'); - - //Change name of file to link of uploaded attachment - $upload.find(".name").html(''+currentFile.name+''); - - //Call the checkForUploads to find the next upload div that has data-status="incomplete" and start the upload process. - checkForUploads(); + //Error handling for bad read + alert('Could not read file'); } - } \ No newline at end of file + }; + +}else{ + //This file has completed, all byte chunks have been uploaded, set status on the div to complete + $upload.attr('data-status','complete'); + + //Change name of file to link of uploaded attachment + $upload.find(".name").html(''+currentFile.name+''); + + //Call the checkForUploads to find the next upload div that has data-status="incomplete" and start the upload process. + checkForUploads(); +} +}