Learn how to drag, drop & chart a file using HTML5


This is going to be an in depth tutorial teaching you how to build a complex web application using html5, CSS3 and Javascript. It is geared towards people with an intermediate level of experience with web technology and should take 2-3 hours to complete. The end goal is to get a webpage where a user can drag and drop a csv file onto it and the page reads the contents of the file, loads it into a chart and prints the chart onto the page.

Beginning Files

Click the button above to download all required libraries including Modernizr for detecting browser support, highcharts for creating graphs, normalize to reset CSS elements and some images used for backgrounds. Once downloaded and unzipped you need to add a file and call it index.html. Add the following code to the file:


<!DOCTYPE html>
<html>
	<head>
		<title>File Uploader</title>
	</head>
	
	<body>
		<header>
			<h1 class="title">Upload a File - Get a Graph!</h1>
		</header>
		
		<aside>
			<div id="file-data"></div>
			<div id="dropbox">
				<span id="message">drag & drop here</span>
			</div>
		</aside>
		
		<content>
			<div id="container"></div>
		</content>
		
		<footer>
			<div class="copyright">This HTML5 demo was created by Alex Thorpe</div>
		</footer>
	</body>
</html>

This is a very simple html document with a title, content section where we will load the graph, sidebar where we will have the upload box and a footer for the copyright area. Once the basic file structure is in place we need to check if all the new HTML api’s we are using are supported by the browser. To handle this we will use a great javascript plugin called modernizr. It has the ability to check the current browser and load different files if it sees the api’s are not fully supported. For this tutorial the only api we need to worry about is the filereader because it is only supported by firefox and chrome (safari 6 and IE 10 will both have support later this year. Include the following code right before the closing body tag:

<script type="text/javascript" src="http://cdn.hosting4real.com/js/modernizr/modernizr.js"></script><script type="text/javascript" src="js/modernizr.file-api.js"></script>
<script type="text/javascript">
	Modernizr.load({
	  test: Modernizr.filereader,
	  yep: ['js/highcharts.js', 'js/modules/exporting.js', 'js/html5demo.js'],
	  nope: ['js/nosupport.js']
	});
</script>

Now will walk through each line and explain what this does.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.hosting4real.com/js/modernizr/modernizr.js"></script>
<script type="text/javascript" src="js/modernizr.file-api.js"></script>

This loads the jquery library, the core modernizr plugin along with an extension to check for the file-api.

Modernizr.load({
  test: Modernizr.filereader,
  yep: ['js/highcharts.js', 'js/modules/exporting.js', 'js/html5demo.js'],
  nope: ['js/nosupport.js']
});

There are 3 parts to a check done by modernizr. First is a test for a specific api, in our case the filereader api. If support is detected the yep statement tells the browser what file to load, if there is no support the nope statement allows you to load other files.

Now that we have detected what files to load we need to create those files. Inside the js folder make a new file and name it nosupport.js. This is going to be a simple javascript file that tells the user there current browser is not supported and they should try a different one. Add the following lines of code to the newly created nosupport file.

var container = $('#container');

container.innerHTML = '</pre>
<h1 class="no-support">Your browser does not support FileReader. Try using <a href="http://www.firefox.com"> Firefox</a> or <a href="http://www.google.com/chrome">Chrome</a></h1>
<pre>
';

You can now close this file and create a new one in the same js folder called html5demo.js. This file all of the magic is going to happen. Begin by adding the following lines of code to the html5demo.js file:

//set the DOM element of dropbox as a variable
var dropbox = document.getElementById("dropbox");

//Listen for the drag & drop events
dropbox.addEventListener("drop", drop, false);

This code looks cryptic but it is rather simple once you know what you are looking at. We set the HTML element with an id of dropbox from our index.html file as a variable and then we “listen” for something to happen to that element. When the event is detected the function declared is executed. The final paramater is whether to bubble up or capture the event. Don’t know what that means? Then read this explination and come back to this tutorial when you are done. For this tutorial we are going to chose bubble so use false. There are several other events that you can register with the drag and drop api listed here.

//event handler parts
element.addEventListener("event", function, type);

Next we need to write the drop function. Add the following code after the event handlers:

//When File is dropped on zone do the following
function drop(event) {

	//stop moving through DOM
	event.stopPropagation();

	//Prevent the defualt action
	event.preventDefault();

	//Register the data transfer event
	var files = event.dataTransfer.files;

	//Record number of files being uploaded
	var number = files.length;

	//Only trigger parseFiles function if at least one file uploaded
	if (number > 0) {
		parseFiles(files);
	}
}

This function is how we tell if a file has been dropped onto the dropbox element.

//stop moving through DOM
event.stopPropagation();

//Prevent the default action
event.preventDefault();

These two lines from the function stop any default actions otherwise used on the page. To better understand the stopPropagation and preventDefault commands I suggest you read this. The 2 commands are not truly necessary for this application but if you are building a more advanced application they will be so I included them to make this code as useful as possible.

//Registers the files as an array
var files = event.dataTransfer.files;

This line records each file as an element in an array called files. Then we create a new variable to record the number of files uploaded

//Record number of files being uploaded
var number = files.length;

The last part only calls the parseFiles function if there was at least 1 file uploaded.

//Only trigger parseFiles function if at least one file uploaded
if (number > 0) {
	parseFiles(files);
}

After the drop function we need to create another function named parseFiles that will be responsible for selecting the file and reading all the meta information.

//Process the uploaded file
function parseFiles(files) {

	//Register the first file uploaded
	var file = files[0];

        //set the DOM element of file-data to a variable
  	var fileData = document.getElementById("file-data");

  	//Add file information to the HTML page
        var output = "
file: <span class="meta">" + file.name + "</span>

";
    	output += "
last update: <span class="meta">" + file.lastModifiedDate + "</span>

";
		output += "
type: <span class="meta">" + file.type + "</span>

";
		output += "
size: <span class="meta">" + file.size + "</span> bytes

";

	fileData.innerHTML = output;

	//Open FileReader to read content
	var reader = new FileReader();

	// begin the read operation
	reader.readAsText(file, "UTF-8");

	// init the reader event handlers
	reader.onload = handleReaderLoad;

}

The first 2 statements create 2 new variables:

//Register the first file uploaded
var file = files[0];

//set the DOM element of file-data to a variable
var fileData = document.getElementById("file-data");

The first records the first document saved in the files array, for this tutorial we only care about the first uploaded file but you could easily add a loop to read multiple files. The second is setting the div with an id of file-data as a new object so we can add information to it.

//Add file information to the HTML page
    var output = "
file: <span class="meta">" + file.name + "</span>

";
    	output += "
last update: <span class="meta">" + file.lastModifiedDate + "</span>

";
	output += "
type: <span class="meta">" + file.type + "</span>

";
	output += "
size: <span class="meta">" + file.size + "</span> bytes

";

This section takes all the metadata from the file such as name, date last modified, type and size and adds it to a new variable named output along with some html tags to help us style the information later. Then we add the output contents of the output variable to the inside of the file-data div:

fileData.innerHTML = output;

Next we create a new instance of the filereader, tell the browser to read the contents of the file as unicode data and once the file has been loader start the handleReaderLoad function.

//Open FileReader to read content
var reader = new FileReader();

// begin the read operation
reader.readAsText(file, "UTF-8");

// init the reader event handlers
reader.onload = handleReaderLoad;

After the parseFiles function add the following:

function handleReaderLoad(event, file) {

  	//Set csv variable equal to file contents
  	csv = event.target.result;

  	//Run CSV parsing function
  	processData();
}

We are using a new variable call csv to store the inforamtion read from the file but we have not yet declared it. Because we need to use it several times in the document we should create it as a global variable. You do this by adding the following lines right bellow the var dropbox line at the top of the page.

//create new global variable
var csv;

The processData function is the most complicated part of this app but it is also the most powerful. It allows you to read through a csv file and create seperate objects for each piece of data. In our app we will take this and chart it directly but you could also use this function to load a huge excell file into a database for processing or more complex data manipulations. The function should go bellow the handleReaderLoad function:

function processData() {

    //Seperate file by line breaks
    var allTextLines = csv.split(/\r\n|\n/);

    //Set Key equal to the first row
    var headers = allTextLines[0].split(',');

    //loop through each line
    for (var i=1; i
    	//Seperate line by comma
        var data = allTextLines[i].split(',');

        //Create associated array for each line
        if (data.length == headers.length) {

	    //Declare temp array
            var array = {};

            //Loop through values
            for (var j=0; j
            	//Set header to Key
            	var key = headers[j];

            	//Set data to value
            	var value = data[j];

            	//Add Key Value pair to temp array
            	array[key] = value;
            }

            //Add temp line array to array of all data
            lines.push(array);
        }
    }

    //Convert data to graphical form
    renderChart();
}

The first line seperates each line of the csv file and places it into an array called allTextLines. It looks like this:

allTextLines = ["Name,Fall 2011,Winter 2012", "john,3.2,3.4", ...];

The next line

//Set Key equal to the first row
var headers = allTextLines[0].split(',');

Splits the first row of data into a new array by commas. This are the categories associated with the csv file:

var headers = ["Name", "Fall 2011", "Winter 2012"];
//loop through each line
for (var i=1; i
    //Seperate line by comma
    var data = allTextLines[i].split(',');

    //Create associated array for each line
    if (data.length == headers.length) {

        //Declare temp array
        var array = {};

        //Loop through values
        for (var j=0; j
        	//Set header to Key
        	var key = headers[j];

        	//Set data to value
        	var value = data[j];

        	//Add Key Value pair to temp array
        	array[key] = value;
        }

        //Add temp line array to array of all data
        lines.push(array);
    }
}

We set up a loop to start at the second row of the allTextLines array (we already used row 1 for the headers) and parse each string separately.

//Seperate line by comma
var data = allTextLines[i].split(',');

There is a new temporary array created called data to hold all the values from the split.

//Create associated array for each line
if (data.length == headers.length) {

	//Declare temp array
    var array = {};

    //Loop through values
    for (var j=0; j
    	//Set header to Key
    	var key = headers[j];

    	//Set data to value
    	var value = data[j];

    	//Add Key Value pair to temp array
    	array[key] = value;
    }

    //Add temp line array to array of all data
    lines.push(array);
}

This loop checks if the number of data points is equal to the number of categories and then adds the category name and data point to an object array as a key value pair:

//example of the object array
array = {
   name: john,
   fall 2011: 3.2.
   winter 2012: 3.4
}

After all values have been added for the record the object array is added to an array of arrays called lines. We have not created the lines array yet so we should do that now. Add the following code to the top of the page right bellow the var csv; line.

//Create new global array for parsed csv data
var lines = [];

The final command calls the function that will create the chart using the data we just loaded. Add the following function after proccessData.

function renderChart() {

	var options = {
	    chart: {
	        renderTo: 'container',
	        defaultSeriesType: 'line'
	    },
	    title: {
	        text: 'Grades'
	    },
	    xAxis: {
	        categories: []
	    },
	    yAxis: {
	        title: {
	            text: 'GPA'
	        }
	    },
	    series: [{
			name: 'Fall 2011',
			data: []
		}, {
			name: 'Winter 2012',
			data: []
		}],
	    exporting: {
            enabled: true
        }
	};

	//Add object values from csv to the chart
	for(i = 0; i < lines.length; i++) {
		var obj = lines[i];

		for(var index in obj) {
			if (index === 'Name') {
				options.xAxis.categories.push(obj[index]);
			}

			if (index === 'Fall 2011') {
				options.series[0].data.push(parseFloat(obj[index]));
			}

			if (index === 'Winter 2012') {
				options.series[1].data.push(parseFloat(obj[index]));
			}
		}
	}

	//Create the chart
	var chart = new Highcharts.Chart(options);
}

The code for creating the chart comes from the highcharts website. They do a great job explaining there system in the documentation on there site here. The values from the object arrays in the lines array are added based on the category each row belongs too. For this tutorial the categories are hard coded but in a real app you would want to make them more dynamic.

To style the application add a new file in the css folder with the following code:

@import url('normalize.css');
@import url(http://fonts.googleapis.com/css?family=Droid+Serif);

body {
	background: url(images/arches.png);
	font-size: 12px;
	font-weight: 400;
}

header {
	width: 100%;
	display: block;
	background: #292a2d; /* Old browsers */
	background: -moz-linear-gradient(top,  #292a2d 0%, #000000 100%); /* FF3.6+ */
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#292a2d), color-stop(100%,#000000)); /* Chrome,Safari4+ */
	background: -webkit-linear-gradient(top,  #292a2d 0%,#000000 100%); /* Chrome10+,Safari5.1+ */
	background: -o-linear-gradient(top,  #292a2d 0%,#000000 100%); /* Opera 11.10+ */
	background: -ms-linear-gradient(top,  #292a2d 0%,#000000 100%); /* IE10+ */
	background: linear-gradient(top,  #292a2d 0%,#000000 100%); /* W3C */
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#292a2d', endColorstr='#000000',GradientType=0 ); /* IE6-9 */
	color: #fff;
	border-bottom: 2px solid #f8f8f8;
	-webkit-box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .5);
	-moz-box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .5);
	box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .5);
}

header h1.title {
	font-family: 'Droid Serif', serif;
	font-size: 2em;
	line-height: 1.5em;
	padding: 5px 15px;
}

h1.no-support {
	font-size: 24px;
}

#fileToUpload {
	position: absolute;
	right: 30px;
	bottom: 140px;
	padding: 5px;
	margin: 0px;
	width: 170px;
}

#dropbox {
	width: 180px;
	height: 110px;
	background: url(images/download.png) no-repeat center center;
	background-color: #454545;
	position: absolute;
	right: 30px;
	bottom: 20px;
	-webkit-border-radius: 3px;
	-moz-border-radius: 3px;
	border-radius: 3px;
	-webkit-box-shadow: inset 1px 2px 8px 1px rgba(0, 0, 0, .4);
	-moz-box-shadow: inset 1px 2px 8px 1px rgba(0, 0, 0, .4);
	box-shadow: inset 1px 2px 8px 1px rgba(0, 0, 0, .4);
}

#message {
	color: #F8F8F8;
	font-size: 1.2em;
	bottom: 5px;
	display: block;
	text-align: center;
	margin-top: 73px;
}

#container {
	margin: 50px 0 0 50px;
	width: 700px;
	height: 350px;
}

#file-data {
	font-family: 'Droid Serif', serif;
	font-size: 1.25em;
	line-height: 1.5em;
	right: 30px;
	top: 100px;
	width: 180px;
	position: absolute;
}

#file-data span.meta {
	color: #308FD9;
}

.copyright {
	position: absolute;
	left: 20px;
	bottom: 15px;
	font-weight: bold;
	color: #454545;
}

.highcharts-container {
	-webkit-box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, .5);
	-moz-box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, .5);
	box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, .5);
}

I am not going to go over the styling I used in this tutorial but please look through it and try to understand what each part is doing.

Finally we need to add a reference to the css file in the header of index.html.

<link rel="stylesheet" type="text/css" href="css/style.css" />

To run the application you have to upload it to a hosted server. The file api does not work locally for security reasons. Once you have the web app deployed you can use the included csv file to test out if it works. The next steps you should take are to try and figure out how to make the charted categories dynamic rather than hard coded.

You now have a web application that can take a csv file that is dropped onto the page, read it and display the information as a graph without have to refresh or talk to a server. You can view the commented source code on github or check out the completed demo bellow. I hope you learned some new techniques while reading this tutorial and if you have any questions or improvements please leave them in the comments.

Source Files Demo

One Comment on "Learn how to drag, drop & chart a file using HTML5"

  1. Bryan says:

    How can I change this to allow for the app to detect a file set in a network directory? So the app will graph automatically anything listed in the directory either as an addition to the drag and drop or a replacement of the drag and drop.

Got something to say? Go for it!

 
Read previous post:
Mobile Device Policy & Development in Enterprise

As mobile devices become more powerful and ubiquitous in the workplace companies need to begin supporting and developing for these...

Close