views

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).

Drupal 5 Views Recipes

I recently finished reading the book "Drupal 5 Views Recipies", the first book about one of Drupal's most relevant modules: Views. Here's my  summary.

At first glance, Views' module page looks like any other page on Drupal.org. Only the high number of commits and comments reveal that this is the module that enables site administrators to use Drupal as a Swiss army knife for content display. In a nutshell: Views is a query-builder with an administrative interface that gives site admins the ability to create almost any possible list of content without having to write the SQL query themselves. From my own experience working with Views, I remember the early challenges I had with this module. The user interface is a monster, not because it is poorly designed, but simply because of the large amounts of settings necessary to give an admin the flexibility she needs. Almost any setting produces some result, but it takes a lot of knowledge to configure a view correctly.

One of the main reasons I was pleasantly surprised about Drupal 5 Views Recipes was the fact that the book doesn't just give a laundry list of views (or recipes), but instead helps the reader understand Views from a conceptual perspective. Personally, I much prefer a book that explains a concept as it allows me to not just execute an example that's mentioned, but transfer my knowledge to my individual requirements.

This book delivers a great overview about Views without being overwhelming and starts out with a very simple view that gets fully dissected and explained. It then moves on to explaining working with default views (which come shipped with the Views module) as a basis for a site's custom views. Views learning curve is especially steep when trying to use taxonomy terms. Drupal 5 Views Recipies describes all the GOTCHA's and ways to get around them, such as an excellent description on displaying all terms from a specific taxonomy and using Drupal's "l" function to create the related links.

Views' right hand (well, they're pretty much equal partners) is the Content Construction Kit, or CCK, which allows the definition of custom fields for content types. Drupal 5 Views Recipes explains very well how to combine the two modules effortlessly and create views such as a blogroll, using the CCK link field. Another rather complex module is CCK's Node Reference field, which requires the usage of arguments in Views. Again, the best way to conceptually understand the interaction between the two is a simple example, exactly as the one used by the author. Furhermore, the book explains how to use dates in Views. Both the Date module itself as well as large number of other module gems are explained in the book, such as setting up a calendar block, visually showing aggregated data using the Timeline module and the setup of a summary view, which can be used for things like "blog posts per month", aggregating the number of posts in a given month.

Since working with the Views module requires dealing with a lot of different aspects of Drupal (content types, users, taxonomy, many different types of CCK fields), I was excited to get much more than just information about Views out of this book. Drupal 5 Views Recipes contains a lot of the "bread & butter" developer knowledge required for every Drupal admin, ranging from dealing with Firebug, how to work with cron and some basic understanding of the UNIX command line. Theming of views results is also mentioned in the book, although it only touches the surface. Also, this are of the book is not applicable to Drupal 6 / Views 2, as theming is the one area that radically changed in Views 2.

Overall, I can highly recommend this book to any Drupal administrator looking to begin working with Views or trying to become a Views expert. I was somewhat sceptical about the fact that this book focuses on Views 1 / Drupal 5 only. Conceptually however, there are no big differences between Views 1 and 2, so I would also recommend this book for Views 2 beginners using Drupal 6. I my view, the documentation that comes with Views is very brief and technical, and I am excited that the author succeeded in explaining the concept of Views in a structured and easy-to-comprehend way. I'll be the first to buy the second edition with an update to Views 2.

The book can be purchased at the Packt Website or Amazon.

Views Breadcrumbs using Arguments

For most of my clients, I need to create Breadcrumbs. Seemingly easily, breadbrumbs is an area that is a bit more involved than most features in Drupal. There's a great module out there called custom_breadcrumbs. The module works great for the display of breadcrumbs on all node pages, but doesn't (yet) work with views.

After doing some research about a solution for view pages, here's my favorite pick: Views Arguments.

For a simple overview page (that uses views), add a "Global: Null" Argument. Within that argument, select "Provide default argument" and select "PHP Code". In here, you can use the drupal_set_breadcrumb function. That function accepts an array of link titles and paths. Here's an example:

$breadcrumb[] = l('Home', null);
$breadcrumb[] .= l('Is The Box Butler For Me?', 'use-cases');
drupal_set_breadcrumb($breadcrumb);

To make this work, it's important to select "Display All Values" for "Action to take if argument does not validate", otherwise the view gets messed up.

To be on the safe side, I've attached a screenshot of the arguments code for this (live) example: http://theboxbutler.com/use-cases.

Using CCK, Views and custom theming to show self-defined nodes

For one of my latest projects, I needed to create node relationships. The client wanted to create nodes ("Photos") that have a common parent node ("Series").

I was contemplating to use OG, as it's are real powerful module, but decided against it, as the requirements were much smaller. Instead, I decided to use the node_reference field that's part of CCK to create relationships, which works like a charm. I didn't run into the problem that these relationships are just pointing in one direction. On the frontend, both items were linked with each other.

The bigger challenge was displaying the Photos in a Series. I previously used Panels for grouping several views and was attempting to do the same thing in this case. After a lot of trial&error, I decided that Panels for Drupal 6 isn't quite ready. The main reason I decided against Panels was the missing feature to use node overrides for different node types.

I was able to create a view that displays all photos in a series, using the Views preview to manually try out different Series node id's (nid). The missing piece was to display both the Series itself and the view to display related items, something that I would have liked to use Panels for. The solution to my problem was a little bit of custom theming. I created a separate template for series using a duplicate of node.tpl.php. Then, I added a function to display a view in a template file (take from here: http://api.freestylesystems.co.uk/api/function/views_embed_view/6):

<?php print views_embed_view('series_photos', $display_id = 'default', arg(1)); ?>

Let's dissect the parameters of that function:

  1. The hard-coded view name (defined by myself)
  2. The display type (I selected default, but this could also be 'page' or 'block'
  3. Arguments passed into the view ( arg(1) makes sure the view knows the nid)

Here's a screenshot of the view "series_photos":

View

Conclusion: I usually try to get around custom theming. Not because I'm too lazy to do it, but because I'd like to keep as much as possible defined in the database. This solution however worked out quite nicely with only minimal theming.

Beginners Tutorial for Views 2 Theming: Creating an alternative block for recent comments

After I got my first actual comment last night (yeah!), I wanted to add the "most recent comments" as a block in the sidebar. Drupal comes with a block for that purpose, but I wasn't happy with the way the block displayed the comments. I remembered that I very much liked the way Typepad displays comments (e.g. like this on a friends blog).

I wanted to display the name (linking to the person's homepage), the word "comment" (linking to the actual comment), the title of the story (linking to the story itself), and the date. So in toto, something like this: "Daniel Hanold added a comment to Story Name X (5 days ago)".

Obviosuly, I created a view that displays comments that were recently created. I attached that view to this story for reference. However, I couldn't get the ouput of that view to display what I wanted. First of all, all the labels were added before the data and were followed by a colon. Views theming, here I come. Usually, there's tons of great documentation and examples for pretty much any module. For views theming however, I didn't find anything that explained the concept well and gave some easy to understand examples, so here's a tutorial to the easiest possible theming of a view output.

Like I said, the output I wanted was "Daniel Hanold added a comment to Story Name X (5 days ago)". The output views gave me by default was "added a: Daniel Hanold to comment Story X 5 days ago". Using views theming, I wanted to do a couple of things:

  • Remove the colon
  • Have the label show up behind the data
  • Add parenthesis around the date

To do that, go to your view and click on "Theme: Information". This gives you a lot of theming information about all the things you can theme. Since this is going to be a basic tutorial, I'll only talk about the row style output of the data fields.

On this page, you can find one link that's called "Row style output". With a file such as views-view-fields--stories-recent-comments.tpl.php (this goes into your theme directory), you can style the general output of all fields of this view. Using that template, I was able to remove the colon and have the labels show up behind the data fields. I attached this file for reference as well. The one remaining thing was adding parenthesis around the data. With a file called views-view-field--stories-recent-comments--block--timestamp.tpl.php, you can style the output of just that one data field, in this case the date (see attached file).

Upload those two files to your server, go back to your view, click on "Theme: Information" and then click on "Rescan template files" for the new templates to be activated. After you've added all those steps, you have a nice "recent comments" block that looks like this:

Recent Comments Example

Syndicate content