Category Filtering: 'javascript'

gadget-ui.input.FileUploader example

CFML, CLI, JavaScript

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.


gadget-ui JavaScript library Updated

JavaScript

I have been working on an update of the gadget-ui JavaScript library that includes a new feature - a multi-file upload component called gadgetui.input.FileUploader. The component is based on some earlier work of mine, and originally used jQuery, so the jQuery version was written first and sticks to the original. 

The plain JavaScript version is a port of the jQuery version, and I am moving ahead with development on the plain JS version. The original component instantiated inside a dialog box with a close button, and I have eliminated those features in the plain JS version, opting to allow the developer to instantiate the component wherever needed, and managing its lifecycle as required. You can clone the library from my gadget-ui GitHub repository.


JavaScript Automation, Part III - Configuring Grunt Tasks

CLI, JavaScript

Having installed and configured Grunt in Part I and Part II of our series on automating JavaScript builds, we proceed to the heart of the matter - configuring Grunt tasks to automate away the tedium of building our JavaScript client app from source files.


JavaScript Automation, Part II - Configuring Grunt

CLI, JavaScript

Building complex JavaScript front-ends is complicated. Grunt can help you improve your code/debug lifecycle by automating a lot of repetitive tasks that otherwise take up a lot of your time. If you are not familiar with Grunt yet, read Part I - Setting up Grunt. Once you have Grunt installed in your project, proceed to the next step - Grunt configuration.


JavaScript Automation, Part I - Setting Up Grunt

CLI, JavaScript

if you work on single-page apps and other JavaScript-centric Web development, you quickly learn that the code-debug cycle in client-side JavaScript is anything but automatic. In these kinds of environments, you end up with enough code that several things happen ...


Real-time server debug info using NodeJS, jQuery, Websockets and Logbox

CFML, Java, JavaScript

Have you ever been working on a web site and wished you could get the server-side debug information without all the tedium of browsing log files or dumping debug information in the UI? Using Logbox and Websockets, you can do just that.


Custom constructors with mixins to add event bindings in JavaScript

JavaScript

Using a custom constructor with Object.create(), you can mixin methods that allow you to bind event handlers to method execution in JavaScript classes. How does that work? Read on ...


Categories


Recent Entries

Entries Search