Fine-grained control of templates.

***BIG WARNING:*** In a non-modified Drupal installation (you can use the Devel module to switch this off, but...), any code which manipulates preprocessors and theming needs a cache refresh and relevant page refresh before you see anything.

If you try the code on this page,
EMPTY CACHE AND REFRESH TEST PAGES

What they do

These functions generate template suggestions which give a designer very close control over templating in Taxonomy Treemenus.

There is a down side to using this on this page. Any generalized preprocessing involves extra function calls and probably code filtering. Finally, this could become a performance issue. Also, the code will generate huge numbers of suggestions on deep or complex multiple menus. This may become the critical performace factor?

Where to put the code

These functions work on data either from external functions or provided by base templates. So they can be put anywhere within the preprocessing chain.

There is a performance issue, not critical, but best avoided, when using engineName_preprocess() and engineName_preprocess_hook() in Drupal 6, see Setting up variables for use in a template (preprocess functions)

We have used phptemplate_engine_preprocess for the examples, but you could also use preprocessors within the theme itself, such as themeName_preprocess(). Note also that we have often used the hook specific extensions, such as phptemplate_engine_preprocess_node(). If you only need to alter pages or nodes, it should more efficient to be more specific in your code. It is also clearer...

Where they work

Much of this will work in general. The code is not Taxonomy Treemenu specific.

taxonomy_treemenu_get_tid_ancestry() 
needs Taxonomy Treemenu installed (can be done otherwise - taxonomy_get_ancestry(), but cheaper if the module is there).
taxonomy_treemenu_find_css_file()
is a stand-alone function, and could even be ripped out of the module (if you feel that way).

General Discussion

Druapl derives its automatic template suggestions from the URL. So node/node% can be targeted with a template 'node-9.tpl.php' A page containing a node also carries taxonomy parantage data for the node, which would seem to be helpful.

This breaks down for two reasons.

Issue one is that data will be dropped for non-stock URLs, for example, Taxonomy Treemenu's extended URL feature. Node pages accessed through extended URLs lack any node, and so term, data! To Drupal, they are a dumb box; a raw page with something in it, passed through from somewhere else.

The other issue is that Drupal's theming does not handle the taxonomy data, just the URL. If the URL has no taxonomy data, by default, no template suggestions are generated which refer to the Taxonomy.

The upshot is this. If you want to theme part of a Taxonomy Treemenu, you need a template for every single node, which is obscure and hard to maintain.

The following code snippets are ways of reducing your template code. Some are expensive, some have limitations. But all of them will make a large reduction in template code, making it much easier to maintain.

Sub-templating

Controlling templating by sub-templates

First, if you have code which is repeated through templates, here is a useful trick. It offers no performance advantage (though if you have many css files you may try assembling the templates on the client side, using jQuery. Finally, this suggests a client-side AJAX site, though that has it's own problems).

Call some different templates as you are already. If you want to style a Treemenu, the templates are probably multiplying fast, with very repetitive material. for example, they may all have the same body code. So try this...

Rip out the body contents and put them in a separate file, called, say, myThemeName-view-node.php. And put something similar to this into the templates where the body should be,

  <body id='cdvpage-node-body'>

    <!-- node template -->
   <?php require_once(drupal_get_path('theme', 'myThemeName') . '/view-ttm-node.php');
 ?>
    <!-- EOF node template -->

  <?php print $closure ?>
  </body>
Now the template files, though multiplying, are MUCH smaller. Also, you have the advantages of encapsulation; less likely for a rogue template to throw hard-to-trace errors, easier to change the base template in one edit.

Did you wonder where this idea comes from (all ideas come from somewhere)? It's from Ruby on Rails, another website construction framework. Rails allows you to create many and varied sub-templates which are barely snippets (as though Drupal's themes were consistent with the template code). Ruby on Rails has it's own set of usage issues; Rails sites are near-impossible to deploy without a dedicated server, and it is hard for a non-professional to build a secure and fully rounded site. But Rails is way ahead of Drupal in this area. That's a personal opinion, of course.

Control over node templates

Fine-grained control over node templates, dependant on position in the taxonomy.

Summary

In myTheme/template.php add,

function phptemplate_engine_preprocess_node(&$vars) {
  $node = $vars['node'];
  if ((module_exists('taxonomy')) && !empty($node->taxonomy)) {
    foreach ($node->taxonomy as $term) {
      $vars['template_files'][] = 'node-tid-'. $term->tid .'-descendant';
    }
  }
}

Fine-grained control over node templates, dependant on position in the taxonomy, for Treemenu extended URLs

The problem with extended URLs is that Drupal's base template function (template_preprocess_page()) will search for a stock node route (?q=node/%node), and so fail to find the node data for the extended URL. This would seem to be an ugly situation, as templates have no mechanism to draw in initial data as variables. So, unless Treemenu implements its own template, the useful node/term data gathered from menu and page routing is dropped. Therefore, a designer can not use the simple method above.

However, the following solutions for page templating will work. So you could modify one of them, but for a node template and maybe a node preprocesser.

Control over page templates

Page templates for stock urls, dependant on the active term path through the taxonomy.

This is reasonably easy, as Drupal's page template preprocessor glues node data into the page preprocessor data. So all a designer needs to do is modify the node code given above, for a page template. Sorry, no example yet.

Page templates for extended urls, dependant on the active term path through the taxonomy

For extended URLs, we now have no taxonomy or node data passed through to the preprocessor and templates. The preprocessor code code must retrieve the data. The obvious and unfortunate approach would be a complex and semi-recursive taxonomy search (taxonomy_get_ancestry() would work).

Fortunately, Taxonomy Treemenu carries term parentage within it's menu links. So we have built a function into Taxonomy Treemenu to retrieve a taxonomy ancestry. The function is probably as efficient as Drupal's own node template taxonomy search.

In myTheme/template.php add,

function phptemplate_engine_preprocess_page(&$vars) {
  $q = $_GET['q'];
  $qa = explode('/', $q);
  if ($qa[0] == 'ttm' && $qa[2]) {
    foreach (taxonomy_treemenu_get_tid_ancestry($qa[1], $qa[2], $qa[3]) as $tid) {
      $vars['template_files'][] = 'page-node-tid-'. $tid .'-descendant';
    }
  }
}
Now that is very Drupal, and not un-efficient (if you must use preprocessors).

Control over css, using base templates

So, you are an organised person, who believes in the web credo of content separated from style ("The way it should be done"). So you only have maybe two templates for your site, but you are hoping you can identify parts of a treemenu using css. A crude example; you wish to turn the corporate parts of your site royal blue.

If you hoped you could follow the model above you are in for a nasty shock. This is an unresolved area of Drupal code, see this blog on preprocessing anything other than template suggestions. The problem is, while Drupal processes template suggestions near the end of the theming pipeline, most other content processing (css, content, messages, and javascript) is done near or at the start. So this, in a preprocessor,

    $vars['css'] = drupal_add_css(drupal_get_path('theme', 'myThemeName') .'/test.css', 'theme');
is valid code, but won't get rendered in, as the css has already been processed.

As a module designer, you would hope to put the information in first, because we already have the data when the module processes a URL. However, as mentioned, Drupal will not allow modules to provide variables beforehand.

Failing that, you would have hoped that the css, and other information, was processed after the preprocessors, not before. To add to this, it would be great if css (and other data) followed the scheme of template suggestions. Then a designer could have named files such as,

node.css
front-page.js
Though I can see there might be reasons of overriding, and what is called 'aggregation', which would prevent this.

Anyhow, you can,

Or you can try the fixes listed below. Read on!

Adding css, based on named files, using different templates

This is the most efficient and least flexible solution. You keep the usual Drupal template suggestions, but give them individual css. You then reduce the redundancy by using the sub-template code mentioned earlier. The css is locked to the template, so you would only use this solution in a situation, say, where you have very general templates (one per Treemenu?) that you wished to style differently (but they share the same body code).

In each template, inject css snippets like this,

  <head>
    <title><?php print $head_title ?></title>
    <?php print $head ?>
    <?php print $styles ?>

    <!-- Our stylesheet -->
 <?php 
$filepath = drupal_get_path('theme', 'myThemeName') .'/targetFile.css' .'?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
print '<link type="text/css" rel="stylesheet" media="all" href="'. $filepath .'" />'."\n";
?>
    <!-- EOF our stylesheet -->

    <?php print $scripts ?>
    
  </head>
  
That works, but you are now multiplying templates again, with essentially the same body contents. So use the sub-template code (described above) to extract the template body into a separate file.

Adding css, dependant on the active term path through the taxonomy, using a custom template variable

This should never really be done. The code should be in an admin interface, or automatic. However, you could use a switch.

This is probably the solution to choose if you want to change the css on only one template, and when code efficiency is important.

Hiding the switch in the preprocessor is hard to maintain, so this example puts the switch into the template. This is also a bad place, because templates shouldn't contain code logic - but needs must.

Do something like this in template.php (template suggestion code left in as you may want them too)

function creakdv_preprocess_page(&$vars) {
  $q = $_GET['q'];
  $qa = explode('/', $q);
  if ($qa[0] == 'ttm' && $qa[2]) {
    $ta = taxonomy_treemenu_get_tid_ancestry($qa[1], $qa[2], $qa[3]));
    foreach ($ta as $tid) {
      $vars['template_files'][] = 'page-node-tid-'. $tid .'-descendant';
    }
    /* Key line. Adds a template variable with all the tid ascendants (to a treemenu extended url page) */
    $vars['tid_ancestory'] = $ta;
  }
}

Right, now you can do something like this in the template,

 <?php 
  // NOTE: $tid_ancestory is provided by preprocessor X.
  if (in_array(2, $tid_ancestory)) {
    $targetfile = target1;
  } 
  elseif(in_array(5, $tid_ancestory)){
    $targetfile = target2;
  };
  
  $filepath = drupal_get_path('theme', 'myThemeName') .'/'. $targetfile .'.css' .'?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
  print '<link type="text/css" rel="stylesheet" media="all" href="'. $filepath .'" />'."\n";
  ?>
This solution offers no performance advantage, but slashes code to a minimum, which is good. It isn't very elegant or maintainable, but at least the switching is in a sensible place. And your code is reduced to necessary templates only.

Adding css, by similar name, using taxonomy_treemenu_find_css_file()

And now, a helper function built into Taxonomy Treemenu. The function is used to search the theme folder for css files with the same name as the template suggestions. This solution requires a separate template for each change in theming, but is well-organised. It requires no preprocessor code at all. This is probably the code of choice if you would like some variations in the HTML (template code), with snippets of extra css.

So take, for example, a template such as page-node-tid-3-descendant.tpl.php. You name the special css file as page-node-tid-3-descendant.css, and put it in the same folder. The function will find the file, and render it, and you can use the result.

There are two ways of using the function,

  1. Place the helper into a preprocessor and pass the results as a new variable (as in the code from the previous section).
  2. This would be more appropriate for a fully committed site.
  3. Place the function straight into the template.
We illustrate the second option below.

Make up a .css file with the extra css, give it the same name as the template, and place it in the same folder.

In the template, place something like this,

  <head>
    <title><?php print $head_title ?></title>
    <?php print $head ?>
    <?php print $styles ?>

    <!-- Find the similar stylesheet -->
 <?php 
print taxonomy_treemenu_find_css_file($directory, $template_files);
?>
    <!-- EOF Find the similar stylesheet -->

    <?php print $scripts ?>
    <!--[if lt IE 7]>
      <?php print phptemplate_get_ie_styles(); ?>
    <![endif]-->
  </head>
That's it. When the template is called, the .css file with a similar title is also loaded. No switches.

Like the other suggestions, this idea does not aggregate the css. It is untested on sub-themes and the file MUST be in the same folder as the template. Mess round with the template suggstions too much and it could get confused. Also, the code is even slower, as it does a directory search. But it is a Drupalish solution, and easy to maintain.

Adding css, by tid, using taxonomy_treemenu_find_css_file()

At this point you may have had enough, I guess. But there is another possibility, the most expensive solution to css variation by far. However, it is nearly as good as the code above for maintainability, and by far the best for DRY, efficient coding. Still interested?

This solution classifies additional css files by general names. It requires preprocessor code, but in this case the preprocessor code is generic, so once added, the code will never need altering.

Here is the full declaration of taxonomy_treemenu_find_css_file(),

/**
 * Find and render a css file from an array of identifiers.
 * @param $dir
 *   To search in. No recursion.
 * @param $fids
 *   An array of partial filenames, to select from.
 *   The array is reversed and the first match wins.
 * @param  $filename_prefix
 *   To add to the fids, for matching against existing files.
 * @return
 *   A rendered css line, ready for a template.
 */
function taxonomy_treemenu_find_css_file($dir, $fids, $filename_prefix = '') {
...
}

So you can do this. Put the following code into your template.php file. This generates a tid ancestory variable (a little like the above, but no need for the template suggestions, as we now can use one template),

function creakdv_preprocess_page(&$vars) {
  $q = $_GET['q'];
  $qa = explode('/', $q);
  if ($qa[0] == 'ttm' && $qa[2]) {
    /*Key line. Adds a template variable with all the tid ascendants (to a treemenu extended url page) */
    $vars['tid_ancestory'] = taxonomy_treemenu_get_tid_ancestry($qa[1], $qa[2], $qa[3]));
  }
}

Now put this into the template,

    <!-- Find tid based stylesheets -->
 <?php 
  // NOTE: $tid_ancestory is provided by preprocessor X.
  print taxonomy_treemenu_find_css_file($directory, $tid_ancestory, 'page-node-descendant-tid-');
 ?>
    <!-- EOF Find tid based stylesheets -->
and add, to your theme folder, stylesheet files such as page-node-descendant-tid-5.css and page-node-descendant-tid-47.css. Now you have just one template, and it will call extra css depending upon the tid ancestry of it's node content. You can create a Web2 XTML compliant utterly modern 'blink' site in minutes...

NOTE: The prefix can be anything you choose. We suggest

The downsides are that this is the most expensive solution. And you may feel that you shouldn't have to do this to add some dependant css action. The taxonomy ancestry may get confused or too general on large or multiple ancestry sites. This code needs some preprocessing, which the previous suggestion avoided. The file identification tag must go at the end of the filename. Otherwise, in terms of maintainability and code reuse, this is good.