The Artima Developer Community
Sponsored Link

Computing Thoughts
Web App with jQuery, CoffeeScript, Ajax, Web.py & Heroku
by Bruce Eckel
September 30, 2011
Summary
Following up on my previous article, I create a super-simple example where a web page communicates with the server (written in Web.py) sending a JSON request object and receiving a JSON object as a response, which it uses to dynamically update the page. Afterwards, James Ward quickly loads the example into Heroku using their new Python support.

Advertisement

Here is the previous article, showing how to use CoffeeScript and jQuery.

I chose Web.py because it's the simplest and most straightforward web framework I've found for Python. Despite that, you can do quite a bit with it. If you want to get fancy, you'd probably choose Django since it seems to have become the leading web-framework contender for Python. But for quick-and-simple, which is what I wanted for this example, it's hard to beat Web.py.

The intent of this example is to utilize high-level tools to create a very simple web app. The app will live on a single page that the Web.py server will hand you when you go to the URL. Every two seconds, this page will create a JSON object consisting of data from a text field on the page, and a randomly-generated number. The JSON object will be sent to the server as a POST request, and the server will respond by modifying the data and returning it to the page as a new JSON object. The page will take the data and update itself. Thus it makes the web page a reasonably-complete user interface for the application (this app will even automatically start up the web page, so it could be used as a desktop application).

The web page is very minimal, with a text field for input and an H1 field to be manipulated by jQuery to display the output. The page lives in the templates subdirectory and is named displaydata.html which you'll shortly see is important:

<!doctype html>
<html>
    <head>
    <title>Webpy, CoffeeScript and jQuery Ajax</title>
    <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon">
    <script type="text/javascript" src="/static/jquery.js"></script>
    <script type="text/javascript" src="/static/getdata.js"></script>
    </head>
    <body>
      <center>
      <input class="text" id="mod" size="10" style="font-size:2em"/>
      <h1 id="data"></h1>
      </center>
    </body>
</html>

Note that we include getdata.js here, which will be produced later via CoffeeScript.

(Also, this is the first time I've -- finally! -- figured out how to set up the icon, thanks to help from someone on the Web.py newsgroup. I don't know why that one is so obscure).

I'll describe the Web.py application in pieces, and then give you the whole file at the end. After the imports (note that Web.py just requires a single file), the urls describes a mapping between URLs and classes to handle those URLs. Here, we just have one:

import web, json, webbrowser, multiprocessing

urls = ( # URLs mapped to handler classes
    '/', 'home',
)

Next, we tell Web.py where the templates live in the file system. In this example, I am not doing any actual rendering, but Web.py has a nice templating system for substituting when you call a template, and you can insert Python code that gets executed as the template is rendered. This can be quite powerful, and it's nice because it's just Python so you don't have to learn yet another language for templating:

render = web.template.render('templates/')

Here's the class that handles requests to the root URL as mapped in urls. As you might expect, the GET method handles HTTP GET requests at that address, and the POST method handles the POST requests:

class home:
    def GET(self): # Show displaydata.html
        return render.displaydata()

    def POST(self):
        input = web.input()
        web.header('Content-Type', 'application/json')
        return json.dumps({ # Do trivial operations:
            'txt' : input.mod.lower(),
            'dat' : "%.3f" % float(input.num)
            })

The call to render.displaydata() goes to the templates subdirectory and finds displaydata.html, then passes it through the rendering engine (if we had given arguments, those arguments would be evaluated during rendering). In our case, we have nothing but plain HTML so no replacement occurs during rendering. Thus, all it does is return the previously-shown HTML to the browser.

The POST method is called by the page, as you'll see later. The first thing it does is unpack the data from the query by calling web.input(), and this produces the input object which contains the fields created by the web page when it makes its Ajax call. These fields are mod and lower so they can be easily accessed by saying input.mod and input.lower.

In order to return a JSON object, we must set the header on the web page to indicate this. To format the JSON object, we use Python's json.dumps() function, and hand it a structure: the txt field takes input.mod and lowercases the string, while the dat field shortens input.num to be three places beyond the decimal point. The goal here is just to do something simple to show that the server is doing a little work and returning the result.

Here's the main to start everything up:

if __name__ == '__main__':
    app = web.application(urls, globals())
    multiprocessing.Process(target=app.run).start()
    webbrowser.open_new_tab("http://0.0.0.0:8080/")

The first line is the standard way to start a Web.py application. In the second line, I use Python's multiprocessing library to run the server in the background, which then allows me to automatically open a browser window pointing at the URL via Python's webbrowser library. This way, you can create an application that the user can start up by double-clicking an icon, and the UI will appear as a page in the user's browser.

(As an alternative, some browsers allow invocation from the OS specifically to act as UIs for local apps. For example, you can launch Google chrome in app mode by invoking it with a switch at command line specifying the address of the said app, e.g.:

chrome --app=http://localhost:7842

This produces a chrome instance without an address bar displaying your app, living in a different space than the default chrome browser).

Here's the whole Python file, which I called server.py:

import web, json, webbrowser, multiprocessing

urls = ( # URLs mapped to handler classes
    '/', 'home',
)

# Where the templates live:
render = web.template.render('templates/')

class home:
    def GET(self): # Show displaydata.html
        return render.displaydata()

    def POST(self):
        input = web.input()
        web.header('Content-Type', 'application/json')
        return json.dumps({ # Do trivial operations:
            'txt' : input.mod.lower(),
            'dat' : "%.3f" % float(input.num)
            })

if __name__ == '__main__':
    app = web.application(urls, globals())
    multiprocessing.Process(target=app.run).start()
    webbrowser.open_new_tab("http://0.0.0.0:8080/")

There's one more piece, which is the CoffeeScript that produces the getdata.js JavaScript file included in the HTML:

getData = ->
  $.post "/",
    { mod: $(".text").val(), num: Math.random() * Math.PI * 2 },
    (result) ->
      $("#data").html(result.dat + " " + result.txt).hide().fadeIn(500)
  setTimeout getData, 2000 # Repeat every 2 seconds

$ ->
  getData()

The getData function calls jQuery's Ajax post function. The first argument is the URL to post to (it assumes the base of this URL is the same one that the page originated from, which is required). The second is the JSON object that will be sent as the POST data; you'll remember that the keys for this object (used within home.POST in the Python app) are mod and num. mod gets the value from the component with a class of text, while num is generated with JavaScript's random number generator (note that CoffeeScript can transparently call JavaScript code).

The third argument is a callback function which is called when the Ajax request returns. The result argument is the JSON object that is returned by the Python home.POST method, and note that the dat and txt fields are accessed with simple dot notation. The field with id data is modified by inserting our result data formatted as HTML. The hide() and fadeIn() are jQuery calls to fancy it up a little.

Finally, setTimeout is a JavaScript function that calls another function after a number of milliseconds. This way, the page keeps refreshing itself every two seconds.

The last two lines are the equivalent of a main() method: after the page is loaded and ready, the $ -> block will be executed and that will get everything started.

Skip to the end of the article to see the application running in your browser.

Server-Side Events in HTML5

The above approach works in many situations; periodically polling the server is often all you need to do. Sometimes, however, it's a little too primitive and what you'd like is for the application on the server to be able to drive the UI by pushing data to it whenever it needs to.

To address this issue, HTML5 is standardizing Server-Sent Events. This technology is supported now in most browsers, however not IE9 (it may appear in IE10, but you never know with IE). I've posted a question to the Web.py newsgroup asking whether there is support for Server-Sent Events.

Server-Sent Events completes the package and makes HTML5 a full UI; I look forward to experimenting with it.

Heroku Python Support

Heroku is a cloud application platform. They seem to have the goal of making cloud deployment of your app as easy as possible, and they also seem to be trying to adapt to whatever technology you happen to be using.

Just today they announced support for Python. You can find a walkthrough here and there's specific information about using it with Django here.

Because I've been preparing for this speaking trip to Europe, I have yet to delve into the details of Heroku. My friend James Ward (who now works there) asked to see the code for this article, and upon receiving it had it set up in less than 30 minutes (even though he is a Python novice and hasn't used Web.py). Here are the steps he used (with the basic Heroku stuff already set up):

Create a new file in your src dir named "Procfile" containing:

web: python server.py ${PORT}

Create a new file in your src dir named "requirements.txt" containing:

web.py

Create the git repo, add the files to it, and commit them:

git init
git add .
git commit -m init

Create the app on Heroku:

heroku create -s cedar

Deploy the app:

git push heroku master

It's kind of clever the way they use git as the deployment mechanism. I have to say I'm pretty impressed by how quickly this could be deployed on Heroku. I think it's a tribute to the simplicity of Web.py that there weren't any snags.

Here is the app running under Heroku

Talk Back!

Have an opinion? Readers have already posted 5 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Bruce Eckel adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences.

This weblog entry is Copyright © 2011 Bruce Eckel. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use