Home
Empowerment through collective innovation

Building a javascript-based browser map with views for Drupal 6

Recently for a projet I needed to build a javascript-based browsing mechanism for alternatives.ca. You can see the result at http://www.alternatives.ca/projets. I'll try to describe how this was done. It takes about two hours to set this up properly.

Compatibility: IE7 and above.

The basic idea

I categorized my content with taxonomy, then created a view with exposed filters, enabled ajax in the view, and used some extra modules to make the exposed filters more fluid. Finally, some CSS and a css sprite for the navigation map.

Here's what I used

- Drupal 6.x
- http://drupal.org/project/views Views 6.x-2.x
- http://drupal.org/project/views_hacks
- http://drupal.org/project/better_exposed_filters

Note that you should be comfortable installing modules and creating views to get the most of this tutorial. If you want full multilingual support, you'll need some module-building skills.

Start by categorizing your content

To set up map of the world, use the taxonomy module to set up a vocabulary "Continent" and some terms, "North America", "South America", etc. Then create some content for each term.

Setting up the view

Create a new view, set up whatever fields you want to use, and, as a filter, use Taxonomy: term ID, and expose it (here's my final view; if you import it into your site, make sure views_hacks and better_exposed_filters are enabled). Create a new page display in your view and give it a path (for this example, we'll call our view map-browser we'll use the path "map" for this example).

At this point, you should have a drop down menu by country and your view should update when you change the continent and click "Apply".

Getting rid of the "Apply" button

First off, we'd like to make it so that selecting a new continent updates your view without clicking on the "Apply" button. That's what views_hacks is for. Install it, enable its "Views Filters Auto-submit" module, go to admin/settings/views_filters_autosubmit and add your path (or select "Show on every page except the listed pages"). Make sure Javascript is enabled on your browser, and your "Apply" button should have disappeared from your page.

Displaying the filter as radio buttons rather than a drop-down menu

This is crucial to theming your filter as a map. For this we'll use the better_exposed_filters module. Install it and enable it. Now, when you edit your view's filter, you'll see a new menu, "Display exposed filter as:". Select "checkboxes / radio buttons", update, and save. Now your exposed filter should be a list of radio buttons, with no "Apply" button. So far so good.

Preventing the page from "jumping" to the top at every selection

To prevent the unwieldy "jumping" effect, go to your view and turn "Use AJAX" to on. (Note that your view is using AJAX whether this is on or not).

Adding a new div to your markup

Now we need to theme this for it to look like a map rather than a radio list. The trick here is to use the <label> tag as the map segment, and create a new &ltspan class="label-inner"> tag inside <label> to hold the region's name. To do this we'll override theme_radio() in your theme's template.php file, so it looks like this:

<?php
function alternatives_radio($element) {
 
_form_set_class($element, array('form-radio'));
 
$output = '<input type="radio" ';
 
$output .= 'id="'. $element['#id'] .'" ';
 
$output .= 'name="'. $element['#name'] .'" ';
 
$output .= 'value="'. $element['#return_value'] .'" ';
 
$checked = (check_plain($element['#value']) == $element['#return_value']) ? 'checked' : 'not-checked';
 
$output .= ($checked == 'checked') ? ' checked="checked" ' : ' ';
 
$output .= drupal_attributes($element['#attributes']) .' />';
  if (!
is_null($element['#title'])) {
   
$output = '<label class="' . $checked . ' option" for="'. $element['#id'] .'"><span class="label-inner">'. $output .' '. $element['#title'] .'</span></label>';
  }

  unset(
$element['#title']);
  return
theme('form_element', $element, $output);
}
?>

(All we're doing is adding a tag for .label-inner by slightly modifying the default function.)

Create your map image

To avoid multiple HTTP requests to the server, we'll use CSS sprites for this: a single image file which includes all your images for your map. Here is a sample world map CSS sprite.

Add some CSS

Take a look at this CSS file. You'll need to tweak the id's but you get the idea: the enclosure is set to position: relative and each of the possible selections are set to position: absolute according to the positions of the continents on the map. Here's more on CSS positioning.

Add some javascript to remove the labels of hidden elements.

This could be done in css, but then your view would be unusable if javascript is turned off, because labels would remain hidden even when they are selected. Being conscientious developers, we'll develop our site with graceful degradation/progressive enhancement in mind.

This jQuery snippet, added to your theme or module, will do the trick.

Optimizing the order of your taxonomy items

In our example, if Europe and Russia comes after Asia, a large portion of Asia, will be linked to Europe and Russia. This is because our regions are treated as rectangles for hovering purposes. Therefore, you'll want to experiment with the ordering of your terms in admin/content/taxonomy/1 depending on the format of your map and regions.

A multilingual map

Note that there is a known issue with taxonomy terms not being translated in exposed filters (http://drupal.org/node/929368). My solution was to create a custom module implementing hook_form_alter, but you'll probably have to tweak this to suit your needs:

<?php
function mycustommodule_form_alter(&$form, &$form_state, $form_id) {
  if (
$form_id == 'views_exposed_form') {
    foreach (array(
'tid', 'tid_2', 'status') as $tid_id) {
      foreach (
$form[$tid_id]['#options'] as $id => $untranslated_taxonomy_term) {
       
$form[$tid_id]['#options'][$id] = t($untranslated_taxonomy_term);
      }
      if (
$form['term_node_tid_depth_1'] && $form['#id'] == 'views-exposed-form-stage-page-1') {
        foreach (
$form['term_node_tid_depth_1']['#options'] as $id => $val) {
         
$form['term_node_tid_depth_1']['#default_value'] = $id;
        }
      }
    }
  }
}
?>

That's it: a working map navigation system!

Comments?

Leave them here.

AttachmentSize
project_map_v2.png117.12 KB
views_map_view.txt1.85 KB
selector.css_.txt3.77 KB
selector.js_.txt158 bytes
screen_shot_2011-02-01_at_12.50.53_pm.png84.39 KB
screen_shot_2011-02-01_at_12.51.24_pm.png98.91 KB