Fun with Views and Location

For my day job, I've been using the Location module quite extensively. Thanks to its many contributed modules, it integrates with various parts of the Drupal ecosystem, including Views, making it a good base to start developing your own location-aware features. Here are some puzzles I faced during the past few days:

Filtering views with existing cities

The city filter is a text field. That's not ideal because it does not give users a hint about existing cities in the database. Here's a simple city filter that displays a drop-down list only containing existing cities:

<?php
// @file mymodule_handler_filter_location_city.inc

class mymodule_handler_filter_location_city extends views_handler_filter {
  function
value_form(&$form, &$form_state) {
   
$options = db_query_array('SELECT DISTINCT(l.city) FROM {location} l ORDER BY l.city ASC', QUERY_ARRAY_MODE_SINGLETON_KEY_VALUE);
   
$form['value'] = array(
     
'#type' => 'select',
     
'#title' => t('City'),
     
'#default_value' => isset($this->value) ? $this->value : NULL,
     
'#options' => $options,
     
'#multiple' => TRUE,
    );
    return
$form;
  }

  function
admin_summary() {
    if (!empty(
$this->options['exposed'])) {
      return
t('exposed');
    }
  }
}

// @file mymodule.views.inc

/**
* Implementation of hook_views_data().
*/
function mymodule_views_data() {
 
$data['location']['city2'] = array(
   
'title' => t('City (dropdown)'),
   
'help' => t('The city of the selected location.'),
   
'filter' => array(
     
'field' => 'city',
     
'handler' => 'mymodule_handler_filter_location_city',
    ),
  );
  return
$data;
}

/**
* Implementation of hook_views_handlers().
*/
function mymodule_views_handlers() {
  return array(
   
'info' => array(
     
'path' => drupal_get_path('module', 'mymodule'),
    ),
   
'handlers' => array(
     
'mymodule_handler_filter_location_city' => array(
       
'parent' => 'views_handler_filter',
      ),
    ),
  );
}
?>

Note that you'll need the db_query_array function I defined earlier.

Node views with author's location fields

This was a nice puzzle: given a view of nodes, how do you display the location fields of each node's author? Note that I don't want the node's location fields. Here's my solution: The idea is to create an explicit relationship between node and user, such that the location data can be pulled out of the user, instead of the node.

  • Create a new relationship between node and user:
<?php
// @file mymodule.views.inc

/**
* Implementation of hook_views_data_alter().
*/
function mymodule_views_data_alter(&$data) {
 
$data['node']['uid'] = array(
   
'title' => t('User'),
   
'help' => t('Relate a node to the user who created it.'),
   
'relationship' => array(
     
'base' => 'users',
     
'field' => 'uid',
     
'handler' => 'views_handler_relationship',
     
'label' => t('user'),
    ),
  );
}
?>
  • In the node view, instantiate the node/user relationship.
  • Add location fields based on the given relationship.

Location searching via Content Profile views

This one eluded me for 2 days until I hit upon this simple solution. Given a view of Content Profile nodes, how do you provide a Search Terms filter that includes user locations? I won't bore you with my many unfruitful attempts, so here's the solution: Since the Search Terms filter searches the 'node' index, I need the user location information to be indexed there. Therefore, I update the Content Profile node index with the author's location, during hook_nodeapi('update index'):

<?php
// @file mymodule.module

/**
* Implementation of hook_nodeapi().
*/
function mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  global
$user;

  if (
$op == 'update index') {
   
$output = array();
    if (
$node->uid) {
      if (
$node->type == 'experience') { // that's my content profile node type
        // Add the user location.
       
$locations = location_load_locations($node->uid, 'uid');
        if (!empty(
$locations)) foreach ($locations as $location) {
         
$output[] = theme('location', $location);
        }
      }
    }
    return
implode('<p>', $output);
  }
}
?>

You'll need to rebuild your index for this to work.

There! Now wasn't that fun!

Comments

City (dropdown) not appearing on the Views UI at all...

Hi infojunkie!

Thanks for the post, BUT couldn't you create a module of that to show how it really works? After creating my own module, I simply didn't have success when trying to expose the City filter as a dropdown as it didn't even appear on the UI. I'm using Drupal 6 with Views 6.x-2.16 and Location 6.x-3.1.
There's a book called Drupal's Building Blocks: Quickly Building Web Sites with CCK, Views, and Panels, which writes about hook_views_data(), citing (page 207): "The hook_views_data() method returns an array of tables to Views. Each table in this array should be owned by (that is, implemented in the schema of ) the module implementing hook_views_data(). What this means is that modules should not return data about tables not owned by the module. For example, only node_views_data() should return information about the node table, and only user_views_data() should return information about the users table. When modules need to make alterations to the data provided by other modules (and it is common to need to make additions to the node table), hook_views_data_alter() should be used instead. Attempting to return data about tables defined by other modules could actually corrupt the data when PHP tries to merge the data arrays together."
What about that? In your hook_views_data() implementation, you reference a field that doesn't really exist in the database ('city2')! How should it work?
I will post a link to my own module in half an hour which is built based on your instructions here. Could you please inspect what's the problem with that (why City (dropdown) filter doesn't appear on the UI at all)? Thanks.

Pete

OK, seems to work since then!

Sorry, I think there was a problem with the place where I included the file containing db_query_array() function: I just realized that I tried to include it in mymodule_handler_filter_location_city.inc, and maybe this was the source of the problem (but I didn't get any PHP errors...) - or I don't know exactly, in the meantime, I changed some things in the code, and than reverted some of them. :D

Your query is not correct (at least doesn't work on my site), it causes a PHP error:
user warning: Unknown column 'l.city' in 'field list' query: SELECT DISTINCT(l.city) FROM location ORDER BY l.city ASC in ....

So it's the original code:

  $options = db_query_array('SELECT DISTINCT(l.city) FROM {location} ORDER BY l.city ASC', QUERY_ARRAY_MODE_SINGLETON_KEY_VALUE);

I think it should instead look like this (note AS l in the query [or it could simply be {location} l, without the "AS" keyword]):

  $options = db_query_array('SELECT DISTINCT(l.city) FROM {location} AS l ORDER BY l.city ASC', QUERY_ARRAY_MODE_SINGLETON_KEY_VALUE);

BUT when applying a relationship in my own view, your code seems to work perfectly! So thank you, infojunkie!

So I created a module with your username (infojunkie) in it. :) You can download the module created based on your instructions from here: skydrive.live.com/?cid=1979d80274fe2038&id=1979D80274FE2038%21117 (why can't I link a public site normally?? This leads to a word verification, which says I typed the wrong code, even if I type the correct one... OK, by the way: that's a link to a public SkyDrive file, location_extension_infojunkie.zip, which contains the module)

You could maybe continue to develop it, and make a public version out of it. :) Thank you once again!

Best regards, Pete

Thanks so much for sharing this. After adding your db_query_array(...) function, as you put in the notes, it worked great. Just what I was looking for.

there are three exposed filters in my view. one is to enter term ids (not term name)seperated by ',' . I did it by arguments and altering exposed filter form, created a textfield and altered the #action. Second one is simple node type. Third one is for the fields to display i.e there are checkboxes for title, teaser, body. Now i have to show only those fields. How can i do that??

I don't know if there's module to select fields to be displayed in an exposed filter.

What about filtering with multiple tids (not term names) with comma seperated??

Could use this code to provide a drop down for predefined street addresses?

This is exactly what I'm looking for, but implement how exactly?

I've created a "user_location.inc" file with the above code. And a "user_location.info" file.

Created a folder - "user_location_mod", inserted files, and put it into the modules folder.

What about a ".MODULE" file? Without it, there is no module to enable...

And then there will be a new type relationship I can add? Thus the CITY (or any fieled I guess) field will then have a drop-down that includes this relationship?

Thanks. Mercury FLiXER

Hi,

Thanks for the insight. i apologise in advance if my questions sounds a little damb. but where exactly do you in put the code you have specied? i won't mention that i a newbee..but you get the idea.

thanx.

Tu run this code:

  • Create a new, empty module with some name.
  • In each snippet of code above, you will find an @file comment. That gives you a hint about the filename: just replace "mymodule" with your module's name.
  • In each snippet, replace all occurrences of "mymodule" with your module name.
  • Enjoy!

you put them in a new module dude if u see the code it has stuff like this: function (my_module) replace the mymodule with your module name activate the module and ready you go

Hello,

I enabled the module containng the above code in the appropriate files. How do i enable it in a view?

Clear the cache then find the filter called "City (dropdown)" in the "Location" group.

hey infojunkie, this is exactly what i'm looking for (city as dropdown). it would be awesome if you could supply the contrib module, casue i'm a newbie and making my own module will take me forever. If you feel it's time for me to learn then i'm willing, but you'll save me loads of time. thanks in advance.

Drupal 7 solution:

Search API http://drupal.org/project/search_api