Drupal

Find your place on the Red Planet

Today is one day before my one-year anniversary at the Mars Space Flight Facility, and also one day after I finally got something else launched that the public might have a remote interest in.

Suggest an Image

Yesterday we sent out the official press release about our Suggest an Image project.

In a nutshell, for now you can go to the site and download a KML file to load into Google Earth 5. This file shows you where the Mars Odyssey spacecraft is going to be orbiting over the next week, and lets you suggest areas on the planet for the mission planners at my office to image with the THEMIS camera. If you've got the Google Earth browser plugin installed, you can also try this out without ever leaving your browser.

If/when the spacecraft takes the picture, we email it to you, and it looks something like this.

Eventually (i.e. when the Google guys get around to it) this will be a feature in the Mars Gallery in Google Earth itself.

For those of you interested in how this is done with Drupal, I did a writeup here.

Demonstrating the application at JPL's Open House earlier this month:
IMG_1648 IMG_1650


Drupal on Glassfish with clean urls using Url Rewrite Filter

You have a Glassfish server.

You are a Drupal developer.

You want to run Drupal in Glassfish. More importantly, you want to have it use clean urls because without that capability, all of your urls look like this: /index.php?foo/bar/baz. Which sucks, of course.

Let's set aside for the moment the desirability of running PHP in a Java application container for the moment1, and jump right to the meat of this geeky post. Here's how I got some of the clean url functionality you'd normally get from Apache's mod_rewrite, or using either mod_rewrite or Lua in lighttpd.

I'm assuming you've already got Glassfish installed, so from there:

  1. Get a copy of Quercus, Caucho's Java implementation of PHP 5. I downloaded the version 3.2.1 .war file.
  2. Unzip the .war:2
    jar -xvf quercus-3.2.1.war

  3. Get a copy of Url Rewrite Filter. I used version 3.2.0 (beta), but 2.6 should work also.
    > cd quercus-3.2.1
    > wget http://urlrewritefilter.googlecode.com/files/urlrewritefilter-3.2.0-src.zip
    > unzip urlrewritefilter-3.2.0-src.zip
  4. Get Drupal. I used the latest 6.x version.:
    > cd ../
    > wget http://ftp.drupal.org/files/projects/drupal-6.10.tar.gz
    > tar zxvf drupal-6.10.tar.gz
     
    Copy Drupal files to the quercus docroot
    > cp -r drupal-6.10/* quercus-3.2.1/
  5. Configure Url Rewrite Filter. This is where it gets a little sketchy. Drupal comes prepackaged with a .htaccess file, which sets up the mod_rewrite rules for Apache:
    # Rewrite current-style URLs of the form 'index.php?q=x'.
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

    Unfortunately, Url Rewrite Filter doesn't support the REQUEST_FILENAME directive (yet). So I've put in some hacks to at least get clean urls working. I don't pretend that this is production-ready, but it gets it working for basic testing. If anyone has input, I'd welcome it. Anyway, in the WEB-INF/web.xml file, the following directives need to be added:
    <filter>
      <filter-name>UrlRewriteFilter</filter-name>
      <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>UrlRewriteFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

    This should go before the <servlet></servlet> section.

    Next, create WEB-INF/urlrewrite.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE urlrewrite
    PUBLIC "-//tuckey.org//DTD UrlRewrite 2.6//EN"
    "http://tuckey.org/res/dtds/urlrewrite2.6.dtd">
    <urlrewrite>
      <rule>
        <note>
          Prevent rewriting of specific files. Definitely not
          the best way to do this.
        </note>
        <from>^/(.*)(css|js|png|jpg|gif)$</from>
        <to>/$1$2</to>
      </rule>
      <rule>
        <note>
          Prevent rewriting of files in the files directory
        </note>
        <from>^/(.*)/files/(.*)$</from>
        <to>/$1/files/$2</to>
      </rule>
      <rule>
        <note>
          Do the Drupaly stuff
        </note>
        <from>^/(.*)$</from>
        <to>/index.php?q=$1</to>
      </rule>
    </urlrewrite>
  6. Re-zip your directory back into a .war file to deploy onto the app server:
    >jar -cvf quercus-3.2.1.war quercus-3.2.1/*
  7. Deploy to Glassfish through the admin console, or from the auto-deploy directory. Note that you should set your context-root to / to run Drupal at the root of the app server.
  8. Oh yeah, you'll probably want to connect to a database too, right? In the admin panel, go to Resources > JDBC > Connection Pools and create a new Connection Pool. Let's call it mysqlpool.

    Set Datasource Classname to com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource.

    Plug in the following Additional Parameters for your Drupal DB:

    password
    user
    databaseName
    portNumber
    serverName

That should be about it.

1: There are a couple of reasons I think this is interesting. First, Caucho claims that Quercus should run PHP apps as least as fast as Apache + APC, and I've seen numbers of 24-56% faster for the same complex app. Second, you can use Java functions natively from within PHP. Working in an environment with a lot of Java code, that's potentially appealing, especially for complex scientific functions I don't really want to re-implement in PHP. Third, the ability to deploy a .war file containing the whole Drupal docroot is also interesting.

2: I expect that most Drupal developers aren't using IDEs like Eclipse or NetBeans, so I'm just using command-line tools here.

Finally something to show for my seven months on Mars

So I quit my job in newspapers last May and hauled my then-pregnant wife across the country to work for NASA.

I've been a busy little bee, working on all sorts of projects, mostly involving Drupal and/or jQuery. But sadly, not one has been allowed to see the light of day.

Well, no more. Though it's not officially released, I'm putting my latest work out there - a unified Mars Image Explorer that'll eventually replace a half dozen or more different kludgy sites with a single easy-to-use interface for images from a variety of instruments orbiting Mars.

Mars Image Explorer

I'll eventually write up a case study, but it's an interesting mix of technologies:

Drupal powers the page generation, user management and access control, jQuery runs the AJAX interface, Python/web.py manipulates images in real time efficiently, and Java servlets deliver instrument data.

So there. I actually have been doing something.


About time

Yeah, I finally got around to re-theming my site, and moving it to Lighty at the same time.

How did I manage this? Søren was being pacified by his Granddaddy and Grandma Carol:

DSC_0107

Check out the hair! Yes, it's still blond in person, despite how it looks in pictures.


Multi-column lists with jQuery, an alternative method

So I needed a method to take a long, nested list and turning it into a compact, multiple acolumn list, in order to display it as sort of a site map for the home page for a site I'm working on.

Being a huge fan of jQuery, it was naturally my go-to library of choice.

Scanning the plugins site, I found a possible solution from a feller called Ingo Schommer called columnizeList.

Score, right? Well... not exactly, at least for my case.

Ingo used some of the methodoligies outlined in this article on multi-column lists on A List Apart. One of the caveats of his methodology is that each list item has to be the same height. This works Ok for a lot of use cases, but since I'm using a Drupal menu as the source for the list, it could contain arbitrary text I don't control.

So, I started from scratch. Instead of relying on consistent line heights, and applying different margin settings to list elements, I instead decided to decompose the large source list into several smaller lists (one for each column) and then use a css float parameter to make them all appear side-by-side.

Here's a sample list for a demonstration, cribbed from Ingo's example:

  1. harold (3550)
  2. horatio (1320)
  3. hitler (1120)
  4. henry (784)
  5. hector (358)
  6. haploid (315)
  7. hopping (50)
  8. herbert mulroney (44)
  9. hopscotching (29)
  10. hominibus (19)
  11. honkey (19)
  12. hermoine (18)
  13. hieronymus (13)
  14. halliburton (12)
  15. hummer (10)
  16. harlod (10)
  17. heironymious (9)
  18. hemorrhoids (7)
  19. hammersack (6)

(apparently a list of the most common fillers for the middle initial in Jesus H. Christ)

Anyway, here's what my script does to the above list:

  1. harold (3550)
  2. horatio (1320)
  3. hitler (1120)
  4. henry (784)
  5. hector (358)
  6. haploid (315)
  7. hopping (50)
  8. herbert mulroney (44)
  9. hopscotching (29)
  10. hominibus (19)
  11. honkey (19)
  12. hermoine (18)
  13. hieronymus (13)
  14. halliburton (12)
  15. hummer (10)
  16. harlod (10)
  17. heironymious (9)
  18. hemorrhoids (7)
  19. hammersack (6)

And here's the code:


/*
Copyright (c) 2007 Christian yates
christianyates.com
chris [at] christianyates [dot] com
Licensed under the MIT License: 
http://www.opensource.org/licenses/mit-license.php
 
Inspired by work of Ingo Schommer
http://chillu.com/2007/9/30/jquery-columnizelist-plugin
*/
(function($){
  $.fn.columnizeList = function(settings){
    settings = $.extend({
      cols: 3,
      constrainWidth: 0
    }, settings);
    // var type=this.getNodeType();
    var container = this;
    if (container.length == 0) { return; }
    var prevColNum = 10000; // Start high to avoid appending to the wrong column
    var size = $('li',this).size();
    var percol = Math.ceil(size/settings.cols);
    var tag = container[0].tagName.toLowerCase();
    var classN = container[0].className;
    var colwidth = Math.floor($(container).width()/settings.cols);
    var maxheight = 0;
    // Prevent stomping on existing ids with pseudo-random string
    var rand = Math.floor(Math.random().toPrecision(6)*10e6);
    $('<ul id="container'+rand+'" class="'+classN+'"></ul>').css({width:$(container).width()+'px'}).insertBefore(container);
    $('li',this).each(function(i) {
      var currentColNum = Math.floor(i/percol);
      if(prevColNum != currentColNum) {
        if ($("#col" + rand + "-" + prevColNum).height() > maxheight) { maxheight = $("#col" + rand + "-" + prevColNum).height(); }
        $("#container"+rand).append('<li class="list-column-processed"><'+tag+' id="col'+rand+'-'+currentColNum+'"></'+tag+'></li>');
      }
      $(this).attr("value",i+1).appendTo("#col"+rand+'-'+currentColNum);
      prevColNum = currentColNum;
    });
    $("li.list-column-processed").css({
      'float':'left',
      'list-style':'none',
      'margin':0,
      'padding':0
    });
    if (settings.constrainWidth) {
      $(".list-column-processed").css({'width':colwidth + "px"});
    };
    $("#container"+rand).after('<div style="clear: both;"></div>');
    $("#container"+rand+" "+tag).height(maxheight);
    // Add CSS to columns
    this.remove();        
    return this;
  };
})(jQuery);

Download

There are only two parameters - cols, the number of columns to break the list into, and constrainWidth a boolean (defaulting to false) to specify whether you want all columns to be the same width.

I've tested with IE 6&7, FF3, Safari3 and Opera 9.something (for the three Opera users on the planet). The code could use a bit of refactoring perhaps for the purpose of beautification.

Update: I've added this to the jQuery Plugin site.

My first real contribution to open source

So I finally gave something back. Granted, in proportion to how I've benefitted from open source software over the years, it's not much. But it's (marginally) better than nothing, right?

I released a Drupal module I call Commentify, which is one of several modules I wrote (or thought up/architected) to integrate Drupal with the proprietary CMS we use at work.

This happens to be the module with the widest potential appeal, since I can't imagine too many people are interested in modules that interface with proprietary registration systems, or with non-mainstream, non-public video vendors. (Randy's Location Ads module rocked, but there's no way that's ever going to be ported to Drupal 5.x or 6.x)

Basically, it lets you attach Drupal as a commenting solution to any sort of foreign content management system.

It was something of an inauspicious release, because I managed to make not one but TWO big, bonehead mistakes with CVS, which I blame on haste and my preference for and familiarity with Subversion.

Embarrassing, but they were fixed with some help from the CVS guru at Drupal.org.


High productivity

Have I mentioned that I loves me some Drupal?

Oh yes. I probably have. Anyway, in the last 48 hours of last week I managed to take two sites from photoshop mockup to fully functioning. Well, almost, anyway. There are of course some minor issues with Internet Exploder to deal with, and some pages to be tweaked.

We created a common template architecture for niche sites that can rapidly be reskinned with CSS to create unique sites with minimal code changes. The rest, from the Heritage site course guide to the Mom2MomSC.com multimedia sharing page was implemented with a minimum of coding, and in very short order using community-contributed Drupal modules. We plugged in our proprietary Omniture analytics module, Vmix video integration and my own Videowrapper module to round out the sites.

I'm a fan of more abstracted frameworks like Ruby on Rails, Django and the like for highly specialized vertical applications, but for a rapid time-to market general purpose application platform it's hard to beat Drupal, where most of the common hooks like authentication, access control, content organization, are already in place, and hundreds (thousands) of other high-quality modules are just a click away. And if you want to plug in your own alternate solutions (like we have for integration with a proprietary corporate registration system), you can do that too. So basically, you can concentrate on features that achieve your business goals, rather than the ancillary elements.

What would you rather do? Spend your time an energy reinventing the wheel, or building revenue and audience generating products?

IMHO, which is worth what you paid for it, I'd rather run something that got me 95% of the way on a project and let me spend most of my time on the remaining edge cases, optimization and security than spend 95% of my time just getting the framework built to support whatever business goals I have.


How to: Save a crapload of money converting from print to web

We spend lots of dough each year converting material from our dead-tree editions into web-friendlier versions for our web sites. We crank out a bunch of PDFs, and send them through the ether to somewhere where the labor is cheap and the workday long, like Vietnam, Indonesia or Canada or something. Then some poor soul slices 'n dices them into jpegs and links and such, and sends 'em on back, and posts them on our site.

So I thought to myself, "Self, you can do that without having to do something silly like use people and worse, pay for it.

It's a work in progress, but it goes something like...

  • Export PDFs of ads from our DTI advertising system, and page PDFs from our Newsway prepress system.
  • Multiplex the PDFs through xpdf, imagemagick and swftools to extract text, convert to bitmaps and convert to Flash files respectively, with some proprietary workflow software. Maybe we'll OCR them with Tesseract if we can get a box with enough CPU horsepower, rather than the virtual machine it's running on, for extra text-extraction points.
  • Combine the files into an XML feed.
  • Send the files to the front-end system. Probably Drupal, but possibly a Rails app, or McClatchy's own Workbench CMS.
  • Display to the user with a combination of flash, jquery and CSS like so:

Shazaam! $35k saved.

Not to mention, jquery almost makes coding javascript fun. Almost.

It's alive!

Well, it's finally up and running - an unholy marriage of Wordpress and Drupal.

I bring you BlogSavannah and its Drupal-powered registration site. After experimenting with a half dozen community modules, I decided to set up the framework for a real event-oriented site, instead of doing a one-off solution. I was able to use the great Event, Signup and Location modules, leaving me with only the sidebar stuff to write code for. Good times. Now we'll have a great framework for publicizing more Savannah events in the future.

Oh, and then there's the event that started all this - the BlogSavannah UnConference 2008. You can get more information and register online for this free event we're holding at AASU on January 25th.


I continue to be amazed

...at the super geeky powers of the Drupal developer community. I've been playing with modules over the last couple of days trying to find the right combination to create a multimedia sharing site for work that even the octogenarian residents of Greater Bluffelson could use.

  • Upload video in most any format and convert it to flash with one click? Check.
  • Post content via email, and determine if they're images or text? Check and Check.
  • Integrate with Flickr? Check.

Of course we've created (and by 'we' I mean mostly Randy) a bunch of custom stuff too. The more I use it, the more I like about it. There's a huge base of code, and the core software makes it easy to implement new features without starting from scratch.

Obligatory Flickr example:
[flickr:56/119630321/b582235ef9|center|500|1000]