<About />

My name is Kevin. I am a web professional living in Massachusetts. I build websites mostly using Drupal and jQuery. I use Vim even when I don't need to. When I'm not on the computer, I'm usually hanging with my wife, Melissa.

<Search />

Kevin Hankens's blog

Dropdown Drupal menus with accessibility-friendly image links

I just finished up the preliminary work on a new site where the client wanted drop down menus and fancy image links for the top-level menu items. The requirements were to use the Drupal menu system to maintain permissions on the links, but also use stylized images for the main menu links. It took a bit of searching, but the solution was pretty elegant thanks to the Drupal community.

The drop downs were easy. I used the Nice Menus module for the first time and immediately fell in love with it. It provides up to 99 blocks to which you can assign Drupal menus. The menus can drop down or fly out, depending upon your design. Plus, you can stylize them to your hearts content to match your theme. So, the only real problem was how to use images for the top level menu.

There are apparently a few ways to solve this problem. My first attempt was to just use CSS to size the menu items, hide the menu text and place a background image for the element. This seemed to be the least obtrusive method for Drupal, but flaws emerged with compatibility and accessibility. Firefox seems to like color: transparent; but IE does not. They both seem to like text-indent: -1000px; but with either of these solutions the links completely disappear when you turn images off :(

Luckily, I stumbled upon this comment which details the use of theme_menu_item_link() to swap the menu title field with the description field and allow it to use HTML for display. After implementing the function in your template.php file, you can type your HTML img tag into the description field, and your hover text into the title field when creating a menu item.

Now, you have an accessibility friendly menu which uses Drupal permissions, image tags with alt text and an anchor tag with title text. You just have to do a little bit more CSS cleanup to get the drop downs to match your theme and you are golden!

Many thanks to the Drupal community on this one!!

Firefox add-on ScreenGrab! is a huuuge timesaver

I don't know how many times I've had to paste together five screen captures to make a single image of a web page. For some reason, I never really went out looking for a solution. So, yesterday I had to do a bunch of these and finally decided that /someone/ must have come up with a good solution for this.

Yes they did.

ScreenGrab! https://addons.mozilla.org/en-US/firefox/addon/1146

This add-on is quite simple. You can either save a screen capture as a file or copy it to the clipboard. You also have the option of capturing a complete page, the visible section, a selection or the current window.

Super handy.

Alter Drupal Email Messages

I just had a requirement to add some text to the body of Drupal's outgoing emails. Turns out it's not too tough using hook_mail_alter(). I started out by using var_export() and drupal_set_message() to find out more information about the array that Drupal creates for the message:

<?php
/**
* Implementation of hook_mail_alter().
*/
function your_module_mail_alter(&$message) {
 
// For example, submit the contact form and you
  // will be able to see the $message array

 
$v = var_export($message, true);
 
drupal_set_message("message: " . $v);
}
?>

The email text is constructed from an array of elements, so adding a new element to the email body is just a matter of adding an element to the body array:

<?php
/**
* Implementation of hook_mail_alter().
*/
function your_module_mail_alter(&$message) {
 
// Add a link to the user's profile in the message body
  // when you submit the site wide contact form:

 
switch ($message['id']) {
    case
'contact_page_mail':
      global
$user;
      if (
$user->uid != 0) {
       
$message['body'][] = 'Profile: [your-site-address]/user/' . $user->uid;
      }
    break;
  }

}
?>

You could just as easily use this for a variety of other cases. For example, you could add conditional recipients, do custom logging, or even use the message content as a conditional trigger. Lots of good stuff there.

Skewed Stats

browser pie chartSo, I got a big influx of traffic from Planet Drupal yesterday. Check out the browser stats. Definitely telling that it's an open source community. Funniest part is that the 6th ranking browser is the googlebot :)

BTW, I'm in love with Skitch. If you haven't tried it, download it - even if just to watch the intro video - fun, fun!

Adding containers to Drupal form elements

So, I ran into an issue recently where I needed to add a container <div> to stylize my Drupal form elements. The issue was two-fold. One, I wanted to style form elements differently depending upon type, and two, I needed a container <div> that only wraps the form element and not the label.

Problem: I need a container around the <input> tag so that I can make the background of the input transparent, ditch the border then place a background image that is not attached to the <input> tag (there are scrolling issues in IE if the bg is set for the input tag). This way I can end up with a nice rounded input that matches the site theme.

This won't work for me:

<div id="edit-field-address1-0-value-wrapper" class="form-item">
<label for="edit-field-address1-0-value">Address</label>
<input id="edit-field-address1-0-value" class="form-text required text" type="text" value="" size="60" name="field_address1[0][value]"/>
</div>

Quick fix in template.php using theme_form_element() (Look for the line "ADD A CONTAINER DIV WITH A CLASS BASED UPON ELEMENT TYPE")

<?php
/**
*implementation of theme_form_element().
*/
function mytheme_form_element($element, $value) {
 
// This is also used in the installer, pre-database setup.
 
$t = get_t();

 
$output = '<div class="form-item"';
  if (!empty(
$element['#id'])) {
   
$output .= ' id="'. $element['#id'] .'-wrapper"';
  }
 
$output .= ">\n";
 
$required = !empty($element['#required']) ? '<span class="form-required" title="'. $t('This field is required.') .'">*</span>' : '';

  if (!empty(
$element['#title'])) {
   
$title = $element['#title'];
    if (!empty(
$element['#id'])) {
     
$output .= ' <label for="'. $element['#id'] .'">'. $t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
    }
    else {
     
$output .= ' <label>'. $t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
    }
  }

 
// ADD A CONTAINER DIV WITH A CLASS BASED UPON ELEMENT TYPE
 
$output .= "<div class = 'round-" . $element['#type'] . "'> " . $value . "</div>\n";

  if (!empty(
$element['#description'])) {
   
$output .= ' <div class="description">'. $element['#description'] ."</div>\n";
  }

 
$output .= "</div>\n";

  return
$output;
}
?>

Resulting HTML:

<div id="edit-field-address1-0-value-wrapper" class="form-item">
<label for="edit-field-address1-0-value">Address</label>
<div class="round-textfield">
<input id="edit-field-address1-0-value" class="form-text required text" type="text" value="" size="60" name="field_address1[0][value]"/>
</div>
</div>

Above you can see the <div class="round-textfield"> wrapping my form element. Now, I am able to style the container div based upon which type of field it is!

Live Coverage Module is Live!

Well, I finally got around to releasing my first module. Woo hoo! This makes me feel a bit better about having to miss the first two days of Drupalcon DC.

The module is a live blogging tool that I'm calling Live Coverage. Basically, you create a Live Event node, and then add short updates to it as an event is going on. These short updates are then automatically refreshed on your site visitor's pages.

Check it out at: http://drupal.org/project/livecoverage

It's still very much in beta, but I'm really stoked about it. Hopefully some of the good Drupal folk can lend a hand in testing it out :)

Apache Solr + Acquia Search = Rocks!

I finally got around to setting up the Apache Solr module and Acquia Search. Holy cow, it's crazy nice.

  • Faceted search - filter by author, content type, author, etc.
  • Speling suggestions!
  • Crazy relevant
  • Crazy fast

So, the Acquians have been hard at work setting up a big cluster of Solr servers in the cloud to get ready for the big launch of Acquia Search. It's cool because they do all of the heavy lifting and Solr maintenance for you. Drupal search is meh, ok but Solr search is insanely fast and relevant. Your site sends an index (securely) to the Acquia servers, then all of your site queries go (securely) to the Acquia servers to retrieve results.

It's even complete with Drupal access API integration. If you are running the Node Access module, or Organic Groups, it will respect your node permissions!

Drupal.org just switched over to it and people are finally using the site search instead of Google - that's saying something! :)

More regression testing

Very interesting test results from some regression testing I did today for a client. I was using Siege and Apache AB for anonymous user testing, and attempting to simulate authenticated users by specifying cookie data with AB. The tests were done remotely from an EC2 instance.

Here were the basic commands:

> ab -t 300 -kc 50 http://example.com/
>
ab -t 300 -kc 50 -CSESS[...]=[...] http://example.com/
>
siege -c 50 -t 300s -f url_list.txt

Rough Results:

No Drupal caching; No opcode caching
AB Anonymous 5.23 req./sec.
AB Authenticated 3.20 req./sec.
Siege Anonymous 7.0 req./sec.
Normal Drupal caching; No opcode caching
AB Anonymous 121.34 req./sec.
AB Authenticated 6.40 req./sec.
Siege Anonymous 218.83 req./sec.
Normal Drupal caching; eaccelerator enabled
AB Anonymous 338.31 req./sec.
AB Authenticated 14.49 req./sec.
Siege Anonymous 543.50 req./sec.

I ran each test a few times and selected the median value. So, these aren't 100% conclusive, but there is definitely a trend here. The interesting thing is the huge difference between anonymous and authenticated users.

Deer Camp

Deer Camp

Here's a quickie panorama from this weekend at Deer Camp.

Regression testing Drupal with Siege

So, I was doing some playing with Siege today which can be used to do regression testing and benchmarking on a webserver.

Install on RedHat was easy:

$ yum install siege

Quick test of 100 concurrent users for 10 seconds randomly hitting the urls from url_list.txt (one URL per line):

$ siege -c 100 -t 10s -f url_list.txt

The interesting thing was turning on normal caching in Drupal (no block caching because I'm using the node privacy byrole module). Check out the difference:

Before caching:

** SIEGE 2.66
** Preparing 100 concurrent users for battle.
The server is now under siege...
Lifting the server siege..      done.
Transactions:           27 hits
Availability:       100.00 %
Elapsed time:         9.77 secs
Data transferred:         0.77 MB
Response time:         5.56 secs
Transaction rate:         2.76 trans/sec
Throughput:         0.08 MB/sec
Concurrency:        15.37
Successful transactions:          27
Failed transactions:            0
Longest transaction:         9.26
Shortest transaction:         0.00

After caching:

** SIEGE 2.66
** Preparing 100 concurrent users for battle.
The server is now under siege...
Lifting the server siege..      done.
Transactions:          179 hits
Availability:       100.00 %
Elapsed time:        10.27 secs
Data transferred:         3.45 MB
Response time:         4.67 secs
Transaction rate:        17.43 trans/sec
Throughput:         0.34 MB/sec
Concurrency:        81.40
Successful transactions:         179
Failed transactions:            0
Longest transaction:        10.01
Shortest transaction:         3.65

Third time running after caching:

** SIEGE 2.66
** Preparing 100 concurrent users for battle.
The server is now under siege...
Lifting the server siege..      done.
Transactions:         1353 hits
Availability:       100.00 %
Elapsed time:        10.50 secs
Data transferred:        18.73 MB
Response time:         0.74 secs
Transaction rate:       128.86 trans/sec
Throughput:         1.78 MB/sec
Concurrency:        95.98
Successful transactions:        1353
Failed transactions:            0
Longest transaction:         1.40
Shortest transaction:         0.03

I hit my max_clients limit with plenty of memory to spare the first run, but only had a concurrency of 15. However check out the difference in transactions after turning on caching. 27 before caching and 179 immediately after. Then 563 and 1353 after running a second and third time. Plus, the last run, I hit 95 concurrency!!

Now, Drupal caching isn't going to be as great for authenticated users, but for a site that has mostly anonymous visitors, the caching is pretty impressive. So, moral of the story, turn on normal caching when you install a new Drupal site :)

On a side note, you should also install the devel module and log queries - again quite impressive when you turn on caching.