Dugan Chen's Homepage

Various Things

Drag and Drop Uploading with HTML 5 and CoffeeScript

About half a year ago, I blogged about Progressively Enhancing Upload Forms. Here’s another implementation.

This time, I’m going to use CoffeeScript. With CoffeeScript, you get for free what you would otherwise have to struggle for if you try to follow best practices and make everything pass jslint. It makes easy what is often difficult: writing Javascript elegantly. I now see no reason to ever not use it.

(While most articles have compared CoffeeScript to Python and Ruby, it is just as much influenced by Haskell, particularly in its function call and function definition syntax).

As this is a demo, I’m also going to assume a modern browser. No shims, shivs, polyfills, progressive enhancements, or graceful degradation. With this in mind, the simplest implementation actually uses no Javascript frameworks or libraries. All I need is the standard DOM API.

The server-side framework I’ll use for this demo is Django. Why not?

I begin with a Django template:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Drag n Drop Test</title>
	</head>
	<body>
		<h1>Drag n Drop Test</h1>
		<form method="POST"
			action="{% url demo.views.home %}">
			{% csrf_token %}
			<p>Drag text file here</p>
		</form>
		<pre></pre>
		<script src="{{STATIC_URL}}demo.js"></script>
	</body>
</html>

The form (which has Django’s standard CSRF protection) accepts a (text) file dropped onto it. Below the form, is a preformatted area to show the content of that file.

If the dropped file is a text file, our coffeescript will AJAX-POST it to the server, then load the response into the preformatted area:

form = document.querySelector 'form'

form.addEventListener 'dragenter', (e) ->
	e.stopPropagation()
	e.preventDefault()

form.addEventListener 'dragover', (e) ->
	e.stopPropagation()
	e.preventDefault()

form.addEventListener 'drop', (e) ->
	data = new FormData form
	xhr = new XMLHttpRequest()
	file = e.dataTransfer.files[0]

	if file.type is 'text/plain'

		xhr.addEventListener 'load', (e) ->
			upload = document.querySelector 'pre'
			upload.innerHTML = xhr.responseText

		xhr.open 'POST', '/'
		data.append 'file', file
		xhr.send data
	
	e.preventDefault()

The Django view (what other frameworks call a controller) is simple. For a GET, it serves the form. For a POST, it returns the content of the uploaded file:

from django.shortcuts import render
from django.http import HttpResponse
from django.http import HttpResponseNotAllowed

def index(request):
    if request.method == 'GET':
        return render(request, 'index.html')

    if request.method == 'POST':
        text = request.FILES['file'].read()
        return HttpResponse(text)

    return HttpResponseNotAllowed(['GET', 'POST'])