An Asynchronous Image Uploading Interface for Svbtle

For the past few months, I've been using Svbtle, Dustin Curtis' blogging platform. The key theme running throughout the service is minimalism, and if Svbtle is designed correctly, then the interface should fade away. Content is everything. However, Svbtle originally lacked a photo uploading interface, and Dustin was hesitant to add one that didn't fit with the design philosophy. So I wrote one that is asynchronous and adds no visual UI.

Svbtle’s editing interface is incredibly straightforward; all you see is the text you’re crafting. Articles are written in Markdown, so no formatting controls or complex WYSIWYG editors need to be involved. It’s the simplest possible thing.

However, this makes designing an image upload interface tricky. Where should the browse button be, the file dialog and the upload progress? How could this feature be implemented without cluttering up the interface?

When I was looking at the problem, I decided to take a step back and think about what I'd want from an end user perspective. One of the worst experiences is browsing for a file, and waiting for it to be uploaded. I wanted to avoid that at all cost. Ultimately, as a writer, I just want to drag an image onto the text and have everything else handled behind the scenes.

Fortunately, in modern browsers, it's incredibly easy to do something like that using HTML5. First we just need to listen to drop events when files are dragged onto the browser. Then we can upload them in the background using Ajax.

The Implementation

Let's have a look at the actual implementation in JavaScript. Before we can even bind to drop events, we need to cancel the dragenter, dragover and dragleave events (I won't even try to explain why the spec requires that). To do that I've written a simple plugin for jQuery, called jquery.drop.js.

Next we need to iterate over the dataTransfer.files array, calling our upload function.

$('body').dropArea();

$('body').bind('drop', function(e){
  e.preventDefault();
  e = e.originalEvent;

  var files = e.dataTransfer.files;

  for (var i=0; i < files.length; i++) {
    // Only upload images
    if (/image/.test(files[i].type)) {
      createAttachment(files[i]);
    }
  };
});

So far so good. Now we actually need to implement the createAttachment() function. The secret sauce in this function is generating the client-side GUID. This will be sent along with the upload to the server, stored in the database, and used whenever we need to generate a URL to the upload.

The fact that we're generating a GUID on the client-side means we can use it immediately to generate a URL for that image, inserting the markdown tags straight into the editor. We don't have to wait for the upload to finish to retrieve the database-generated ID. To the user, it seems like the image has been uploaded instantly, and it doesn't block their writing.

Next we're using the little known FormData constructor here. This lets us do multipart uploads without having to deal with boundaries and binary encoding, it'll do all of that behind the scenes. For this to work seamlessly with jQuery, we need to disable the contentType and processData options.

var createAttachment = function(file) {
  var uid  = [App.username, (new Date).getTime(), 'raw'].join('-');

  var data = new FormData();

  data.append('attachment[name]', file.name);
  data.append('attachment[file]', file);
  data.append('attachment[uid]',  uid);

  $.ajax({
    url: '/attachments',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    type: 'POST',
  }).error(function(){
    // ...
  });

  var absText = '![' file.name + '](/attachments/' + uid + ')';
  $('.new_post_area').insertAtCaret(absText);
};

Finally we're inserting the image markdown tags into the page using the insertAtCaret() function. This too is a custom jQuery plugin called jquery.insert.js. It'll make sure that any text inserted is placed after the blinking caret.

So that’s how we implemented image uploading on Svbtle. It contains no additional visual interface, and it functions completely asynchronously, without blocking the writer. It works like a charm; you simply drag the image onto the page and then continue on writing. The file is uploaded in the background, and by the time the article is written, all the transfers will have finished.