Views Integration with your custom module and Overwriting Views Handlers

The module I’m using at least once a day is (what a surprise) Views. While the module is great and (after somewhat of a learning curve) pretty self-explanatory, I’ve always found it hard to find a good documentation about writing custom views handlers. The Views 2 API manual at http://views.doc.logrus.com has all the information, but is a bit too technical, even for my taste. So for myself, and everyone else trying to write their own custom views handler or just modify the functionality of an existing one, here is a very non-technical, concept-driven explanation of Views Handlers (forgive me, Earl).

Let’s assume one writes a custom module that stores some information in its own database tables and fields. The Views documentation itself does a good job about what’s required to expose the data of your own table into views. Successfully done so, you will be able to show your module’s custom data using Views. Here’s a real life example: I wrote a module that tries to mimic Facebook’s Newsstream. The module tracks a specific action in the database every time a user uploads a new photo, creates a new comment etc. One of the fields that I store in the database is the uid of the person “doing the action”. If I want to expose that field to views, I need to do a few things:

  1. Tell my module that I want to use Views to display data by implementing hook_views_api
  2. Define my table as a base table for Views
  3. Tell Views what to do with my database fields

1. Implement hook_views_api

This is a pretty simple step and documented well. In my case, the module is called socialfeed, so the implementation of hook_views_api looks as following:

/**
 *  Implementation of hook_views_api().
 */
function socialfeed_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'socialfeed'),    
  );
}

2. Define my table as a base table

If I want to use the data from my table alone (meaning, not in combination with nodes or users), the easiest way is to define my custom database table as a base table for views. My table needs to have a unique identifier for that to happen (usually an auto_increment counter). Alternatively, I can join my data with another tables data (e.g. the user table). Here, I define my table as its own base table by implementing hook_views_data():

/**
 * Implementation of hook_views_data()
 */
function socialfeed_views_data() {
  $data = array();
  $data['socialfeed']['table'] = array(
    'group' => t("Social Feed"),
    'title' => t("Social Feed Stream"),
    'help' => t("Shows social stream items targeted at the current user."),
  );
  $data['socialfeed']['table']['base'] = array(
                'field' => 'sfid',
                'title' => 'Socialfeed Items',
                'help' => t("Social Feed items are generated through user activity."),
                'weight' => 10,
  );
}

3. Tell Views what to do with my data: Define the Views Handlers

If you’ve used the Views module before, you’re familiar with the different ways to use data. Basically, there are 5 different groups for database fields: Fields, Sorting, Filters, Arguments & Relationships. For any of those five groups, Views needs to know what to do with the data coming from each database field. Here’s the kicker: The handler’s functionality has to be different depending on where it is used (this makes total sense, but took me a bit to get my head around). If I use the field “actor” (representing a uid) in the “Fields” list, I’m most likely just displaying it. If I use it in “Sorting”, I’m using the uid to filter down the results. I’m using the same field, but Views needs to handle it differently. This should make sense to any Views user: When I show a “uid” field in the Fields list, I have the option to select things such as “Link this field to its user”. If I use a “uid” field in Sorting, I can use things such as “Filter this view to the logged in uid”. Exactly that functionality is defined by Views Handlers.

For this example, let’s talk about the easiest view handler: views_handler_field. This handler displays information in the “Fields” section for view and is the most general handler for the “Fields” section. Views itself provides a number of more customized field handlers that extend (!!) the functionality of views_handler_field. Here’s the direct link to the documentation: http://views.doc.logrus.com/classviews__handler__field.html.

Back to our module example (a newsstream called “socialfeed”). In order to expose the “actor” field from my database table “socialfeed”, I use the following construct (to be placed within the implementation of hook_views_data):

$data['socialfeed']['actor'] = array(
  'title' => t('Actor User'),
  'help' => t('User that created the action.'),
  'field' => array(
    'handler' => 'views_handler_field',
  ),
  'filter' => array(
    'handler' => 'views_handler_filter_user_current',
    'type' => 'yes-no',
  ),
);

Let’s explain this step-by-step:
The field I’m defining is called “actor”, and it’s stored in the “socialfeed” table. The title for this field in the Views UI is going to be “Actor User”, the description will be “User that created the action”. In the “Fields” section of Views, I want this field to be displayed using the views_handler_field, in the “Filter” section of Views, I want to use the views_handler_filter_user_current handler with a Yes/No selection. You might ask yourself the question: How should I know which handler to use? In general, you could start with the most generic one. If you know another module that exposes its data to Views, you could take a look at their Views implementation and use the same handler. Alternatively, you can look at the name of the handler itself (which is pretty self-explanatory), at the source code of the handlers (all the handlers that come with Views itself are located in sites/all/modules/views/handlers) or you could read the documentation at http://views.doc.logrus.com.

After you’re done with all these steps with your own module, you should be able to use your module’s data just like the data of any other modules that integrates with Views. And that means you can use all the sweetness that comes with the Views module itself.

 

Overwriting Views Handler Functionality

For everyone who’s not overwhelmed with the information so far, here’s the icing on my cake: What should you do if you don’t like the way Views handles the data (meaning, the Views handler doesn’t do what you want it to do). For most cases, this won’t happen, but it actually happened to me, so here’s an easy-to-follow example.

In an earlier version of the Socialfeed module, I was storing HTML text in the database,for performance reasons 🙂 Anyways, I got Views to display that data as text, but the HTMl was escaped, and for my case, it should have been unescaped. After a lot of back-and-forth, I decided to modify the behavior of the handler I was using (views_handler_field).

Of course, there’s a hook for that: hook_views_handlers. Here’s how the implementation for this case looked:

/**
 * Implementation of hook_views_handlers().
 */
function socialfeed_views_handlers() {
  return array(
  'info' => array(
    'path' => drupal_get_path('module', 'socialfeed'),
    ),
  'handlers' => array(
    'socialfeed_message_views_handler_field' => array(
     'parent' => 'views_handler_field',
     ),        
    ),
  );
}

The first few lines are just basic setup, the definition of “handlers” is where it gets interesting: I’m defining my own handler called “socialfeed_message_views_handler_field”, which extends the functionality of the handler called “views_handler_field”. The name of my handler is pretty much custom, but in this case, I usually try to use this structure: databaseName_fieldName_originalViewsHandler. In order to define this handler, you need to create a new file (in the same folder) called socialfeed_message_views_handler_field.inc (the name of the handler). In that field, we can go ahead and define the functionality we want to change.

Coming back to my example, I had to find out why the HTML in the data was escaped. To find that, I had to look into views_handler_field.inc and found the culprit in a function called render that returned the text after using Drupal’s check_plain function. Here’s the original function:

function render($values) {
  $value = $values->{$this->field_alias};
  return check_plain($value);
}

To overwrite that function, you need to know a little bit of object-oriented PHP, as Views uses objects and classes. The contents of socialfeed_message_views_handler_field look as following (and that’s literally the whole file):

<?php
class socialfeed_message_views_handler_field extends views_handler_field {
  function render($values) {
    $value = $values->{$this->field_alias};
    return $value;
  }
}

The difference to the original function is quite obvious and programming-wise not so hard. However, the process of creating a custom handler to overwrite the functionality of the original handler wasn’t quite clear to me after reading the Views documentation, hence I decided to write a blog post (mostly, so I’ll remember the next time I have to do this).