Smokes your problems, coughs fresh air.

Author: Rowan Rodrik (Page 16 of 27)

Rowan is mainly a writer. This blog here is a dumping ground for miscellaneous stuff that he just needs to get out of his head. He is way more passionate about the subjects he writes about on Sapiens Habitat: the connections between humans, each other, and to nature, including their human nature.

If you are dreaming of a holiday in the forests of Drenthe (the Netherlands), look no further than “De Schuilplaats”: a beautiful vacation home, around which Rowan maintains a magnificent ecological garden and a private heather field, brimming with biological diversity.

FlashMQ is a business that offers managed MQTT hosting and other services that Rowan co-founded with Jeroen and Wiebe.

Modern self-hosted, open-source web statistics software

I’ve been using Google Analytics for most of my websites for quite some time. Philosophically, I’d prefer a self-hosted solution though. In the past I’ve used Webalizer and I still use awstats for my ad-hoc, static website, but these programs are a bit primitive and limited compared to Analytics.

Of the newer programs that I’ve been looking into Piwik looks the most promising to me, but there’s also Obsessive Website Statistics which looks pretty reasonable. W3Perl looks pretty, but seems rather spartan.

An example of Piwik in action

An example of Piwik in action

One of the things that has made me a bit shy of Google Analytics is its reliance on JavaScript, a dependence that means that I can’t gather statistics about robots or other user agents lacking JavaScript support. On the other hand, this method lets you gather much more data for the clients that do support JavaScript. Piwik depends on JavaScript in the same way as Google Analytics does. This can be seen as either an advantage or a disadvantage depending on your view on this issue.

I’d have to install Piwik to determine my view…

VIM insert mode shortcuts

A few months back, I stumbled upon a blog post with some interesting VIM shortcuts for within insert mode.

  • Ctrl+t and Ctrl-d adjust the current line indent one level to the right and left, respectively.
  • Ctrl+w deletes the last word. I already use this one a lot because it’s the same as in Readline’s Emacs mode (which I prefer over its vi mode).
  • Ctrl+u deleted all the way to the first non-whitespace character.

But the best stuff was in the comments:

  • Ctrl+o enters normal mode for just one command and then returns to insert mode. I find myself very often going to normal mode ‘temporarily’. This should make these occasions pass quicker.
  • Ctrl+y repeats the character at this position from the line above in this line. Ctrl+e does the same for the line below.
  • Ctrl+x Ctrl+l can be used to copy a whole line, allowing you to cycle through the lines with Ctrl+P and Ctrl+N.

Configuring X input devices through HAL

When I went from X.org < 7.3 to X.org > 7.3, I had to make some changes to my X input configuration.

First of all, I had to replace x11-drivers/synaptics (http://web.telia.com/~u89404340/touchpad/) with the newer x11-drivers/xf86-input-synaptics, which is automatically installed if you set INPUT_DEVICES="${INPUT_DEVICES} synaptics" or if you add “x11-base/xorg-server input_devices_synaptics” to /etc/portage/package.use.

Starting with X.org 7.3, you’re supposed to use HAL for all the X input configuration. All the "InputDevice" sections have to be removed from xorg.conf (and of course the references in "ServerLayout"). If you don’t want this, you can add the following options:

Section "ServerFlags"
  Option "AllowEmptyInput" "off"
  Option "AutoEnableDevices" "off"
EndSection

I chose HAL. I wanted to replicate the following settings using HAL.

Section "InputDevice"
    Identifier "Keyboard1"
    Driver     "kbd"
    Option "AutoRepeat" "500 30"
    Option "XkbRules"  "xorg"
    Option "XkbModel"  "thinkpadintl"
    Option "XkbLayout" "us"
    Option "XkbOptions"        "ctrl:nocaps,altwin:menu,compose:ralt,eurosign:e"
EndSection
 
Section "InputDevice"
    Identifier "Synaptics Touchpad"
    Driver     "synaptics"
    Option "SendCoreEvents" "true"
    Option "Device" "/etc/psaux"
    Option "Protocol"    "auto-dev"    # Auto detect
#    Option "TouchpadOff" "1" #Uncomment if you just want to disable the touchpad and use only the trackpoint
#    Option "HorizScrollDelta" "0" #Why is this in here by default. By Gods, it kill horizontal scrolling!
    Option "RightEdge" "5500" #This is a little bigger than the default narrowing the scroll region
    Option "BottomEdge" "4500" #This is a little bigger than the default narrowing the scroll region
    Option "RTCornerButton" "0" #disable Right Top corner "button" 
    Option "RBCornerButton" "0" #disable Right Top corner "button" 
    Option "SHMConfig" "on" #this allows configuration of the touchpad using qsynaptics, synclient, or what have you
EndSection

The keyboard part was easy. I copied /usr/share/hal/fdi/policy/10osvendor/10-keymap.fdi to /etc/hal/fdi/policy/99-keymap.fdi and made a few adjustments:

<?xml version="1.0" encoding="ISO-8859-1"?> <!-- -*- SGML -*- -->
<deviceinfo version="0.2">
  <device>
    <match key="info.capabilities" contains="input.keymap">
      <append key="info.callouts.add" type="strlist">hal-setup-keymap</append>
    </match>
 
    <match key="info.capabilities" contains="input.keys">
      <merge key="input.xkb.rules" type="string">xorg</merge>
 
      <!-- If we're using Linux, we use evdev by default (falling back to
           keyboard otherwise). -->
      <merge key="input.xkb.model" type="string">thinkpadintl</merge>
      <match key="/org/freedesktop/Hal/devices/computer:system.kernel.name"
             string="Linux">
        <merge key="input.xkb.model" type="string">evdev</merge>
      </match>
 
      <merge key="input.xkb.layout" type="string">us</merge>
      <merge key="input.xkb.variant" type="string" />
      <merge key="input.xkb.autoRepeat" type="string">500 30</merge>
      <merge key="input.xkb.options" type="string">ctrl:nocaps,altwin:menu,compose:ralt,eurosign:e</merge>
    </match>
  </device>
</deviceinfo>

The Touchpad configuration was a little more involving, because I added a few options which, before, I didn’t know the thing supported. 99-x11-synaptics.fdi:

<?xml version="1.0" encoding="ISO-8859-1"?>
 
<deviceinfo version="0.2">
  <device>
    <match key="info.capabilities" contains="input.touchpad">
 
    <merge key="input.x11_driver" type="string">synaptics</merge>
    <!-- Arbitrary options can be passed to the driver using
         the input.x11_options property since xorg-server-1.5. -->
 
    <!-- Switch on shared memory, enables the driver to be configured at runtime -->
    <!-- This allows configuration of the touchpad using qsynaptics, synclient, or what have you -->
    <merge key="input.x11_options.SHMConfig" type="string">on</merge>
 
    <!-- Enable vertical scrolling by dragging your finger along the right edge -->
    <merge key="input.x11_options.VertEdgeScroll" type="string">1</merge>
 
    <!-- Enable horizontal scrolling by dragging your finger along the left edge -->
    <merge key="input.x11_options.HorizEdgeScroll" type="string">1</merge>
 
    <!-- This is a little bigger than the default narrowing the scroll region -->
    <merge key="input.x11_options.RightEdge" type="string">5500</merge>
 
    <!-- This is a little bigger than the default narrowing the scroll region -->
    <merge key="input.x11_options.BottomEdge" type="string">4500</merge>
 
    <!-- disable Right Top corner "button" -->
    <merge key="input.x11_options.RTCornerButton" type="string">0</merge>
 
    <!-- disable Right Bottom corner "button" -->
    <merge key="input.x11_options.RBCornerButton" type="string">0</merge>
 
    <!-- Enable vertical scrolling when dragging with two fingers anywhere on the touchpad -->
    <merge key="input.x11_options.VertTwoFingerScroll" type="string">1</merge>
 
    <!-- Enable horizontal scrolling when dragging with two fingers anywhere on the touchpad -->
    <merge key="input.x11_options.HorizTwoFingerScroll" type="string">1</merge>
 
    <!-- Enable tapping to emulate mouse buttons -->
    <merge key="input.x11_options.TapButton1" type="string">1</merge>
    <merge key="input.x11_options.TapButton2" type="string">2</merge>
    <merge key="input.x11_options.TapButton3" type="string">3</merge>
 
    <!-- Maximum movement of the finger for detecting a tap -->
    <merge key="input.x11_options.MaxTapMove" type="string">221</merge>
 
    <!-- For other possible options, check CONFIGURATION DETAILS in synaptics man page -->
</match>
</device>
</deviceinfo>

I’m still not completely satisfied with the Touchpad configuration (actually less so than before I started messing with HAL), so I wrote a little tool to be able to mess around with my configuration a little bit easier.

Introducing synclient-sync

With SHMConfig on, you can use the synclient program to make changes to the touchpad configuration without having to restart X. I decided that it would be handy if I could utilize this to test out changes I made to the HAL config file without having to reload it, so I wrote a little script which detects the differences between the current (live) configuration and the configuration in the HAL .fdi file. The running configuration is then updated to reflect the changes in this file. (Newer versions of the script can be found on GitHub)

#!/bin/bash
 
HAL_FILE=/etc/hal/fdi/policy/99-x11-synaptics.fdi
 
VERBOSITY_LEVEL=2
 
usage()
{
    cat <<EOF
Usage: $0 options
 
OPTIONS:
--verbosity [level] Control the level of output processed by the script.
Level 0: No output at all
Level 1: Only output changed options
Level 2: Only output options the configuration file
Level 3: Output all options
--verbose Shortcut verbosity level 3
- Show this
EOF
 
    1
}
 
options_from_hal_file()
{
    HAL_SED_FILTER='s%^\s*<merge\s*key="input\.x11_options\.\(.*\?\)"\s*type="string">\(.*\)</merge>%\1=\2%'
    cat $HAL_FILE \
        | grep '<merge key="input.x11_options.' \
        | grep -v 'SHMConfig' \
        | sed -e $HAL_SED_FILTER
}
 
from()
{
     synclient -l \
        | grep '=' \
        | sed -e 's/\s//g' \
        | option;
key= $option|cut -f 1 -d '='`
            old_val= $option|cut -f 2 -d '='`
            new_val=`options_from_hal_file|grep $key|cut -f 2 -d '='`
 
            [ -z $new_val ];
                [ $VERBOSITY_LEVEL == 3 ] && -e "\e[1;30m$key = $old_val\e[0m"
            [ $old_val != $new_val ];
                [ $VERBOSITY_LEVEL -ge 1 ] && -e "\e[1m$key = \e[1;31m$old_val \e[1;4;32m$new_val\e[0m"
                synclient "$key=$new_val"
            # The HAL file and the life configuration are in sync
                [ $VERBOSITY_LEVEL -ge 2 ] && -e "\e[1m$key = \e[4m$new_val\e[0m"
           
}
 
do_from=0 [ $# -gt 0 ]; do
"$1"
        --from-hal)
            do_from=1
            ;;
        --verbose)
            VERBOSITY_LEVEL=3
            ;;
        --verbosity)
            VERBOSITY_LEVEL=$2
           
            ;;
        -)
            usage
            ;;
   
  [ $do_from == 1 ];
from
usage
 
# vim: set shiftwidth=4 tabstop=4 expandtab: 

References

Microblogging

I’ve been struggling with the decision whether to start a microblog or not. Actually, I had already decided ‘yes’, but then had trouble deciding the software or on-line platform. I actually preferred a self-hosted solution (otherwise, I’d feel the need to backup or even mirror everything myself anyway). Trying to choose a solution took me so much time that I actually reconsidered the need for microblogging. What is it, after all, that attracted me to it?

The first reason that I consider microblogging a great idea is context. I have all these little jots in my computer and bookmarks spread all over the place and most of it is completely irrelevant and should probably be deleted or at least buried and forgotten. Everything that I blog about is immediately buried and I can forget about it, knowing that I can pop it back up whenever I want to by Googling for it or by doing a quick tag search. When I’m blogging about something, because I know it’s going to be published, I take care to put it in proper context so that I’ll know its relevance when I later stumble upon it. A very important piece of this context is the date on which I post something. Every post being dated is also a very important factor in being able to ‘bury’ it. There’s nothing like having a date in the URL for saying: “This might have been important then, but it might not be so now.”

So, I wanted a microblog to be able to much easier organize all these little thoughts and links I encounter by simply dropping them into a stream, but not a data stream. No, not a data stream. del.icio.us, for example, isn’t really working for me because it’s just the same as having a whole lot of crap URLs on disk. I don’t bookmark stuff because I want to have a neat catalog of the world; I bookmark stuff because I want to get it out of my head and get on with what I’m doing. Then, later, I might want to revisit depending on the context. Normally, when bookmarking something, you might be assigning tags and everything and even a description in which you could put the same as you’d put in a post to a microblog, but I’m still in a different mode than when I’m saying:

  • While researching microblogs, I just stumbled across Ping.fm
  • I’m really wondering: shouldn’t I simply use Twitter?
  • But what about the crap limitations on length and layout? I want to be able to put in at least a paragraph.

Even as I’m writing this, I notice how much easier this flows as a method of note-taking than trying to turn something into a coherent story. Note that the context is immediately apparent because the posts follow each other in time.

What has basically hijacked my desire to start a microblog is Twitter, which allows you to send updates by SMS, which imposes a ridiculous limitation on the length of posts. The problem with this is that is confuses my choises, because the second reason to want to start a microblog is sharing. With Twitter and its competitors it’s possible to reply to others in your status updates and some even support a rudimentary form of tagging and groups. This promises a lot of social advantages which I’ve also enjoyed with this blog. However, being limited to interaction within the walled garden of Twitter to me is no better than posting everything as status updates to Facebook (or Hyves, the Dutch social network). It’s actually worse, because at least Facebook has a convenient interface and less arbitrary restrictions coming from SMS.

To solve the walled-garden problem, there’s the OpenMicroBlogging (OMB) protocol with two open source implementations: StatusNet and OpenMicroBlogger. Both can be installed on your own web host, but both also have a free sign-up service (OpenMicroBlogger and identi.ca respectively).

Some guy has even figured out how to post from Laconi.ca (now StatusNet) to WordPress. But, I have to wonder: why did I want to impose the OMB restrictions on myself in the first place? I still can’t use mark-up. This makes me having to use weird line-noise such as ‘@’ and ‘#’. But, worst of all, it means that putting in an URL quickly makes the post exceed its maximum length, because it’s all simple plain text and every character is counted. And that’s my problem with OMB and many of these services in general: 140 characters is just a little too restricting, especially if I’m going to use it for URLs. I simply don’t like URL shortening services. So, why was it that I’d want to use OMB? Interoperability. But my blog has excellent interoperability. Why would I need to torture myself by requiring even more protocols, while all I could really need is some custom theming for short posts. There’s plenty of microblogging themes for WordPress to prove this point.

There’s just the issue of blog posts being a bit meta-data heavy: they require a title, a nice slug and tags. But, on the other hand: OpenMicroBlogger, for example, has already added support for meta tags because inserting lots of little ‘#’ characters in your posts makes them look like line-noise.

I have to resist the temptation of sticking my nose into every new fad and network. It’s simply the reality of the day that the whole World Wide Web is a disjointed mess of data islands, each with different context definitions. For now, I think that I should restrict myself to the context of the blogosphere.

In the end, with my blog blog interoperability is actually better than with a OMB microblog, because of nice features such as trackbacks and the ability to comment without the commenter first having to register with a microblog themselves.

More

  • Louis Gray has written quite a bit about Identi.ca and microblogging in general.

Tracking WordPress in a Subversion vendor branch

Two months prior to writing a script to upgrade MediaWiki installations using Subversion vendor branches, I wrote something similar for WordPress. It’s a little bit more limited and should really incorporate some of the improvements made for the MediaWiki version, but it worked fine so far:

cat upgrade-wordpress.sh 
#!/bin/bash
 
svn_repo_url=file:///var/svn/blog.omega-research.org/vendor/wordpress/
last_version=$1
new_version=$2
 
tmp_dir=`mktemp -d` $tmp_dir
 version $*;
    "Downloading and extracting WordPress version $version..."
 
    [ -z `svn ls $svn_repo_url|grep $version` ];
        branch= $version |sed -e 's/\.[0-9]\+$//'`
        archive_file="wordpress-$version.tar.gz"
        md5_file="wordpress-$version.md5"
 
        wget "http://wordpress.org/$md5_file"
        wget "http://wordpress.org/$archive_file"
 
        [ `md5sum $archive_file |cut -f 1 -d ' '` != `cat $md5_file` ];
            "MD5 sum did not match!" >&2
            1
       
 
        tar --extract --ungzip --transform "s/^wordpress/$version/" --file $archive_file
        svn_load_dirs.pl $svn_repo_url -t $version current $version
   
 -
svn merge "$svn_repo_url$last_version" "$svn_repo_url$new_version" .

I’m actually planning to make both scripts a little bit more generic (in the sense that svn_repo_url becomes an external param) and to track future changes to them using GitHub’s Gist. (How ironic is that, tracking an script for Subversion using Git?)

reCAPTCHA fun

If you’ve never heard of reCAPTCHA before, reCAPTCHA is a free CAPTCHA service that helps to digitize books, newspapers and old time radio shows. I’m using reCAPTCHA on this and other blog to protect myself from automated spam comments. I’m also using it on some of my MediaWiki sites to protect myself from wiki spam.

The great thing about reCAPTCHA is that it solves two problems at once. First, the OCR problem: it uses user time that would otherwise have been wasted solving meaningless capchas to aid the digitization process of public texts, to fill in the gaps where OCR fails. By doing so it solves the automated spam problem by challenging website visitors to proof that they’re human. We can be pretty confident that someone is human if they’re able to recognize characters that the best OCR software cannot.

Anyway, enough about the serious stuff. I’m blogging about this now because Wiebe sent me a few links to funny reCAPTCHA combinations. Here’s a nice example (from the I Am Not A Robot weblog):

Johnson Chorus

Johnson Chorus

www.stichting-ecosafe.org

Stichting EcoSafe is a Dutch foundation for the safe-keeping of the funds that are necessary for the maintenance of hardwood plantations. In July of 2006, together with Johan Ockels, I created a website for the Foundation. Johan was responsible for the organization of the whole process. This went very smooth and the website ended up being an emblem of simplicity and clarity. That’s why I wanted to blog a bit about it now, even though there are a few things that I’d probably end up doing different if I were to start from scratch. [There’s actually a disturbing number of things for which this is true, I’m coming to notice.]

File structure

Like with most websites, I started with creating an SVN repo so that I wouldn’t have to be afraid of ever losing anything.

The file structure was pretty standard:

  • a css dir for stylesheets;
  • img for images;
  • inc for shared PHP and mod_include stuff and for AJAX partials;
  • jot for to-do’s and other notes;
  • and js for JavaScript files and libraries.

Possible file structure improvements

If I were to redesign this file structure, I’d collapse css, img and js into one directory called layout, because these are typically things that require the same robots.txt and caching policy. Also, it is meaningless to organize things by file extension. If you want to sort something by file extension, use ls -X (or ls --sort=extension if you’re on GNU).

Server-side includes

The site would be so simple that I felt that any type of CMS or content transformation would be completely unnecessary. Instead, I decided to rely on Apache’s mod_include and just use a few partials for repeating page elements such as the left sidebar containing the logo and the menu.

Also, because I didn’t need to transform the HTML files, I decided I could use good ol’ HTML 4 instead of XHTML 1 (which I’d have to send to the browser with the wrong mime-type anyway).

This is the HTML for contact.nl.shtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/loose.dtd">
 
<html lang="en">
  <head>
    <title>Contact EcoSafe</title>
 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
    <link rel="stylesheet" type="text/css" href="/css/style.css"></link>
  </head>
 
  <body>
    <!--#include virtual="/inc/left-side.en.html"-->
 
    <!--#include virtual="/inc/alt-lang.phtml"-->
 
    <div id="content">
      <h1>Contact</h1>
 
      <p>Your email to EcoSafe kan be sent to the following address:
      <a href="mailto:service@stichting-ecosafe.org">service@stichting-ecosafe.org</a>.
      Or, alternatively, you can fax us at +31 50 - 309 66 58.</p>
 
      <h2>About this website</h2>
 
      <p>For comments and/or suggestions concerning this website,
      you can direct an email message at:
      <a href="mailto:webmaster@stichting-ecosafe.org">webmaster@stichting-ecosafe.org</a>.</p>
    </div>
  </body>
</html>
Alternative language selection

Alternative language selection

I use <!--#include virtual--> to include the repeating parts. <!--#include virtual--> has several advantages over <!--#include file--> in that it allows for content-negotiation, execution of dynamic content etc., but here the only place were it holds an advantage is in the inclusion of /inc/alt-lang.phtml. alt-lang.phtml is a messy PHP script that figures out which language variants of a page are available and displays a selection of alternative language versions (variants with a language different from the current).

SSI and caching

Without the XBitHack directive set to full, all content handled by mod_include is sent without a Last-Modified header. However, I don’t want to use XBitHack at all, because I don’t want just any executable file to be handled by mod_include; that just too much of a … hack.

If I were to do something similar now, I’d use some kind of (sed) substitution to pre-process the includes locally so that more of what I end up uploading is simple static content. The dynamic part of the included PHP script, I would simply replace with JavaScript.

Visual design

As you can see in the HTML example, there’s hardly anything layout oriented in the HTML source. This is good, and means that I have to touch only the CSS for most minor and major lay-out modifications. (It is a pipe-dream to think that you only need to change the CSS to make the same HTML page look however you want as long as that HTML is rich enough in meaning, but for a site with pages of such simple structure, it’s a dream that comes pretty close to reality.)

I’m not much of a designer, but I think design is overrated anyway. Actually, I think that most website suffer from too much design.

The EcoSafe logo

The EcoSafe logo

To start the design, I got a logo made by Huite Zijlstra. Because the logo was pretty big and didn’t look good scaled down, I decided to put it at the left of the content area instead of at the top. This would still leave enough room for the menu (which actually takes less space horizontally than the logo).

Colors

For the color scheme, I just picked a few colors from the logo. As always, the base of the scheme would be black text on a white background for maximum readability. The print version hardly uses any colors.

@media screen {
body            {:;  }
*               {:;             }
a:link          {: #585;              }
h1              {: #880;              }
h2              {: #888;              }
strong          {: #a62;              }
#menu li a      {: #660;              }
}

Underlines

I wanted an underline below the level 1 and 2 headings. Because I didn’t like the effect of text-decoration:underline (too thick for <h2>s, too dark for <h1>s and different from browser to browser) and because border-bottom was set too far from the text, I made two simple PNG images that I could repeat-x along the bottom edge.

@media screen {
h1 {:('/img/h1-border-bottom.png'); }
h2 {:('/img/hx-border-bottom.png'); }
}

The menu is very simple. The markup is part of inc/left-side.en.html for the English version and inc/left-side.nl.html for the Dutch version:

cat inc/left-side.en.html
<div id="left" lang="en">
  <a class="logo" href="/index.en"><img class="logo" alt="[Logo]" src="/img/logo.jpg"></img></a>
 
  <ul id="menu" class="menu">
    <li><a href="/index.en" rel="start">Start page</a></li>
    <li><a href="/plantations.en">For plantations</a></li>
    <li><a href="/investors.en">For investors</a></li>
    <li><a href="/history.en">History</a></li>
    <!--<li><a href="/goals">Goals</a></li>-->
    <li><a href="/methods.en">How it works</a></li>
    <li><a href="/cost-structure.en">Cost structure</a></li>
    <li><a href="/cost-calculator.en">Cost calculator</a></li>
    <!--<li><a href="/clients.en">Clients</a></li>-->
    <li><a href="/contact.en">Contact</a></li>
  </ul>
</div>
 
<script type="text/javascript" src="/js/menu.js"></script>
The EcoSafe menu (in English)

The EcoSafe menu (in English)

As is customary, I started by removing all the default list styles and made the anchors behave as block-level elements. I used the big O from the logo for bullets in the list (using background-image instead of list-style-image because the latter gives unpredictable cross-browser results and doesn’t make the bullet clickable).

#menu {
 : 2em;
 : 2em;
 :;
 : 0;
}
 
#menu li {
 : 0;
}
 
#menu li a {
 :;
 :('/img/o-21x16.png');
 :;
 :;
 : 30px;
 :;
 :;
 :;
 : #660;
}
 
#menu li a:hover,
#menu li.active a {
 :('/img/oSafe-21x16.png');
}
 
#menu a:hover {
 : #787800;
}

JavaScript menu item activation

To add the active class to the currently active list item (<li>), I used a client-side solution using JavaScript. After all, it’s proper use of JavaScript to enhance your user interface with it (as long as, as many would say, it isn’t required for the UI to function (as it is in the Cost Calculator)).

// menu.js
 
var menu = document.getElementById('menu');
var anchors = menu.getElementsByTagName('a');
var locationHref = window.location.pathname.toString();
  
for (i = anchors.length - 1; i >= 0; i--) {
  a = anchors[i];
  aHref = a.href;
    
  // Does this menu item link to the current page?
  // We find out by looking if the window location contains the URL in the anchor
  // or the other way arround. The reason to look at both is content-negotiation.
  // It's also true if the location is just '/' and we're looking at the anchor of
  // the 'start' page.
  if ( (locationHref === '/' && a.rel === 'start') ||
       (locationHref !== '/' && ( locationHref.indexOf(aHref) !== -1 ||
                                  aHref.indexOf(locationHref) !== -1 ) ) ) {
    a.parentNode.className = 'active';
    break;
  }
}

I actually just fixed a long-standing bug that was caused by me not being able to fully rely on HTTP language negotiation for the selection of the appropriate language variant, which made me change all links from being language-neutral to including the language in the link target (e.g.: http:///history became http:///history.en and http:///history.nl), the problem with this being that, instead of being able to link to link to http:/// (http://www.stichting-ecosafe.org/), I had to link to http:///index.en or http:///index.nl, making it more difficult to detect the active anchor if we’re requesting the home page through http:/// instead of on of its language-specific URLs.

The JavaScript rested on the assumption that by reverse iterating through all the anchors in the menu and thus processing the link to http:/// as last, I’d know that I had struck the home page and wouldn’t need to worry that any of the links contain a slash. (I don’t know if I intended it to work this way, but it sure seems to me now that the only way this could ever have worked was as an apparent side-effect of the looping order; the SVN logs seem to agree.)

I could have solved this by redirecting all requests for http:/// to the appropriate variant. Maybe I should have (to avoid duplicate content). Instead I chose to add a rel="start" attribute to the links to the home page, as can be deduced from the JavaScript above. (To resolve the duplicate content issue, I could also add a canonical link to the header of the two language variants.)

Anyway, all this brings me to the messy subject of content negotiation.

Content and language negotiation

The EcoSafe website would be bi-lingual (English and Dutch) from the onset. Initially, I wanted to use language negotiation to the extend of having completely language-neutral URLs. For example: http:///cost-calculator instead of http:///cost-calculator.en and http:///cost-calculator.nl. In the end, you can make this work properly in the browser with the help of a cookie, but it’s still a pipe-dream because nothing else will work if you do not also offer another navigational path to the different variants. Maybe, we’ll revisit this topic for a later experiment.

Content-type negotiation is almost effortless with Apache thanks to mod_negotiation. If, like me, you despise to have .html, .htm, .xhtml, .phtml, .pxhtml. .sxhtml, .php, .xml in your URL (I actually used all of these at some time or other), you only have to make sure that MultiViews is in your options.

I’ve configured SSI by means of the following instead of a “magic mime-type”:

AddType         text/html       .shtml
AddHandler      server-parsed   .shtml
AddCharset      UTF-8           .shtml
AddOutputFilter Includes        .shtml

For PHP I couldn’t do the same because my web host was still at Apache 1.3. Otherwise, the following should have worked equally well for PHP:

# This doesn't work with Apache 1.3
AddType        text/html       .phtml
AddHandler     php-script      .phtml
AddCharset     UTF-8           .phtml

Configuring language priority is easy with Apache:

Integrating PHP and SSI

The integration of PHP with all the weirdness that I had configured and created around SSI took some figuring out. Luckily, PHP offers a virtual() function that works roughly the same as mod_include's <!--#include virtual-->. Here’s an example:

<body>
  <?php virtual('/inc/left-side.en.html'); ?>
  <?php $uri = '/cost-calculator.en.phtml'; include('inc/alt-lang.phtml'); ?>

In retrospect, it’s pretty much bullshit to use it. I could have just as well require()d the partials (which I actually did for the alternate language selection), but I probably started out using virtual on a more generic URL without language and content-type selection in it.

406 handling

Because I deployed on Apache 1.3 and the ForceLanguagePriority directive was only introduced with Apache 2.0.30, I had to write an ugly hack to avoid visitors getting 406 errors. To that end, I added a 406 handler to my .htaccess file:

LanguagePriority en nl
ForceLanguagePriority Prefer Fallback # This doesn't work with 1.3
 
ErrorDocument 406 /error-406.php # Luckily, this does 

error-406.php is a PHP file that figures out the available variants based on $_SERVER['REQUEST_URI']. Then, it simply picks the first one (which works because, accidentally, that’s the one I’ve given priority using the LanguagePriority directive as well), outputs a 200 OK header instead of the 406, and virtual()s the file of the variant. The code looks somewhat like this:

<?php
chdir($_SERVER['DOCUMENT_ROOT']);
$filenames = glob(basename($_SERVER['REQUEST_URI']) . ".*");
 
$filename = $filenames[0];
 
apache_setenv('DOCUMENT_URI', "/$filename");
 
header('HTTP/1.1 200 OK');
virtual("$filename");
EcoSafe Cost Calculator

EcoSafe Cost Calculator

EcoSafe Cost Calculator results

EcoSafe Cost Calculator results

The Cost Calculator

The EcoSafe Cost Calculator is some of the least neatly contained and most procedurally oriented PHP code I’ve ever produced while knowing full well what I was doing. It does almost everything it does in global scope. Yet, it does it well.

The thing is designed as a dynamic web page rather than a web application. What I mean by this is that it’s simply two pages (one for English and one for Dutch) using PHP among a number of pages using SSI. In an application, it’s usual to have just one ‘view’ that is the same for all languages, but here I chose to put the different language versions in different language pages and then include everything reusable (and language-neutral) from within these files.

Most of the actual processing and calculating is done in inc/costs-functions.php. (The part about gotos is a joke. (Labeled blocks would have been quite sufficient. 😉 ))

<?php # costs-functions.php - Stuff that's includes by cost-calculator.{nl,en}.phtml
/**
 * Just remember that this code was never meant to be general purpose or anything.
 * So, relaxeeee and keep your OO-axe burried where it belongs.
 * Oh, if only PHP would support GOTO's ... Sigh ...
 */

The rest of the file is just a whole lot of processing of form data and turning it into something that can be easily traversed for display to the user. There are even the function calls without arguments doing all their work on globals. These are actually only added to make it clearer em a piece of code is doing. And—I must say—after a few years it’s still remarkably clear to me what each part of the code is doing. There’s no deep, confusing nesting structures or anything. There’s just a whole lot of very simple code.

Some simple AHAH increases form interactivity

Users of the calculator can add any number of plantings and locations. When the user decides to add a planting or a location, the onClick event triggers the execution of addExtraPlanting() or addExtraLocation(). Here’s how addExtraPlanting() looks:

function addExtraPlanting() {
  lang = document.documentElement.lang;
 
  new Ajax.Updater(
    'plantings', '/inc/planting.' + lang, {
      method: 'get',
      insertion: Insertion.Bottom
    }
  );
}

Ajax.Updater comes from the Prototype JavaScript framework.

Here’s what inc/planting.en.phtml looks like. The same file is also included in a loop to rebuild the form’s state after submitting.

<li>
  <input name="num_hectares[]" type="text" size="5" value="<?php echo $num_hectares ?>" />
 
  hectares have been planted in
 
  <select name="plant_years[]"><?php require('planting_options.php') ?></select>
 
  (<a title="Remove this planting" href="#" onclick="removePlanting(this); return false;">x</a>)
</li>

I think that I’ve gone into small enough detail by now to get to the conclusion. Also showing the contents of planting_options.php would be pushing it. Ah, well…

<?php
 
if ( !isset($this_year) ) $this_year = intval(date('Y'));
if ( !isset($plant_year) ) $plant_year = $this_year;
 
for ($i = $this_year; $i >= $this_year - 20; $i--)
  echo "<option" . ($i == $plant_year ? " selected='1'" : "") . ">$i</option>\n";

(Yesterday, I couldn’t resist the temptation of turning this into a simple file to require() instead of the function definition it was. I think it’s funny to refactor something to remove encapsulation.)

Conclusion

As is usual when looking at old code, I see many things that I’d do (even just a little) different today, but I saw a surprising number of solutions that I actually still like now that I see them back after three years. Removing some of the remaining warts probably won’t do much good besides the masturbatory satisfaction it could give me. (It’s likely that the website won’t live much longer, making such extra attention very undeserved.) But, nevertheless, I’ve enjoyed blogging about it now to recoup the whole experience and to at least look at what I’d do different now and what I learned in the meantime.

Some links

« Older posts Newer posts »

© 2024 BigSmoke

Theme by Anders NorenUp ↑