What in the world have I been up to?

You probably know that Nancy and I spent two years on our bicycles riding from the far north of Canada to Argentina in South America. You can read all about the adventure on hobobiker.com

Even before we got back, I restarted my Drupal development consulting (Drupal is a Content Management System and web development environment) and have been plenty busy in the intervening time. It's been my intent to list some of the projects and learning here, but to this point I haven't done it. Maybe now :-)

Drupal: Panels Plugin Example module

For the Amazon_store Drupal module I've been working on for Alonovo.com, I set up several of the pages to use Panels plugins. But there isn't a lot of documentation on how to do this, so I had to dive in and learn it from the examples available in the code. The result was a set of examples of how an drupal module can implement panes, the Panels Plugin Example which is now available on on Drupal.org.  There are examples of content_type, argument, context, and relationship plugins.  I'm hoping that others will review the code and improve it.

Portfolio and Resume

I've been doing a lot of Drupal jobs and websites over the years, and sometimes update this portfolio.

A couple of my own websites:

  • hobobiker.com. A blog with theming and custom modules for mapping of routes.
  • warmshowers.org. A social networking site: Reciprocal hospitality for touring cyclists. Much custom drupal work, including Ajax-driven Google maps display and geolocation. And now it'sinternationalized and translated into Spanish, French, and Portuguese.

A sampling of drupal work, upgrades, modules development:


Resume: And my resume is attached, detailing 30 years of work in the computer field, from Unix development to C# to perl to network administration and more.

PDF icon Randy Fay Resume, updated 201598.88 KB

Internationalization and Localization of Warmshowers.org

Warmshowers.org is my biggest site, with more than 7500 members, and almost certainly the most useful. It's a reciprocal hospitality site for touring cyclists - people offer touring cyclists a place to stay and a shower. It's been in English for years, but that cuts out a significant part of the world's population, and it means that options for cyclists in Latin America and several other places in the world have been more limited than they ought to be.

But we just launched es.warmshowers.org, a Drupal 6 version with all the key parts internationalized and then localized into Spanish. We're very proud of this accomplishment!

It took a lot of work by a lot of people to do this. First, Chris Russo nearly singlehandedly upgraded the site to Drupal 6, a significant accomplishment because of the custom modules we have developed for the site. Then we began learning how to do all of the localization into Spanish, and learning how to get a group together to do the translation.

There are probably four parts to the translation of the site:

  1. Translation of the important, static content (articles, FAQ, nodes)
  2. Translation of the interface (menus, status and warning messages, dialog boxes, prompts, forms)
  3. Translation of the rest of the interface that's left over (outgoing emails, non-code instructions to users, etc.)
  4. Translation of transient user-generated content (forums, other postings)

The last of these we're going to try to do with a custom module that uses the <a href="http://code.google.com/apis/ajaxlanguage/"Google AJAX Language API", specifically using the Jquery Translate jquery plugin. We did everything that seems reasonable by hand translation with native speakers, but it does not seem possible to keep up with all of the user-generated content. We'd never get enough translators to do that.

Why didn't we use machine translation for the entire site? Because it still stinks, despite what anybody says. If you know two languages, and you use Google Translate or Babelfish or any of the other services to translate them, you'll understand what I mean. Often the result is useful, meaning that you can understand the intent. Sometimes it's hilarious. Many times it's stupid. And sometimes it's just plain misleading or incomprehensible. Not a very good thing to build a website on.

But our biggest issue in translation is not the technology, it's the people. It takes a strong team to translate and then maintain a website in another language. So we're trying to learn how to do that. It's baby steps we're taking, but we're taking them.

We sent out an email to our worldwide membership some time ago and 31 native Spanish speakers responded by saying they were interested in joining the project. I created a Google Group for discussion and maintenance of the translation, and invited them all. 21 signed up for the group. And 5-10 have been active in helping with the translation.

We started the training with an online conference using the yuuguu screensharing technology and Skype. But technical problems (internet connectivity mostly) made this not work so well. So I made three 5-minute screencasts showing how to do the translation. That seemed to work better than the group conference technique.

We then set the group loose on translation. One of the biggest issues at this point was knowing who might be working on what. At first we used an editable document within the Google Groups framework, but then I was able to create two Drupal views ( 1 and 2 which show the status of translation activity using native Drupal information. The cool thing here is that due to the sophistication of views, these will work unchanged in other languages. So we're building up the infrastructure needed to do French, and then Portuguese, etc.

Another challenge any volunteer organization faces is deciding who's in charge. We needed an editor to make final decisions about usage, and to try to give the translated material some semblance of consistency. One of our translators, David Peluso, has graciously agreed to do this for now. And we now need somebody to answer emails sent to the site administrator in Spanish, so another translator, Manu, has signed up for this task. We'll use a special "Spanish" category on the contact page so that emails sent in Spanish will go straight to him.

Now the questions are: Can we maintain the site in multiple languages? How many languages should we take on?

Compared to my past attempts at internationalization of websites, this one is much farther along. In the past I always underestimated the scope of the task. It's big. This time, with a great team, I think we have a working site. Also, I think that Drupal 6 is far better at this than previous incarnations. Every attempt I made in the past ended up with very confusing language switches for no reason, and lots and lots of broken links. I think this one is going to work.

DrupalCamp Colorado next week - My session got accepted

My session on AHAH in Drupal got accepted for next week's Drupal Camp Colorado in Denver. So it's time to get it ready. (Update: see the materials here.

AHAH (Asynchronous HTML and HTTP) is a technology built into the Drupal Forms API which allows for updates of a form without doing a full page refresh, and it makes some things really nice. All of the "add to cart" and similar behaviors in the Amazon Store module (try it at alonovo.com/amazon_store) are done with AHAH, meaning that you can add to cart and not end up on another page, and not have a long wait.

Basically, pressing a button causes a simple call back to the web server for replacement elements for the form. Instead of submitting the whole page to the webserver and then reprocessing and redisplaying it, only the part that needs to be updated (like your message that an item was added to the cart) has to be done.

Cooler forms with Drupal's AHAH (Asynchronous HTML and HTTP)

[This material was developed for a presentation at DrupalCamp Colorado in the summer of 2009. It should serve well as a standalone resource, so I hope it will help out anybody learning or struggling with AHAH. Since that time, all of the examples here have been incorporated into the Examples module's AHAH Example, so it should serve as an easy way to experiment with these concepts. If you find any problems with this or want to suggest any improvements, send me an email or catch me on IRC (rfay).]

All of us know about "Web 2.0" and "AJAX", or "Asynchronous Javascript and XML". The basic idea behind AJAX is that a Javascript program running in the browser communicates (usually via HTTP and XML) with the webserver and gets information that it uses to do a task or update the page. Several Drupal modules use AJAX, and it's widely deployed on the web. It normally requires you to write and maintain some pretty good Javascript along with your PHP, and it's a powerful way to add interactive features to a website. Drupal's excellent contrib module Fivestar, for example, uses XML to allow you to vote without a page load.

Built into Drupal, though, is a no-visible-Javascript technique for doing a particular class of asynchronous (no-page-reload) partial page updates. It's called AHAH, or Asynchronous HTML and HTTP. It also uses Javascript under the hood, but you never see it. Everything the programmer does is in PHP.


Examples of AHAH in Drupal

  • Upload module (core) (Click a submit button and it adds another file)
  • Poll module (core) (Click a submit button and it adds another question)
  • Examples module (Drupal 6) provides a number of AHAH examples in the "AHAH Example module".
  • Quicktabs module (Configuration options change with the type of tab)
  • Amazon Store - Used in "add to cart" and selecting different product attributes.


AHAH Changes for Drupal 7 (It's now called #AJAX)

Drupal 7 AHAH has changed its name to AJAX to use a more familiar term and to be slightly more trendy. A version of this presentation for D7 AJAX is at http://randyfay.com/ajax. D7 AJAX handling is massively simplified.

The AJAX Example for D7 is now in the Examples Module and will be maintained there. If you find AJAX features that you'd like to see in it that aren't there, file a request or submit a patch.


AHAH Example: Textfields driven by checkboxes

Note that this is currently maintained in the Examples project so the code here may not be the latest.

In this example, we just use checkboxes to determine whether textboxes are displayed.

One of the fundamental ideas of having a form change based on selections within the form is that the form is reconfiguring itself based on $form_state. So here, the generation of the form is driven by $form_state['values']. If the checkbox for last name is checked, then we generate a textfield for last name.

Update 2009-08-23: I note that checkboxes and radios do not actually work correctly in Internet Explorer.

(Experiment with this one at http://d6.drupalexamples.info/examples/ahah_example/autotextfields.)

 * @file
 * Show/hide textfields based on checkbox clicks
function ahah_demo_autotextfields(&$form_state) {

$form['ask_first_name'] = array(
'#type' => 'checkbox',
'#title' => t('Ask me my first name'),
'#default_value' => $form_state['values']['ask_first_name'],
'#ahah' => array(
'path' => 'ahah_demo/autotextfields/callback',
'wrapper' => 'textfields',
'effect' => 'fade',
$form['ask_last_name'] = array(
'#type' => 'checkbox',
'#title' => t('Ask me my last name'),
'#default_value' => $form_state['values']['ask_last_name'],

'#ahah' => array(
'path' => 'ahah_demo/autotextfields/callback',
'wrapper' => 'textfields',
'effect' => 'fade',


$form['textfields'] = array(
'#title' => t("Generated text fields for first and last name"),
'#prefix' => '<div id="textfields">',
'#suffix' => '</div>',
'#type' => 'fieldset',
'#description' => t('This is where we put automatically generated textfields'),

   if (
$form_state['values']['ask_first_name']) {
$form['textfields']['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First Name'),
  if (
$form_state['values']['ask_last_name']) {
$form['textfields']['last_name'] = array(
'#type' => 'textfield',
'#title' => t('Last Name'),

$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Click Me'),


ahah_demo_autotextfields_callback() {
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);

$args = $form['#parameters'];
$form_id = array_shift($args);
$form_state['post'] = $form['#post'] = $_POST;
$form['#programmed'] = $form['#redirect'] = FALSE;

drupal_process_form($form_id, $form, $form_state);
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

$textfields = $form['textfields'];
$output = drupal_render($textfields);

// Final rendering callback.
print drupal_json(array('status' => TRUE, 'data' => $output));



Subscribe to Drupal