gadget-ui.input.FileUploader example
I'm going to demonstrate how to use the new gadget-ui.input.FileUploader component in the gadget-ui library. First, we need an HTML page to host the component. This code comes directly from the /test folder in the gadget-ui repo:
fileuploader.htm
<!doctype html> <html> <head> <title>gadget-ui File Uploader Test</title> <script src='../bower_components/modlazy/dist/modlazy.min.js'></script> <script> modlazy.load( [ "fileuploader.js < ../dist/gadget-ui.js", "../dist/gadget-ui.css" ], function() { console.log("All files have been loaded"); } ); </script> <style> body { font-size: 1em; } input, select, select option { font-size: 1em; } #fileUploadDiv { height: 500px; width: 500px; text-align: center; margin-top: auto; margin-bottom: auto; } </style> </head> <body> <p>Test the FileUploader control.</p> <div id="fileUploadDiv"></div> </body> </html>
I am using a small dependency loader called modlazy to load my assets. This page shows a div called fileUploadDiv that will host the component. Let's look at the fileuploader.js file:
fileuploader.js
var options = { uploadURI: "/test/fileuploader.upload.cfm", tags: "file upload", willGenerateThumbnails: true, title: "Upload Files", showUploadButton: false }; filedialog = gadgetui.objects.Constructor( gadgetui.input.FileUploader, [ document.querySelector("#fileUploadDiv"), options ]);
The uploadURI specifies a CFML page that should be compatible with Lucee and Adobe ColdFusion, although I am testing it with Lucee, so there could be bugs in the ACF implementation right now. We'll dig into that page in a minute.
Right now, what I want to show is how the component is being instantiated. Rather than use the new keyword, I have ported all of he gadget-ui code to use the built-in gadgetui.objects.Constructor method, which internally uses Object.create(). I have also included a new option, showUploadButton, so you can either have a drop zone, or have a drop zone and a file upload button.
On drop or file select, the component cuts the file up into 1MB chunks and uploads the chunks one at a time. Let's have a look at the CFML receiver file:
fileuploader.upload.cfm
<cfscript> filePath = "#expandpath("./")#upload\"; separator = iif(expandpath("./") contains "/",de("/"),de("\")); filePath = expandpath("./") & "upload" & separator; chunkPath = filePath & "temp" & separator & createUUID(); inputStream = getPageContext().getRequest().getOriginalRequest().getInputStream(); ioutil = createObject( "java", "org.apache.commons.io.IOUtils" ); outputStream = createObject( "java", "java.io.FileOutputStream" ).init( chunkPath ); ioutil.copy( inputStream, outputStream ); contentLength = gethttprequestdata().headers['Content-Length']; args.id = gethttprequestdata().headers['x-id']; args.filename = gethttprequestdata().headers['x-filename']; args.filesize = gethttprequestdata().headers['x-filesize']; args.part = gethttprequestdata().headers['x-filepart']; args.parts = gethttprequestdata().headers['x-parts']; if( args.part eq 1 ){ fileId = createUUID(); }else{ fileId = args.id; } args.temp_file = chunkPath; args.id = fileId; FilePartDAO = new model.FilePartDAO(); FileService = new model.FileService( filePath = filePath, FilePartDAO = FilePartDAO ); file = FileService.upload( argumentcollection = args ); if( args.part eq args.parts ){ writeoutput( SerializeJSON( file ) ); }else{ pc = getpagecontext(); pc.setHeader("X-Id", fileId ); } </cfscript>
There is a lot going on in this code. Look at the use of Java input and output streams to copy the chunked data. I use Java classes here to guarantee good data coming through from the binary data POST. Note that I am using UUIDs to identify the chunks and store them in a temporary folder on the server, where they stay until they are re-assembled into the original file.
Also see the use of gethttprequestdata() to pull the headers for the chunked data. The x- headers are all custom headers created by the FileUploader component to track the file upload. x-filepart tells the server which part of the file is being uploaded, and x-parts tells the server how many total parts there are.
Next, note that this code takes place in a loop controlled by the FileUploader component. When the end of the loop is reached, the file upload is completed and a response is sent back to the client.
The real work of the upload is being done in the FileService CFML component. This example uses a baseline implementation that uploads the file(s) to a destination folder on the server. Other implementations may store the file in MongoDB or other NoSQL stores. I will dig into the FileService component next time.