Syndicate

Feed

Putting some order in your terms

In Wordpress, unlike in Drupal, terms are not lumped together in posts. Each Wordpress vocabulary has its own “template tag”, and the ones that come out-of-the box are: the_tags(), and the_category(). The following theming tweak is about putting order in Drupal terms before they're output to screen. It you need to break up your terms by vocabulary before you display them, read on.

Here's a Wordpress blog entry:

Terms presented by vocabularies in Wordpress

I will present an all-purpose solution that will print all terms by vocabulary. Each vocabulary list will be wrapped in its own HTML element, and I will use the vocabulary name as a 'label' for each list — a label you will be able to edit in the Administration Section of your Drupal site.

Solution

  1. Edit template.php to rebuild your $node->taxonomy array...

  2. ... and re-theme that array into a new $terms variable... and then...

  3. style your terms as needed in style.css.

Say you have two vocabularies, one for “free tagging”, and another for filing posts under sections, like so:

My vocabularies listed

You may not like your free tags to be lumped together with your “Filed under” terms.

You may prefer to see something like this:

Terms presented by vocabularies in Drupal

Let's get to it.

You will use a prepocess function for your node template. You will add this function to template.php if it has not already been defined. Open your theme template.php file in a text editor, and add the following code (please read the comments):

/**
* Override or insert PHPTemplate variables into the node template.
*/
function phptemplate_preprocess_node(&$vars) {
  // If we have any terms...
  if ($vars['node']->taxonomy) {
    // Let's iterate through each term.
    foreach ($vars['node']->taxonomy as $term) {
      // We will build a new array where there will be as many
      // nested arrays as there are vocabularies
      // The key for each nested array is the vocabulary ID.     
      $vocabulary[$term->vid]['taxonomy_term_'. $term->tid]  = array(
        'title' => $term->name,
        'href' => taxonomy_term_path($term),
        'attributes' => array(
          'rel' => 'tag', 
          'title' => strip_tags($term->description),
        ),
      );       
    }
    // Making sure vocabularies appear in the same order.
    ksort($vocabulary, SORT_NUMERIC);
    // We will get rid of the old $terms variable.
    unset($vars['terms']);
    // And build a new $terms.
    foreach ($vocabulary as $vid => $terms) {
      // Getting the name of the vocabulary.
      $name = taxonomy_vocabulary_load($vid)->name;
      // Using the theme('links', ...) function to theme terms list.
      $terms = theme('links', $terms, array('class' => 'links inline'));
      // Wrapping the terms list.
      $vars['terms'] .= '<div class="vocabulary taxonomy_vid_';
      $vars['terms'] .= $vid;
      $vars['terms'] .= '">';
      $vars['terms'] .= $name;
      $vars['terms'] .= ':&nbsp;';
      $vars['terms'] .= $terms;
      $vars['terms'] .= '</div>';
    }
  }    
}

Here is what the preprocess function does essentially:

Overwriting the $terms variable with a preprocess function.

The new HTML generated from print $terms (in node.tpl.php) is shown in this Firebug screen capture:

Terms are now printed by vocabularies


Creating PHPTemplate variables to pass on to your node.tpl.php template

As Opusoid recommends in a comment below, you can add each vocabulary to a new variable, that you pass on to your node template. In node.tpl.php, you can output each vocabulary variable wherever you wish. It gets super easy to place each vocabulary list in a precise location in the node: above it, below it, wherever you want. Brilliant, Opusoid, thank you!


Ordering vocabularies by weight — Improved version by manuee

Manuee modified the code snippet above to order vocabularies by weight. This is something you'll most likely prefer to do. After all, that's what weight is all about: presentation order. His code snippet is in his comment below. Thanks manuee!


Does that work in Drupal 5?

This solution will work in Drupal 6 only. Of course, there's an equivalent method for Drupal 5, and if someone asks for it I will provide it.

In Drupal 5, there was a function called taxonomy_get_vocabulary($vid) .This function has been renamed in Drupal 6 to taxonomy_vocabulary_load($vid). It has been renamed probably to bring naming consistency between functions that load objects, such as node_load() and user_load(). The function returns the vocabulary object matching the vocabulary ID $vid. The vocabulary object contains 'name' and 'description' properties, both of which you can use in your theme.


CSS styling

You may need to style your terms if you want them to appear on the same line, like so (these are rules added to the Garland theme style.css file):

/**
 * Terms styling rules
 */
 
.vocabulary {
  display: inline-block;
  padding-right: 1.5em;
}
 
.terms {
  float: none;
}
Last edited by Caroline Schnapp about 20 weeks ago.

Comments

Hiding a vocabulary

You may use taxonomy for organizing your content, but may wish to NOT display terms that belong to certain vocabularies in your node. With the solution I presented, it becomes very easy to do so.

You may use CSS like so if you wish to hide terms that belong to the vocabulary with ID 4:

.terms .taxonomy_vid_4 {
  display: none;
}

Or you can decide to remove this vocabulary from your $vocabulary array in your preprocess function like so:

// We get rid of the terms that belong to vocabulary no 4
unset($vocabulary[4]);

Module?

If you'd like others to also be able to easily display their terms grouped by vocabulary, it shouldn't be too hard to implement your code into a contrib module. Especially in Drupal 6, now that you can provide .tpl.php and _preprocess theme functions along with your module, as well as some default CSS styling.

Also, note that taxonomy_get_vocabulary() was renamed to taxonomy_vocabulary_load() in Drupal 6, because the new menu system automagically calls _load functions under certain naming conditions. In this case, The D6 taxonomy module defines the menu item 'admin/content/taxonomy/edit/vocabulary/%taxonomy_vocabulary'; as such, the menu system automagically calls 'taxonomy_vocabulary_load()' (by concatenating the string after the '%' in the menu item, with the string '_load'), when the callback for editing a vocabulary is invoked.

Plus, there are cleaner ways to hide terms from a specific vocabulary, than to hard-code excluded vid's in your site's theme. As a simple alternative, you could define a variable 'taxonomy_hidden_vocabularies', which would be an array of vocabulary IDs that your theme would refer to. For a more complex approach, have a look at the category module, which (as well as a million other things) lets you hide specific vocabularies from view (using the category_display sub-module). Note that I'm the author of this module, so I'm biased - the module is big and complex, and it isn't appropriate in many situations. A simple task like hiding specific vocabularies is NOT enough reason to consider using it!!

Nope

If you'd like others to also be able to easily display their terms grouped by vocabulary, it shouldn't be too hard to implement your code into a contrib module.

In this case, a module is not the best solution. If it's not a good solution for me, I do not consider it a good solution for others. A module is overkill here. This is a tweak. The tweak takes an array that has terms flattened and give it 2 dimensions. Re-theme. Nothing more.

I am a themer just as I am a module developer. I like to show people how to leverage what they see on the page within their theme — and show them how they have full control. Using the preprocess function, it is possible to rename labels, change whether you'll put : [colon] between the label and the list, and you can also create as many variables as you have vocabularies, and print them at different locations in the node view. So you could show the tags in one location, and the terms belonging to another vocabulary somewhere else. Say you have an e-commerce/ubercart site and have faceted vocabularies, like so:

By Garment
Blouse | Skirt | Jacket | Pants
By Color
Red | Azure | Green | Ash | Beige
By Fabric
Cotton | Wool | Rayon | Silk | Linen

You may want to iconize each term based on its vocabulary: garment, or color, or fabric.

Also, note that taxonomy_get_vocabulary() was renamed to taxonomy_vocabulary_load() in Drupal 6, because the new menu system automatically calls _load functions under certain naming conditions.

So that's the reason! Thank you for the insider info.

Plus, there are cleaner ways to hide terms from a specific vocabulary, than to hard-code excluded vid's in your site's theme.

I disagree. By the way, the part about hiding a vocabulary is not the GOAL of this theming exercise. It is simply something that is facilitated... as a side effect. That's why I mention it in a comment. Creating a variable in the theme? For whom? The person who will need to modify that variable is the same person who can do unset(..) in the preprocess function. Some people ask for these tweaks: hide this link, hide this term, etc. It is entirely clean and kosher.

A simple task like hiding specific vocabularies is NOT enough reason to consider using [my module]!!

Amen.

No effect

I'm new at drupal and I've tried to add your phptemplate_preprocess_node funtion in my theme template.php file, but afterwards it takes no effect. The themes variable remains the same in node.tpl.php. I've also tried to create a new variable $vars['myterms'] but it isn't passed to node.tpl.php.

Do I miss anything? It may be some other module that interferes?

Thank you in advance.

Sorry...

Do I miss anything?

Sorry... I've missed to clear the cache...
Thank you for your precious tutorials, I'm learning a lot from them.

No worries

Clearing the cache is a common thing we forget when theming Drupal 6.

THEMENAME_preprocess_node()

I guess it would be even more drupalish to use THEMENAME_preprocess_node() instead of phptemplate_preprocess_node().

Nevertheless thanks heaps for sharing! Cheers.

In theory for Drupal 6, maybe more drupalish

Both ways are fine, actually. However, take note of this: if you create an other theme for which this theme is your base theme, and you have named your function THEMENAME_preprocess_node() in your base theme, the tweak won't be passed down to the child theme, due to a yet unfixed bug as per the current Drupal 6 distribution, 6.5.

Naming [the function] after the theme engine will have it run in the sub-theme but name it after the base theme and [...] sub-theme's will ignore it.

It's been recommended to use the THEMENAME prefix in the Drupal 6 theming guide on Drupal.org (while it was recommended to use the engine prefix in Drupal 5) because of the new theme inheritance — if only that worked properly (but it will in Drupal 6.6). Using the PHPtemplate_ prefix makes your code more portable, but if you are working on a child theme where there's already a function that uses the engine prefix in the parent theme, you will get a PHP error for redefining the same function if you still use the theme engine prefix in the child theme. For now, this is what I do: I use the engine prefix in base themes, and use the THEMENAME prefix in derivated themes.

very exciting tutorial

very exciting tutorial - thank you very much!!

Exciting, eh?

Thank you :-D

Show some love for Drupal 5

We're still using Drupal 5 on our website (due to Panels, mostly.)

I'd really love to be able to use this feature, as right now, ALL terms are being shown on a node, and it rather sucks.

http://thebruns.ca/content/reds-dominate-fall-classic (see the bottom)

I'm trying to create bylines for nodes instead, and that's what led me to this wonderful little tut.

I'm trying to get this to work, but even after replacing taxonomy_vocabulary_load(vid) with taxonomy_get_vocabulary(vid), nothing changes.

I don't have performance caching enabled.. any help?

Yes please!

> This solution will work in Drupal 6 only. Of course, there's an equivalent method for Drupal 5, and if someone asks for it I will provide it.

Along with the previous commenter, I for one would really appreciate the D5 solution.

And just as a side note: having this as a module would be great - but then again I currently don't have a life outside of Drupal, and I know it'll be a learning experience :)

PS you make Previewing comments mandatory, OK but why captcha both then and when Posting?

Term Description?

HI.

I'd like to also display the description of the terms and then change the formatting a little to create a table (or list) with the terms on the left and the description of the term on the right.

Could you point me in the right direction?

thanks,
Sean

If you need code for this

I'd like to also display the description of the terms and then change the formatting a little to create a table (or list) with the terms on the left and the description of the term on the right.

If you need code for this, please request a quote on this page. I will get back to you shortly.

My fees for Drupal development and theming are 50$ US per hour.

Thank you Caroline!! Very

Thank you Caroline!! Very easy to apply and works wonderfully! I've linked to this article from this handbook page which hasn't been updated for Drupal 6 yet: http://drupal.org/node/42680.

Module

There is a module that does the same thing http://drupal.org/project/term_display

The method shown here is nice too. It is the one i'm using right now because i plan to play with the function to hide terms that are not important enough to list but important enough to tag the node with (using cck)

Vocabulary Weight

This is a great solution, but I notice it doesn't respect the vocabulary weight for the order of the vocabulary. How would you do this?

This is a great article! I

This is a great article!
I am curious, how would one go about creating a proper hierarchical viewpoint of terms so Taxonomy: Term 1 >> Subterm 1 >> Subterm 2 >> etc ?
I'm figuring I'd have to make a call to taxonomy_term_path($term), but I'm not sure where to go from there.

Awesome

Your tutorial is magically delicious. ;)
(It has been tough finding readable instructions and understandable code)

In case anyone's a lurking beginner like me: I did end up splitting the 2D array into a few array variables so I could put my "Categories" in one place in my layout and my "tags" in another. Very easy:

In template.php, after iterating through and building the 2D array, I wrote this:


$vars['tags']= theme('links',$vocabulary[4], array('class'=>'links inline'));
$vars['categories']= theme('links',$vocabulary[3], array('class'=>'links inline'));

If you're not sure what your vocab id (the index) is, hover over the "edit vocabulary" link in Admin/Taxonomy
and check your status bar.

Brilliant idea, Opusoid.

That's brilliant. Adding these 2 variables to pass on to your template. It becomes super easy to place $tags and $categories in very different locations... in precise locations in the node's HTML. Thank you for this. I will edit the tutorial to link to your comment.

Aw shucks!

It's all by the seat of my pants, so this is huge validation for me, Caroline - cheers!
:D
Chris

No, cheers to _you_, Chris.

No, cheers to _you_, Chris. I edited the tutorial with a link to your comment.

Help please

Could someone please tell me at which point I need to add:

$vars['tags']= theme('links',$vocabulary[4], array('class'=>'links inline'));
$vars['categories']= theme('links',$vocabulary[3], array('class'=>'links inline'));

in template.php? Where have we built the 2nd array?

Thanks, and I'm really sorry if this is hijacking this thread.

works great in drupal 6

Thanks for that solution - works great on drupal 6
cheers, sel

This is a great

This is a great documentation. Thank you for taking the time to share this. Also Opusoid's idea is brilliant.

Keep up the good work...

That's really sweet of you...

to give me this positive feedback.

It takes me a long time to write these tutorials. I wish I was a quick writer. There's so much I want to write about. Not just tuts on Drupal but tuts on Rails / Ruby / Regular Expressions / jQuery and of course Shopify. It takes me no time to reply to someone on a forum, I can even go and test the code first, whatever snippet I provide. But whenever I sit down to write a tut... different story.

Maybe I should develop a quicky style of tut.

Creating valuable things

Creating valuable things takes time. I think it is why you are taking much time to create these valuable tutorials :) Many people benefit from them...

Thanks again...

Sinan

Thank you!

Thanks for this tutorial - it worked very nicely! I used Opusoid's modification as well, so that I could have more control over the display of the category and the tags, so thanks to both of you!

Vocabulary weight doesn't work.

I've noticed that vocabulary weight doesn't work with this. Does anyone know how to sort the way they appear?

Example:

Tags (ID 10) has a weight of 0 and Category (ID 15) has a weight of -1 but they display as such:

Tags: term1, term2
Category: term1, term2

Switching the vocab weight does nothing, I expected it to looks like this.

Category: term1, term2
Tags: term1, term2

here's a quick modification to take the weight into account

// If we have any terms...
  if ($vars['node']->taxonomy) {
    // Let's iterate through each term.
    foreach ($vars['node']->taxonomy as $term) {
      // We will build a new array where there will be as many
      // nested arrays as there are vocabularies
      // The key for each nested array is the vocabulary ID.
      $vocabweight = taxonomy_vocabulary_load($term->vid)->weight;
      $vocabulary[$vocabweight][$term->vid]['taxonomy_term_'. $term->tid]  = array(
        'title' => $term->name,
        'href' => taxonomy_term_path($term),
        'attributes' => array(
          'rel' => 'tag',
          'title' => strip_tags($term->description),
        ),
      );
    }
    // Making sure vocabularies appear in the same order as the weight
    ksort($vocabulary, SORT_NUMERIC);
    // We will get rid of the old $terms variable.
    unset($vars['terms']);
    // And build a new $terms.
    foreach ($vocabulary as $vocabweight => $vid) {
      foreach($vid as $vid => $terms) {
        // Getting the name of the vocabulary.
        $name = taxonomy_vocabulary_load($vid)->name;
        // Using the theme('links', ...) function to theme terms list.
        $terms = theme('links', $terms, array('class' => 'links inline'));
        // Wrapping the terms list.
        $vars['terms'] .= '<div class="vocabulary taxonomy_vid_';
        $vars['terms'] .= $vid;
        $vars['terms'] .= '">';
        $vars['terms'] .= $name;
        $vars['terms'] .= ':&nbsp;';
        $vars['terms'] .= $terms;
        $vars['terms'] .= '</div>';
      }
    }
  }

To tell you the truth, we should write a patch to taxonomy module, I don't see any situation where different vocabularies would WANT to show in a big blob of links with no classification.

Thanks for the article btw!

Seems like your code is the same as mine

What's the difference?

My code:

// Making sure vocabularies appear in the same order.
    ksort($vocabulary, SORT_NUMERIC);

Your code:

// Making sure vocabularies appear in the same order as the weight
    ksort($vocabulary, SORT_NUMERIC);

A few things changed, do a

A few things changed, do a diff. Verified, this new example correctly displays the weight.

Oh yeah

I see it now, thanks.

In the tutorial I will add a link to your comment if you don't mind.

Done.

Thanks again.

Sure no problem

As far as what I did, basicaly I add another layer to the arrays, with the key of the weight so that when we sort it we use that instead of the vid.
$vocabweight = taxonomy_vocabulary_load($term->vid)->weight;
then use it before when setting the array $vocabulary[$vocabweight][$term->vid]....

After that i just adjust the rest of the code when building the links etc. by diggin one more level down:

foreach ($vocabulary as $vocabweight => $vid) {
      foreach($vid as $vid => $terms) {

Glad you found it useful, not the cleanest way to do it i'm sure, but hey, it gets the job done =)

Patch! Patch!

To tell you the truth, we should write a patch to taxonomy module, I don't see any situation where different vocabularies would WANT to show in a big blob of links with no classification.

YES. Very much agreed!

Or if for some reason the powers that be don't want that, maybe it could be an add-on module?

Oh yesss

To tell you the truth, we should write a patch to taxonomy module, I don't see any situation where different vocabularies would WANT to show in a big blob of links with no classification.

I agree wholeheartedly.

Thanks

Thank you so very much for this tutorial.

Drupal is a mixed blessing for the newbie coder, but it's because of members of its community (like yourself) that we beginners not only stick with it despite of the long hours of blankly staring at PHP code we don't understand, but also grow thirsty to learn how to do more with it.

I hope to one day be able to help Drupal beginners in the way that you helped me today. You're solidly in my bookmarks, Schnapp.

P.S.: Also thank you to Opusoid and manuee for their modifications.

Vocab Labels not wrapped in own HTML

The vocab names are not wrapped in any of there own HTML, making it a little difficult to style them, so I changed the code (that took into consideration weights) so that it had a span with a class of "label" around it. Thanks for the great info! And +1 for the patch. Here it is:

function phptemplate_preprocess_node(&$vars) {
// If we have any terms...
  if ($vars['node']->taxonomy) {
    // Let's iterate through each term.
    foreach ($vars['node']->taxonomy as $term) {
      // We will build a new array where there will be as many
      // nested arrays as there are vocabularies
      // The key for each nested array is the vocabulary ID.
      $vocabweight = taxonomy_vocabulary_load($term->vid)->weight;
      $vocabulary[$vocabweight][$term->vid]['taxonomy_term_'. $term->tid]  = array(
        'title' => $term->name,
        'href' => taxonomy_term_path($term),
        'attributes' => array(
          'rel' => 'tag',
          'title' => strip_tags($term->description),
          'class' => 'tag',
        ),
      );
    }
    // Making sure vocabularies appear in the same order as the weight
    ksort($vocabulary, SORT_NUMERIC);
    // We will get rid of the old $terms variable.
    unset($vars['terms']);
    // And build a new $terms.
    foreach ($vocabulary as $vocabweight => $vid) {
      foreach($vid as $vid => $terms) {
        // Getting the name of the vocabulary.
        $name = taxonomy_vocabulary_load($vid)->name;
        // Using the theme('links', ...) function to theme terms list.
        $terms = theme('links', $terms, array('class' => 'links inline'));
        // Wrapping the terms list.
        $vars['terms'] .= '<div class="vocabulary taxonomy_vid_';
        $vars['terms'] .= $vid;
        $vars['terms'] .= '">';
        $vars['terms'] .= '<span class="label">';
        $vars['terms'] .= $name;
        $vars['terms'] .= ':&nbsp;';
        $vars['terms'] .= '</span>';
        $vars['terms'] .= $terms;
        $vars['terms'] .= '</div>';
      }
    }
  }
}

Works great!

This is amazing, your code solved my issue. I had a custom content type (using CCK) that had tags but the main category term was being thrown in with the tag listing which I did not want.

I thought this was going to be a needle in a haystack trying to find a solution but after a 30 second Google search I found this article, thanks again!

Thats helps a lot

thx for sharing this. I still learn css & html .. but this is great for new php sites!

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <css> <html> <javascript> <mysql> <php> <span> <a> <b> <i> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <sup> <sub> <dd> <del> <blockquote> <img> <q> <p> <div>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <css>, <html>, <javascript>, <mysql>, <php>, <rails>, <ruby>.

More information about formatting options

CAPTCHA
I have to wonder if you're a human spammer or a machine, or less likely someone who cares to leave his or her thoughts behind.