12Jun

Bog-stupid Ajax round trips with Prototype and Django

Posted by Elf Sternberg as django, javascript, programming

Introduction

When you roll around the web you see lots of web applications that show off some localized capability to get data from a back-end in a semi-real-time manner. The most famous is the “search suggestion” drop-down that comes off Google or Bing, and those are wonderful, but often the Javascript for the drop down and handling the div and layer and all that stuff obscures one interesting technology web developers have to master: the round-trip: as events (keypress, mousemove, timeout) happen within the browser’s javascript virtual machine, something is sent via AJAX to the server, and something is then sent back (usually via JSON) to the browser.

So I’m going to tell you how to do a round-trip. We’ll be using Django and Prototype, but this technique works with all application servers and javascript frameworks.

Setting up Django

The first thing you need to do is update settings.py. Here are the changes you make. You’ll note that we’re not doing anything with the database. We are, however, setting up a media directory for static service. You will also note the STATIC_DIR_ROOT trick; please do not deploy with that active. Use it only for development.

<settings.py>=
import os
DIRNAME = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
STATIC_DOC_ROOT = '%s/' % os.path.normpath(os.path.join(DIRNAME, 'media'))
TEMPLATE_DIRS = (
    '%s/' % os.path.normpath(os.path.join(DIRNAME, 'templates')),
)
ROOT_URLCONF = 'roundtrip.urls'

And make these changes to urls.py. There are a lot of tricks here, including the direct_to_template and static_serve methods.

<urls.py>=
from django.views.generic.simple import direct_to_template
from django.views.static import serve as static_serve
from settings import STATIC_DOC_ROOT
from rtdemo import keypress

urlpatterns = patterns('',
    url(r'^/$', direct_to_template, {template: 'page.html'}),
    url(r'^keypress/$', keypress),
    url(r'^static/(?P<path>.*)$', static_serve,
                {'document_root': STATIC_DOC_ROOT, 'show_indexes': True}),
)

The container page

Now, you need page.html. This is going to be our raw display page, and we’re not going to do anything miraculous at all. What we will be doing is importing two pieces of javascript: prototype.js and our own code, roundtrip.js. Roundtrip will contain the heart of our code.

This page also contains a textarea for the input area and a div for output area. I’ve embedded the styles just to reduce the number of files this examples needs, but in practice don’t embed styles; use style sheets. This page is utterly unremarkable, and without almost any styling at all otherwise.

<page.html>=
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=us-ascii" />
  <link rel="stylesheet" href="/static/style.css" type="text/css" />
  <title>Immunity Inc</title>
</head>
<body>

<textarea id="inputarea" style="width: 30em; height: 12ex"></textarea>

<div id="outputarea"
   style="width: 30em; height: 12ex; padding: 1em; border: 1px solid green;">

</div>

<script
  type="text/javascript"
  src="/static/prototype.js">
</script>
<script
  type="text/javascript"
  src="/static/roundtrip.js">
</script>
</body>
</html>

You might have noticed that I put the javascript at the bottom.  This is becoming a pretty standard way of doing stuff; it ensures that all of the HTML content is delivered to the browser before some potentially pipe-clogging and page-wrecking javascript starts coming down.  It also delays all javascript processing until the DOM content is fully loaded.   Most modern Javascript ibraries have an ‘onload’ option to delay loading until the DOM is completely ready, but this helps ensure that delayed processing.

The Protoype part

The real magic here is in the javascript, so let’s get started. We’re going to use Prototype’s class handler, but you could do this with an ordinary javascript prototype handler.

<roundtrip.js>=
var Roundtrip = Class.create({
  initialize: function() {
        this.source = $('inputarea');
        this.target = $('outputarea');
        Event.observe(this.source, 'keypress',
                      this.updateoutput.bindAsEventListener(this));
  },

When this object is instantiated, the object now has handles to both our input and output areas, and expects a function called updateoutput that will, well, do the magic. Let’s describe that:

<roundtrip.js>+=
  updateoutput: function(e) {
     var payload = ('content=' + encodeURIComponent(this.source.value));
     new Ajax.Request('/keypress/',
                     { method: 'post',
                       postBody: payload,
                       onSuccess: this.show.bind(this)});
  },

This function reads the source object’s contents, bundles it into a key/value pair ready to send to the server, and then calls Ajax.Request(). The magic here is that we’re now expecting another new method, show(), that will handle what the server says.  By setting the method, I ensure where Django will store the content.

<roundtrip.js>+=

  show: function(response) {
    var resp = eval('(' + response.responseText + ')');
    this.target.innerHTML = resp.content;
  }
});

And that’s it. We’re gonna send the contents of the textarea to the server where it’ll do something, send back an HTML payload as a response, and show that response in the target div.

Now we have to set up this object:

<roundtrip.js>+=
Event.observe(window, 'load', function() {
    var a = new Roundtrip();
}, false);

Nothing remarkable about that.

The Django piece

The only other piece is the response. Up above we’ve defined the script named rtdemo.py, and a method keypress. Our demonstration will, for every keypress, analyze the whole content and return it backwards.

<rtdemo.py>=
from django.http import HttpResponse
from django.utils import simplejson

def keypress(request):
    content = request.POST['content']
    content = content[::-1].replace('\n', '<br>\n')
    return HttpResponse(simplejson.dumps({'content': content}))

And that’s it. Now we have the whole circuit: draw the page that the user will see, fill in the javascript that connects the input area to an event handler (keypress) that sends the content to the server, and connect another event handler (end ajax transaction) that takes the response and fills in the output area, and the server-side handler.

There are lots of things you can do with this technique. And there are lots of improvements that can be made on it: many of the “drop down” handlers will not send every keypress, but only after a suitable amount of time has passed since the last transaction, to prevent overloading the server. This example sends the whole content, but do you really need to send all of it?

Endnote: this example was developed with the excellent Noweb Literate Programming Toolkit, which made writing this all up much, much easier.

Comment Form

Subscribe to Feed

Categories

Calendar

June 2009
M T W T F S S
« May   Jul »
1234567
891011121314
15161718192021
22232425262728
2930