Tuesday, April 28, 2009

change the default layout output of a node

Node theming by field: deconstructing $node->content in Drupal 5
Submitted by jody on February 11, 2008 - 11:53pm

* drupal
* theming

The CCK ‘display fields’ settings are very useful for theming nodes by content type, but I often find myself having to get further into customizing node output field by field.

Once I decide there’s no way getting around taking finer control of my node’s fields, I create a node-{type}.tpl.php by copying my existing node.tpl.php and open up Devel’s ‘Node Render’ tab. Then I delete the $content variable from the template and start adding things like
<?php
print $node->content['field_image']['#value'];
?>

and
<?php
print $node->content['body']['#value'];
?>

The $content variable is nice for amateurs, but we need the unrendered $node->content to get good control of a node.

While this gives me complete control over the output of the node’s fields, the drawback is that now if I add a new field to the node, or if I add a module that adds something to $node->content, I have to go back to my template and add in this new element. Because I often do my theming and development in parallel, this can be rather annoying, and there is also a danger that I could overlook doing it.

Therefore I think it may be more practical to use code like this in the node template:

<?php
foreach($node->content as $key => $field) {
print $field['#value'];
}
?>


Instead of printing each field that is wanted in the template, I can instead unset the fields that are not wanted and then print all the fields. This is faster to write and it lets any new fields show up. After unsetting unwanted fields it looks like this:
<?php
unset($node->content['field_dumb_field']);
unset($node->content['field_annoying']);
unset($node->content['#children']);

foreach($node->content as $key => $field) {
print $field['#value'];
}
?>


(I found that I need to unset $node->content[‘#children’] to get this method to work correctly.)

Now if you want to control the output of specific fields individually you could add a switch:
<?php
unset($node->content['field_dumb_field']);
unset($node->content['field_annoying']);
unset($node->content['#children']);

foreach($node->content as $key => $field) {
switch ($key) {
case 'body':
print '<div class="hot-body">'. $field['#value'] .'</div>';
break;
case 'field_awesome':
print '<div class="special-markup">'. $field['#value'] .'</div>';
break;
default:
print $field['#value'];
break;
}
}
?>


Now I know that these template files are not supposed to get all mucked up with php, so this method is really just a hack and won’t be appreciated if you are working with a designer who dislikes php. But if you, like me, were finding yourself dissecting $node->content into crazy pieces with tons of php in your node templates already, perhaps this method could be a slight improvement.
Comments
sirkitree (not verified) on February 12, 2008 - 3:40am

Yes it is better to get this done in template.php and then have your .tpl file clean for a designer, but this is a key step that should be learned on the way there. After that, if you find your template.php file getting overcrowded, you can split out your $vars into node-[content-type].vars.php.

* reply

jody on February 12, 2008 - 10:09am

I wonder if we could combine these two methods. In _phptemplate_variables maybe we could assign all the fields we want as $vars and also assign a variable to represent all the fields we neither assigned nor unset, like a catch-all.

* reply

adrinux (not verified) on February 12, 2008 - 6:25am

Fantastic write up! I think I’ll be using this.

* reply

moshe weitzman (not verified) on February 12, 2008 - 8:14am

very nice writeup. we want to improve this experience for themers in D7. they will have to use a few simple php functions but the temptation to override $content will be much smaller. we will introduce render() and hide() so you can put pieces of $content where you want without wrecking future enhancements. your writeup is current state of the art though - thanks for sharing.

* reply

jody on February 12, 2008 - 10:00am

Eaton’s goals in that thread seem to be exactly right - I’m really glad to see people are working hard on this issue.

* reply

Alan Burke (not verified) on February 12, 2008 - 8:45am

There’s a better way
See the theme folder of the CCK module
There’s a readme txt file which explains how to have individual template files for each CCK field.

Regards
Alan

I Thought I had added it to Drupal.org…but I can’t find it now

* reply

jody on February 12, 2008 - 9:36am

Hi Alan,

I checked out that CCK theme README and yes it does explain how to make template files for individual fields. I think that most of the time that won’t be helpful to me. A good example of what I’m most often trying to do with my fields is to just stick a few of them in the same div in the node template. I think a better example of what I’m doing may be:

<?php
unset($node->content['field_dumb_field']);
unset($node->content['field_annoying']);
unset($node->content['#children']);
?>


<div class ="details">
<?php
print $node->content['field_a'];
unset($node->content['field_a']);
print $node->content['field_b'];
unset($node->content['field_b']);
?>


</div>

</div class ="evertything-else">

<?php
foreach($node->content as $key => $field) {
print $field['#value'];
}
?>


</div>

* reply

Jacine (not verified) on February 12, 2008 - 8:33pm

Hi Jodi!

This is great. Thank you for posting.

I have a quick question. How would you do this for CCK fields that are inside a fieldgroup?

Thanks again :)

* reply

jody on February 13, 2008 - 8:44pm

That’s a good question- using fieldgroups makes this even more complex. Here’s an idea showing some fields from a fieldgroup being explicitly displayed, and then anything else in the fieldgroup gets printed last.

<?php
unset($node->content['#children']);
?>


<div class ="details">
<?php
// A regular field
print $node->content['field_a'];
unset($node->content['field_a']);
?>


</div>

<div class ="group-special">
<?php
//All fields within a certain fieldgroup
foreach($node->content['group_special'] as $key => $groupfield) {
if ($groupfield['#value']) {
print $groupfield['#value'];
}
}
unset($node->content['group_special']);
?>


</div>

</div class ="everything-else">
<?php
//All other fields including those in other fieldgroups
foreach($node->content as $key => $field) {
if ($field['#value']) { print $field['#value']; }
else {
foreach($field as $groupfield) {
if ($groupfield['#value']) { print $groupfield['#value']; }
}
}
}
?>


</div>

* reply

Jacine (not verified) on February 15, 2008 - 8:56pm

Thank you Jody! I was struggling with the fieldsets for quite a while. This is perfect :)

* reply

John (not verified) on March 3, 2008 - 12:45am

Thank you for this info. I have used it in a site and it has been helpful. I ran into an issue that unfortunately limits its usefulness however, in that the node content bit that I have pulled into a block is not rendering PHP. I have PHP enabled as the input format on both the node and the block. The PHP [a call to base_path()] shows in the source but is not processed as PHP. Has anyone experienced this and found a solution?

* reply

John (not verified) on March 12, 2008 - 1:38pm

I think i was trying to do too much with this. While i never did really find an answer to my issue, i did resolve the situation by using “CCK Blocks” to get my additional fields into blocks that would evaluate PHP.

So your technique was helpful in that i could add fields to a node but keep some of them from displaying, for example in a typical content-in-center-column layout. Then through CCK Blocks i could use those fields elsewhere on the page (left column in my case).

Thanks again for taking the time to post this helpful technique.

* reply

jody on March 12, 2008 - 1:46pm

I would be concerned if PHP was not evaluating in my blocks. I hope that you did the obvious and enclosed it in php tags. I have never heard of this issue.

As regards simply not showing certain of your cck fields, you can take care of this by visiting the ‘display settings’ tab of your content type’s administration. The technique posted here is intended for more advanced theming needs.

* reply

Martijn (not verified) on March 19, 2008 - 8:48am

Hi Jody,

Excellent writeup. This helps a lot with Drupal theming!
You yourself have a great theme also!

greetings,
Martijn

* reply

Chacha (not verified) on March 25, 2008 - 4:39pm

Hey!! So good to find your website. This write up is great. I am now going to try unset on my dumb annoying fields right now!

* reply

Marcus (not verified) on May 30, 2008 - 6:57pm

Jody,

Thanks for posting these node theming tips. The example code you set forth has been immensely helpful.

Much appreciated,
Marcus

* reply

Marcus (not verified) on May 30, 2008 - 6:58pm

Hi, Jody,

Thanks for posting these excellent tips on how to theme nodes.

Much appreciated,
Marcus

* reply

No comments: