MOKUJI—collection of
notes by Zac Fukuda

Drop & Upload File Tutorial - Step 1: Pure Javascript

018
Jun 15, 2017

In next three articles, I’m going to show you how to create a Drop & Upload application in native Javascript. The goal of these articles is to get you familiar with FileReader API and React in the syntax of ES2015. Each step of the articles covers:

  • Step 1: Drag and Drop with FileReader in native Javascript
    Before we dive into building a React application, first let us understand how the basic Drag & Drop API works.
  • Step 2: Save files on the server with Ajax & Express.js
    Using Express, we make the app able to save the dropped images on the server. We will send the data of images to the server through XML HttpRequest.
  • Step 3: React-ify with Babel & webpack
    To make it more adaptable to the modern web development, we will turn the app from Step 2 into a React app.

This is for Someone Who is…

This step-by-step tutorials are intended for someone who already knows the basics of Javascript but not how to build the modern Javascript application. When I say the modern Javascript I mean Node.js, React, Angular, webpack, and so on.

If you are absolutely new to the programming or Javascript, you might find it difficult to understand. But feel free to take a look at the article and see what’s lying ahead of you. If you are already an expert in building Javascript app, you might find it pointless.

In another words, this tutorials are for someone like me. Someone who used to write a jQuery to build a informative website but has less experience and knowledge in Javascript as for the both front and back side.

Get the Code

The source codes being used in each step are available on Github.
Or just simply:

$ git clone https://github.com/zacfukuda/react-drop-upload-app.git

The codes of Step 1 is located in step1-html folder.

Before We Get Started

The code being used here is mostly based on Reading files in JavaScript using the File APIs from HTML5 Rocks. I put each of code into one file eliminating the unnecessary parts and add some modifications in order to minimize editing when we make it Ajax and React application in the next following two steps.

The end purpose of this tutorial is to get you familiar with React. So I don’t go through all codes and don’t examine closely. Please check out the HTML5 Rocks’ post if you want to know how FileReader works.

Now Get Started

The file structure of Step 1 is as follows:

.
└ index.html
└ dropUploadFile.js
└ style.css

Now first create an index html. It looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Drop & Upload Files</title>
	<link rel="stylesheet" href="./style.css">
</head>
<body>

<div class="wrapper">
	<h1>Drop & Upload Files</h1>
	<div class="dropZoneBox">
		<div id="dropZone">Drop files here to upload</div>
		<h6>Upload Status</h6>
		<table id="progressTable">
			<tr>
				<th><b><span id="progress">000</span>%</b></th>
				<td><div class="progressBar"><div id="progressBar"></div></div><button id="abortButton" onclick="abortRead();"><span></span></button></td>
			</tr>
		</table>
	</div><!-- .dropZoneContainer -->
	<output id="output"></output>
</div><!-- .wrapper -->

<script src="./dropUploadFile.js"></script>
</body>
</html>

For style.css, please refer to the file on Github.

Now dropUploadFile.js. This is the file in which the whole application logic is written. Just like mentioned above, the code of dropUploadFile.js is based on HTML5 Rocks. I don’t want to repeat what’s written in there. So I give you the complete code.

dropUploadFile.js
(function () {
	'use strict';

	// Check for the various File API support.
	if (window.File && window.FileReader && window.FileList && window.Blob)
		console.log('OK');
	else
		alert('The File APIs are not fully supported in this browser.');

	var reader, files;
	var dropZone = document.getElementById('dropZone'),
			progress = document.getElementById('progress'),
			progressBar = document.getElementById('progressBar'),
			outputTag = document.getElementById('output');

	/**
	 * Event handlers for ReadFile.
	 */

	// Stop reading files.
	function abortRead() {
		reader.abort();
	}

	// FileReader abort Handler
	function abortHandler(e) {
		alert('File read Canceled');
	}

	// FileReader Error Handler
	function errorHandler (e) {
		switch(e.target.error.code) {
			case e.target.error.NOT_FOUND_ERR:
				alert('File Not Found!');
				break;
			case e.target.error.NOT_READABLE_ERR:
				alert('File is not readable');
				break;
			case e.target.error.ABORT_ERR:
				break; // noop
			default:
				alert('An error occurred reading this file.');
		}
	}

	// Display the progress of FileReading.
	function progressHandler(e) {
		if (e.lengthComputable) {
			var loaded = Math.round((e.loaded / e.total) * 100);
			var zeros = '';
			
			// Percent Loaded in string
			if (loaded >= 0 && loaded < 10) zeros = '00';
			else if (loaded < 100) zeros = '0';

			// Display progress in 3-digit and increase the bar length.
			progress.textContent = zeros + loaded.toString();
			progressBar.style.width = loaded + '%';

		}
	}

	// Event after loading a file completed (Append thumbnail.)
	function loadHandler(theFile) {

		return function(e) {
			var newFile = document.createElement('div');
			var picture = document.createElement('picture');
			var img = document.createElement('div');
			img.style.backgroundImage = 'url(' + e.target.result + ')';
			img.title = escape(theFile.name);
			img.className = 'thumb';

			picture.appendChild(img);
			newFile.appendChild(picture);
			newFile.className = 'file';

			outputTag.insertBefore(newFile, null);
		}
	}

	// Main function for ReadFile and appending thumbnails.
	function appendThumbnail(f) {
		reader = new FileReader();
		reader.onerror = errorHandler;
		reader.onabort = abortHandler;
		reader.onprogress = progressHandler;
		reader.onload = loadHandler(f);
		reader.readAsDataURL(f);
	}

	/**
	 * Main Event Handler to deal with
	 * the whole drop & upload process.
	 */
	function handleFileSelect(e) {
		e.stopPropagation();
		e.preventDefault();

		dropZone.classList.remove('dragover');
		progress.textContent = '000';
		progressBar.style.width = '0%';

		files = e.dataTransfer.files; // FileList object.
		
		// Go through each file.
		for (var i=0, f; f=files[i]; i++) {

			// Only process image files.
			if ( !f.type.match('image.*') ) continue;
			appendThumbnail(f);

		} // END for

	} // END handleFileSelect

	/**
	 * functions associating "drag" event.
	 */
	function handleDragEnter (e) {
		e.stopPropagation();
		e.preventDefault();
		this.classList.add('dragover');
	}
	function handleDragLeave (e) {
		e.stopPropagation();
		e.preventDefault();
		this.classList.remove('dragover');
	}
	function handleDragOver (e) {
		e.stopPropagation();
		e.preventDefault();
		e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
	}

	/**
	 * Setup the event listeners.
	 */
	dropZone.addEventListener('dragenter', handleDragEnter, false)
	dropZone.addEventListener('dragleave', handleDragLeave, false)
	dropZone.addEventListener('dragover', handleDragOver, false);
	dropZone.addEventListener('drop', handleFileSelect, false);
})();

How is it Different from HTML5 Rocks

More element “id”s

We are going to create React components in Step 3. So I added ids to each important element, and made it easier for them to associate with the functions they’re assigned to.

Drag & Drop over <input type="file">

We—or I—want rich a user interface. We don’t want users to browse around their computers in the file-select window. Why not drag & drop—although I assume it is more likely to cause troubles. This article is for learning. Not for using it.

The createElement(), appendChild(), insertBefore()

In HTML5 Rocks, the thumbnail or the file name are designed to be inserted by writing HTML texts using innerHTML. But I use createElement(), appendChild(), and insertBefore() because the thumbnail will be <Thumbnail /> React component. Plus, this also makes things easier in Step 3 building the React app.

The “dragenter” and “dragleave” events

Since we use the drag & drop interfaces, two extra events are added to show users that they’re dragging a file. The similar thing can be done with CSS :hover selector. But I decided to use the Javascript event just to make it a little bit cooler.

Next: Ajax with Express

The Drop & Upload application above is a simple HTML application. Which means that you don’t have to run a server to see if the code is working or not. This is good when you test a piece of code that doesn’t require a server-side functionality. But we want to upload the dropped files to the server, then save them. What I just showed you is not a Drop & Upload application, but a Drop application. In order to save the files, we need a server up-running. That’s what we do in the next step.

We are going to use Express to run our small application, and let it handle a POST request sent by a client through XML HttpRequest a.k.a. Ajax.

Now move on to the next step Ajax with Express.js.
Or jump into the last step React-ify with Babel & webpack.