NaNoWriMo Graph Web Application with Dancer

November 13th, 2010
PerlDancerNaNoWriMoAxKit

NaNoWriMo Graph Web Application with Dancer

So You Think You Can Dance, Canada?

playing on the other channel: Hellcatalyst

My dragon has been a participant of NaNoWriMo — the National Novel Writing Month — for several years. A long, long time ago I wrote a small graphing web application for her and her friends to keep track of their word count throughout the month. It was built using AxKit and to say that this application was getting long in the tooth would be, aah…, kind to it. It was only a question of time before bit rot would finally take its due, so it was not a huge surprise when it kinda went belly-up this year.

I could probably have to resuscitate the old work-horse, but I decided instead to take this occasion to do something I wanted to do for a long time: give a whirl to Dancer.

Dancer touts itself as a lightweight, yet-powerful web application framework. As we will see in a few lines, it sure seems to live up to both promises. These days I’m mostly writing web applications using Catalyst, and if we compare both frameworks, Catalyst could be seen as a full-fledged Broadway extravaganza, complete with girls clad in pheasant feathers, swimming pools, bears on unicycles and a philharmonic orchestra, Dancer has the simplicity and strength of a solo tap-dancing act. There’s nothing wrong with either, mind you, both approaches are good and justified. Sometimes, you do need the unicycled bears. But other times, you want small and fast.

But enough about the hype. Let’s see how hard it was to get my app up and running, shall we?

The Specs

At the core, the word count of all participant is kept in a csv file looking like this:

2010-11-01,Andy,0
2010-11-01,Bernadette,0
2010-11-01,Claude,0
2010-11-02,Bernadette,120

The graph is only going to be used for one month, so there’s no need to be any fancier. Not to mention that the csv format makes it very easy to edit badly entered counts when someone will make a boo-boo.

The application we need around that is very simple. Basically, we need:

  • A main page, showing a graph and a form to enter new results.
  • An auxiliary page to process the input of a new word count.

Got it? Now, let’s get crackin’.

Creating the App

As with Catalyst, creating the skeleton of a new application in Dancer is incredibly complicated. First, you have to do

$ dancer -a ournowrimo

and then, uh, you’re done. Okay, so maybe it’s not that complicated after all. :-)

My personal template system of choice these days is Mason, so I also edited the configuration file and changed the default templating system for the app to use Dancer::Template::Mason:

logger: "file"
appname: "ournowrimo"
template: mason

Adding the Actions

By now, we have an application that is already in working order. It won’t do anything, but if we were to launch it by running

$ ./ournowrimo.pl

it would do it just fine.

The Main Page

The routes (i.e., the urls that the app will recognize and act upon) are defined in lib/ournowrimo.pm.

For the main page, we don’t do any heavy processing, we just want to invoke a template:

get '/graph' => sub {
    template 'index', { wrimoers => get_wrimoers() };
};

That’s it. For the url /graph, Dancer will render the template views/index.mason, passing it the argument wrimoers (which is conveniently populated by the function get_wrimoers()).

I’ll not show the Mason template here, as it’s a fairly mundane HTML affair, but you can peek at it at the application’s Github repo (link below).

The only interesting bit to it is that I’m using the Flot jQuery plotting library to generate the graph, and am using an AJAX call to get its data. Which means that we need a new AJAX route for our application.

Feeding graph data via AJAX

For the graph, we need the url /data to return a JSON representation of the wordcount data. Nicely enough, Dancer has a to_json() function that takes care of the JSON encapsulation. All that is left for us to do, really, is to do the real data munging:

get '/data' => sub {
    open my $fh, '<', $count_file;

    my %contestant;
    while (<$fh>) {
        chomp;
        my ( $date, $who, $count ) = split 's*,s*';

        $contestant{$who}{ 1000 * DateTime::Format::Flexible->parse_datetime($date)->epoch } = $count;
    }

    my @json;  # data structure that is going to be JSONified

    while ( my ( $peep, $data ) = each %contestant ) {
        push @json, { 
            label     => $peep,
            hoverable => 1,    # so that it becomes JavaScript's 'true'
            data => [ map  { [ $_, $data->{$_} ] } 
                      sort { $a <=> $b } 
                      keys %$data ],
          };
    }

    my $beginning = DateTime::Format::Flexible->parse_datetime( "2010-11-01")->epoch;
    my $end       = DateTime::Format::Flexible->parse_datetime( "2010-12-01")->epoch;

    push @json, {
        label => 'de par',
        data => [
            [ $beginning * 1000, 0],
            [ DateTime->now->epoch * 1_000, 50_000 * ( DateTime->now->epoch - $beginning ) / ( $end - $beginning ) ]
        ],

    };

    to_json( @json );
};

For more serious AJAX interaction, there’s also the Dancer::Plugin::Ajax module that adds the ajax route handler, but in our case a simple get is just fine.

Processing New Entries

For the entry of a new word count, we are taking in a form request with two parameters, who and count:

get '/add' => sub {
    open my $fh, '>>', $count_file;
    say $fh join ',', DateTime->now, params->{who}, params->{count};
    close $fh;

    redirect '/';
};

Seriously, could things get any easier?

Bonus Feature: Throwing in an Atom Feed

Since everything else resulted in a ridiculously small amount of code, I decided to add a feed to the application to let everybody know of wordcount updates. Surely that will require a lot more coding?

get '/feed' => sub {
    content_type 'application/atom+xml';

    # $feed is a XML::Atom::SimpleFeed object
    my $feed = generate_feed();

    return $feed->as_string;
};

… Seemingly not, it won’t.

Deployment

Dancer, just like Catalyst, can be deployed a gazillion different ways. As a standalone server (development heaven), as CGI (likely to be sloooow, but nice to it’s there if everything else fail), as FastCGI, and as a Plack application. My web server is still using Apache 1 and has the cruft of a decade in its configuration files, so to find the right way to deploy for me was trickier than it should be. But eventually I found something that worked for me. I launched the app as a plack-backed fastcgi

plackup -s FCGI --listen /tmp/ournowrimo.socket ournowrimo.pl

and configured Apache to treat it as an external fastcgi server

Alias /wrimo/ /tmp/ournowrimo.fcgi/
FastCgiExternalServer /tmp/ournowrimo.fcgi -socket /tmp/ournowrimo.socket

The Result

screenshot

Peek at the Code on Github

As usual, the full application is available on GitHub.