The Problem
Today I ran across an issue when trying to format out some Views that were integrated with Organic Groups. I needed to format out a few things, and the one I will speak on and demonstrate here is pulling out the member count in an Organic Group. By default, when you pull that data out in a view, it creates a link to the default OG page for members inside a group. The default path for that page is: og/users/$nid/faces. I find that link horrible, ugly, and just pure nasty in a nutshell. I really don't want anything in a client facing path that references a module name or abbreviation in the path. Of course, you could use a custom path to overwrite those path(s), but that's only if you really want to also use the default view that OG provides for members.
In the project I'm working on, these links that I'm modifying point to a custom panels page with multiple views inserted that relate to the members of the group. This gives me a flexible usage so that any time the member count of the group is pulled & displayed using views, that it will render the way I want, and link where I want the user to go.
In that handler function provided by OG it's also only wrapping a link around the numeric value of the member count, but what I need is more text available to that linking the full text like 24 members.
The project I'm working on has some unique requirements in the wireframes & designs related to the groups, and so some custom work was needed. This walkthrough will show a setup in a custom module that overwrites the default field handler that views uses to render that field.
The Solution
Now, this particular solution is for this simple use case, but can be applied in a variety of ways. I found very little documentation on this topic, and how to accomplish it with ease, hence the reason for this posting.
Step 1 - Adding to your custom module
I'm not going to walk through you creating your own module files, because if you are trying to overwrite a views handler, I'd bet my cookies that you know how to create a .info & .module file, and if not, you can find more on that in this post from the Drupal handbook pages.
First, let's add the views_api hook to our .module file in order to get views to recognize some of the things we want to override. This is going to use us to use a custom MYMODULE.views.inc file and have it pick up the data & hooks we want to implement there as the ones we'll use aren't recognized without this function being present.
* Implementation of hook_views_api().
*/
function MYMODULE_views_api() {
return array(
'api' => 2,
);
}
Step 2 - Create your custom views handler
Next, we create the file MYMODULE.views.inc in the root of your module's directory. We will also create a folder called includes where we stuff our custom handler functionality in another include file. First is the example of the custom handler I've created at MYMODULE/includes/MYMODULE_handler_field_og_member_count.inc. This really can be named anything, but I prefer to keep a very strict naming convention for functions & files so I can remember what the hell is going on later. We will tell views later how to and where to reference the function we will be placing in this file.
function query() {
$table = $this->query->ensure_table('og');
$sql = og_list_users_sql(1, 0, NULL, TRUE);
$sql = str_replace('%d', 'og.nid', $sql);
$this->query->add_field('', "($sql)", 'member_count');
$this->field_alias = 'member_count';
}
function render($values) {
$nid = $values->nid;
$txt = $values->member_count;
if (og_is_group_member($nid)) {
$value = l($txt . format_plural($txt, ' member', ' members'), "node/$nid/members");
return check_plain($this->options['prefix']) . $value . check_plain($this->options['suffix']);
}
else {
return parent::render($values);
}
}
}
In short, this is the custom functionality that I want/prefer to use rather than the default field handler provided in the og_views module. You can further reference the files in the includes folder under that module for more reference on actually putting the handler together.
Step 3 - Tell views about your handler
Next, I want to alert views that I have a field handler that doesn't suck for my usage, and to use it instead. This is where I really found a roadblock as I hadn't needed to tackle this before, and there was only one tiny bit of docs I could find on this in a patch to the views docs that I don't see live anywhere. There are two functions that we will be placing in our MYMODULE.views.inc that we created and left empty just a moment ago.
This first function will look familiar to hook_theme(), and in essence, it is much the same. It tells views where to find functions to use to format certain objects.
So we have told views that we have a handler function, we have defined our handler function, and now our last issue to accomplish is to have views recognize that the handler function is put in place to overwrite another one that we don't like for whatever reason. To accomplish that, we need to use hook_views_data_alter(). Good luck finding docs on this one!!! It was in fact after kind of figuring out how to manipulate the $data array, VERY simple to implement the custom handler I'd created.
Step 4 - Tell views to use your handler instead of the default one
$data['og']['member_count']['field']['handler'] = 'MYMODULE_handler_field_og_member_count';
}
Conclusion
Wow, that was damn easy! The main thing is figuring out where in the $data array in hook_views_data_alter(). I was able to uncover all of that in the place where Organic Groups declares it's array of views data in og_views_data_og(). Using that array structure provided in the function that is providing the table data to views in the first place, you can see where the handler is located, and overwrite that appropriately using hook_views_data_alter().


Pretty cool stuff, but I'm
Pretty cool stuff, but I'm not quite sure how node/123/members is any nicer than og/users/123/faces. Marginally I suppose. I might've chosen something like group/123/members. Also would it have been easier to do this by overriding the output on the theme layer?
Tons of possibilities to override
There were a couple of options here to override this same data.
Using template_preprocess_page, it could have been done in order to pull out the data, and then reformat it. However, the default OG functionality added the link, and made it so when preprocess got the data, it was already a link, and I would have had to regex out the numeric value, and then generate my custom link. (Not a horrible solution)
It could have also been done using the views custom fields module, which allows you to attach PHP code inside a views field. This would have allowed me to pull the data I wanted, but exclude from display, and then use my custom PHP code field to access those values, and do pretty much the same thing. (Not the cleanest option)
Regarding the linking to node/$nid/members, yes, I agree, it's not the best option, but on the project I'm on, those links will later be altered using hook_menu_alter() to use a more appropriate path alias for these pages that were "attached" to OG groups. So in my method, that link could be named to anything under the sun, or altered later.
For me this was mostly an attempt to better understand views handlers, and get a wrap on what would be the "most appropriate" "views way" of doing something like this using some of those obscure views API functions that have little to no documentation. There are tons of ways to have accomplished this same thing that are all valid enough, and would work fine. I think the important thing here is maybe not my silly use case with OG, but other things where you may want to completely overtake the field formatting for an item. Maybe something like imagecache or lightbox, etc. would have a more valid reason to be overwritten on a site.
Jake Strawn
Drupal Rockstar
understanding field handlers
Jake,
when searching desperadly for a way to assign a handler to a field of a default-view I came across your blog.
My problem: When using a default view as a starting point I like to combine two fields into one (and setting a link to somewhere when a URL exists). In view-1 I had no problem defining fields and a handler which does the job. In views-2 it seems not to be possible to define this kind of handler. I tried to set a handler as follows
<code>
$handler->override_option('fields', array(
'biblio_number' => array(
...
'handler' => 'my_field_handler',
</code>
and defined / declared the handler as also described in this blog. But the handler is silently ignored.
1. Your approach was to modify the data structure. But when setting up the default view no extra data were defined (hook_views_data unused). This is only done once for a table.
2. As far as I understand the data structure is for defining structur an dependencies of database tables. This has nothing to do with displaying content - done by views - in various ways depending only on current needs. So, my first question is: Why should I modify the data structure to install a field handler? There might exist 10 different handlers in 10 different views for assembling a specific output.
I would be nice if my gap in understanding views-2 could be filled a little bit.
Regards
Simpler way?
Nice real world example ...
Is it not sufficient to use the Views built in features for 'Rewrite the output of this field' and 'Output this field as a link'? Those appear when you edit this Field in the Views UI. Seems to me like you can change the text and destination of the link with zero code.
If I'm not mistaken
Unless I'm mistaken, the reason what I want to do (at least in this case) couldn't be done using the rewrite output and/or output this field as a link.
Reason being, rewriting the output of the field was the placeholder for this feature until it was figured out. However, you cant run format_plural() inside there. The link to the default page that only wraps the numeric value is also output there, and any text, (members in this case) is output afterwards and not included in the link as the desired effect needed to have.
I think also the output this field as a link ran into the same issues as the link was generated in the handler, and not really able to override it? I don't remember what the result was of playing with that, it might be possible (sort of) with a combination of the two. One of the biggest issues was that appending member(s) or post(s) after result counts I couldn't use format_plural unless I was using that custom views fields module that lets you throw in PHP, and I consider that an evil alternative. :)
Sorry for bashing one of the OG functions for the purpose of this example Moshe!!
Jake Strawn
Drupal Rockstar
There is OG queue for
There is OG queue for that: http://drupal.org/node/558134
holy crap, that's exactly it!
I'll be damned... I swear I never even found that post when I came up with the handler override!!! That's too funny! Even searching on google with site:drupal.org views_data_alter didn't give me that result!
That sure would have saved me a crap load of time!! LOL
Jake Strawn
Drupal Rockstar
I mean, issue in the queue...
I mean, issue in the queue... :)
I'm glad that you didn't find
I'm glad that you didn't find it in the issue queue, because this is a great tutorial.
Thanks!
After trying unsuccessfully
After trying unsuccessfully to override the workflow module for a while I finally figured out the problem. Your tutorial says to "create the file MYMODULE.views.inc in the root of your module's directory" I was following workflow's convention of placing it in the includes folder. After re-reading your article I found this:
* Implementation of hook_views_api().
*/
function workflow_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'workflow') .'/includes',
);
}
stupid nuance that had be scratching my head. Also, hook_views_data_alter is cached and doesn't get run all the time. Hopefully those two tid bits help someone else out.
Post new comment