I've been digging and digging for some best practices around theming custom CCK fields in my content template files, found a number of great tutorials and posts (David Newkerk's guide is very helpful and well written - http://www.davidnewkerk.com/book/30 - and this one as well - http://drupal.org/node/266817). I'm still having trouble wrapping my head around the differing opinions and strategies though.

First method is to forget the $content var and just write your own HTML wrapped around your custom fields, spit out either directly ($node->field_myFieldName[0]["view"]) or using content_format().

CCK's content_format() function seems to be the preferred, best practice for getting at the 'view' value of your custom content field, with the added advantage of applying a formatter to the value. (Note for beginners: Formatters correspond to the values you see in the drop down on the "Display Fields" tab when you're managing your custom content type's fields. Took me a while to figure that out.)

That seems to be the most straight-forward method to me, but I guess it makes things messy for designers, having to juggle these content_format() calls and knowing the values of the custom fields. (If you're both the designer and the developer, I guess you're fine ;))

Second method: you can also write your own tpl files for your custom CCK fields. (create a "content-field-[field system name].tpl.php" file in your theme's folder) However, this only effects your field via the theme() function, the output of which doesn't seem to be accessible in the $node object. It just comes out through the $content. (I suppose you could get the themed output directly yourself if you knew how to call it (print theme('content_field',...) or something like that I'm guessing), but that seems pretty dangerous.)

Question 1: It looks like using tpl files for your custom fields is really only useful if you don't need to change the order of your fields, or place them in different parts of the page, write custom logic around their appearance, etc. Is that correct?

Question 2: Third method is using...preprocess functions? (http://drupal.org/node/378860#comment-1274366) I don't really quite have my head around this, but I think the jist is that you could write your own preprocess function (phptemplate_preprocess_node()) to muck with the contents of the $content variable so fields come out in your own custom order. Alternatively, you could create your own vars that contain the themed output of your fields. (ex. if you wanted to concatenate the themed output of Custom Field A and Custom Field B, you could put them together in a var called $field_A_and_B and the theme just needs to print that var out)

Is that about right?

Deciding between a method probably comes down to what you need to do with your theme, and your comfort level with Drupal's inner workings?

Thanks for your insight...

Comments

dnewkerk’s picture

Thanks for the compliment :D I'm always trying to improve my lessons/guides (the above mentioned one in particular), so I'm keeping an eye here to see if I can learn more as well, and add it to the lesson. I'm particularly interested in providing a good explanation for content_format() and how to use it in all the common cases for fields (single fields, imagefields, multi-value fields, etc). I haven't come across a complete-enough explanation of this yet, so any info or references (from anyone who knows) would be appreciated - and I'll expand my lesson once I understand this myself.

One partial response, regarding your Question 2: for me, preprocess functions are not really something I personally use in the sense I think you are describing to work with ordering or altering CCK fields (though there's no right or wrong way I'm sure - if it does what you want, and it's secure, go for it!). Here is one example of a snippet from the template.php of a site I'm working on, in the phptemplate_preprocess_page() function (I have similar snippets for _node but this is shorter/simple enough for the example):

function phptemplate_preprocess_page(&$variables) {
  // Login, Register, Logout, Account links
  global $user;
  if ($user->uid == '0') {
    $user_login = l(t('Register'), 'user/register') . '<span>or</span>' . l(t('Login'), 'user/login');
  }
  else {
    $user_login = l(t('Log out'), 'logout') . ' | ' . l(t('My Account'), 'user');
  }
  $variables['user_login'] = $user_login;
}

This could equally be placed (and in my case initially was) inside of page.tpl.php, in the spot of the theme were this functionality was to appear (the main difference being global $user wasn't added, that last line wasn't there, and of course just php open/close tags instead of the phptemplate_preprocess_page() function part). However that makes a mess of complex PHP inside my theme file, which would easily distress a themer who didn't know any PHP (such as myself not too long ago). In the theme file, a designer just wants to worry about theming as close to plain ordinary HTML as possible (and even "I" would rather not see that code cluttering up the theme file while I'm trying to work with the HTML and CSS). So instead, I cut and paste that code out of page.tpl.php and into template.php, in my phptemplate_preprocess_page() function. This part, ['user_login'] is where I define the variable that will be made available to page.tpl.php (though I use the same name in my function, it doesn't have to be). Then, using the variable name I chose at the bottom of the function, I can just write <?php print $user_login; ?> in my page.tpl.php. Whenever I make something like this I ensure it has a good HTML structure I can get at with precision using CSS, adding a container with ID or Class when necessary (so a themer would then not have to worry about ever touching that PHP... just check with Firebug to see how to get at it with CSS).

Others may feel differently, but personally I dislike the concept of having a separate template file per field. The quantity of files can quickly become overwhelming and confusing, and seeing everything in context is more difficult. It's also harder in instances where you have one snippet of code (say, an opening container div meant to hold a certain group of the fields) begin in one template file, remain open, and then close in another template file. On the other hand, in a single template file (preferably with proper indenting), this structure is instantly clear, and familiar enough to a themer accustomed to working with static HTML.

For simple cases when overriding $content may be unnecessary: though it also affects the order of fields on the Content Type's create content form (so may not be desirable), for basic re-ordering of fields, the drag and drop order of fields on the "Manage fields" page of the CCK type does affect the order in which fields are output to $content. Assuming you're ok with the same order on the create content page, for some cases this should be sufficient.

Anyhow, I hope this helps... and I hope to learn more myself as well :)

- David

smacphail’s picture

Thanks David, very helpful info!

re: preprocess...When you define your own preprocess function in your template.php file, is it overrideing the others (like a tpl) or is it adding in new variables to the chain?

I guess I was thinking that if you defined your own preprocess function, it overrides the default, and you could build the $variables['content'] any way you want. Doing so I guess would also mean hunting down the preprocess function for CCK as well, which I'm guessing adds the custom fields to the $content var. Sounds like a mission. :/

However, all the processing that goes into making the $content var just goes to waste if you're not doing anything with it. You'd just have to convince the client that yes, this is the order the fields have to go in on the edit/create screen. :P

Anyways, preprocessing definitely seems useful for making cleaner theme files, I'll definitely get more familiar with it!

re: tpl files for CCK fields...yea, I agree, I think this should be avoided. Less files to manage. :/

(Aside: Found a good post on keeping things slim from a theming perspective - http://www.palantir.net/blog/graycor-drupal-theming-works. Not completely related to "content_format() vs. tpl file" but some useful insights in there that could inform the discussion and other newcomers/intermediates.)

smacphail’s picture

re: preprocess...When you define your own preprocess function in your template.php file, is it overriding the others (like a tpl) or is it adding in new variables to the chain?

For those following this, the answer is that writing your own preprocess functions (phptemplate_preprocess_hook() or themename_preprocess_hook() inside your template.php file) does not overwrite other preprocess functions, they are executed along the chain (see list items 7 through 10 here, on the doc page about preprocess functions: http://drupal.org/node/223430)

smacphail’s picture

Thanks again for the tips Dave, they've been helpful for me to figure out how to best approach theming in Drupal.

My process so far has been to build code and logic out in the tpl's, and then refactor that code back into my own custom phptemplate_[whatever]() functions that I call from the tpl, or call them from within phptemplate_preprocess_node() or _page() functions and define $variables['whatever'] to be reused in the tpl or stuff them into the $node itself. (in the latter example, I have a content type called Article which has a sub-content type taxonomy tag, so it can be an Opinion piece or a Timeline or whatever; I'm reading the taxonomy value in a preprocess function, creating a css-friendly string value and dropping that into a $node property to spit out in the tpl)

The preprocess functions are really useful, but it feels like, as with most things, there's a number of ways of getting what you want done. Add a new property to the $node object, create a theme variable or just echo out the result of a custom function in your template.php file...anybody have any guidelines around what scenarios are handled best by which tactic? Is any one better than the other from a performance perspective?

Thanks!

icstars’s picture

for others who stumble here:

beware of using $node->field_name[0]['view'] vs. $node->field_name[0]['value']

the 'view' approach will respect permissions on that field, the 'value' approach will not. this means that even if the permissions has said anonymous users cannot see this field, your theme might blow that away and show that field to the public. yikes!

dnewkerk’s picture

Just to clarify for others... not only permissions issues for viewing field data, but actual "getting your site hacked" concerns. If the field accepts user input (e.g. a content type which your regular users can use) then it's possible for them or someone abusing their account to include malicious code (which Drupal would normally neutralize before allowing it to output to the page) which could cause them to be able to hijack another user's session, run code, steal information, etc -- because 'value' does not run the data through those filters before outputting it to the page. The "only" time I know of where it is ok to just use the '#value' version is for outputting the node body itself: <?php print $node->content['body']['#value'] ?> (which I understand in this case has been sanitized earlier in the process by Drupal) ... or if you are manually altering the filter you wish to use on the content and understand what you are doing (some examples in the book Front End Drupal). Other than that, always use 'view' or 'safe'. If you're ever unsure if a piece of data is sanitized or not, you can run it through check_plain() or check_markup() to be certain the data is cleaned/safe for output. Don't assume if your site doesn't give accounts/access to public users that it is safe to ignore either, as it is not too hard given the right bit of insecure code in a module you have installed, for an attacker to compromise the account of a trusted user.

Farreres’s picture

I just wanted to add a way to obtain the list of formatters available. If noone uses it, at least I write it here for myself in case I need it in the future:

drupal_set_message('<pre>'. print_r(_content_field_types(),true).'</pre>');

will render something like

Array
(
    [fivestar] => Array
        (
            [label] => Puntaje Fivestar
            [description] => Guardar un voto para este tipo de contenido.
            [module] => fivestar
            [formatters] => Array
                (
                    [default] => Array
                        (
                            [label] => Como Estrellas
                            [field types] => Array
                                (
                                    [0] => fivestar
                                )

                            [module] => fivestar
                        )

                    [rating] => Array
                        (
                            [label] => Puntaje (p.ej. 4.2/5)
                            [field types] => Array
                                (
                                    [0] => fivestar
                                )

                            [module] => fivestar
                        )

                    [percentage] => Array
                        (