Help understanding hook_theme & #theme on the form element level

himerus - June 20, 2009 - 23:56

Okay, I've been pouring over the API documentation found at api.drupal.org related to form reference and I can't find the answer or an example of what I need or am overlooking in my situation.

The Situation

I am building a module that needs to simply theme some form elements (not the entire form) very specifically. In this example, radio and checkbox groups.

Here is the code I'm currently trying to massage into working.

hook_theme

<?php
function taxonomy_rockstar_theme() {
  return array(
   
'tr_layout_checkboxes' => array(
      
'arguments' => array('element'),
      
'file' => 'taxonomy_rockstar.theme.inc',
      
'function' => 'theme_taxonomy_rockstar_layout_checkboxes',
    ),
   
'radios' => array(
      
'arguments' => array('element'),
      
'file' => 'taxonomy_rockstar.theme.inc',
      
'function' => 'theme_taxonomy_rockstar_layout_radios',
    ),
  );
}
?>

The theme functions

<?php
function theme_taxonomy_rockstar_layout_checkboxes($element) {
 
krumo($element);
 
drupal_set_message('using <strong>theme_taxonomy_rockstar_layout_checkboxes()</strong>');
}
function
theme_taxonomy_rockstar_layout_radios($element) {
 
krumo($element);
 
drupal_set_message('using <strong>theme_taxonomy_rockstar_layout_radios()</strong>');
}
?>

The relevant form items

<?php
$form
['tr']['tr-used-layouts'] = array(
   
'#type' => 'checkboxes',
   
'#title' => t('Available Layouts'),
   
'#description' => t('You may choose to enable/disable layouts that are by default enabled by sub modules, and exclude them from the specific vocabulary setting pages. This will prevent admins from selecting one of those layout options for a vocabulary, AND will not allow it to be chosen by users who have the ability to modify the filter/interface. <em>If NO options are selected here, it will be assumed you do NOT want to filter these layout types</em>'),
   
'#required' => FALSE,
   
'#default_value' => variable_get('tr-used-layouts', array()),
   
'#options' => taxonomy_rockstar_generate_layouts(),
   
'#theme' => 'tr_layout_checkboxes',
  );
$form['tr'][$vocab->vid]['tr-layout-'. $vocab->vid] = array(
   
'#type' => 'radios',
   
'#title' => t('Layout Type for '. $vocab->name),
   
'#description' => t('Selecting an option here will make this layout the default rendering type for the specified vocabulary.'),
   
'#required' => TRUE,
   
'#default_value' => variable_get('tr-layout-'. $vocab->vid, array()),
   
'#options' => taxonomy_rockstar_generate_layouts($vocab->vid),
  );
?>

The explanation

The taxonomy_rockstar_generate_layouts function is providing me an appropriate list of options in either case, so there is no issue there. The problem is related to using the theme functions ONLY on my specified form elements.

In the example I have provided, BOTH of the custom functions are being called... notice teh #theme though in the checkboxes form element. When the radios are called, it works just fine, HOWEVER, it is being used obviously for EVERY radio button rendered on the site... which is bad in my case.

The checkboxes element works the same way if I comment out the line '#theme' => 'tr_layout_checkboxes',, and rename the appropriate item in hook_theme to simply checkboxes. When I do uncomment the line, and am doing my testing, it IS calling the custom function appropriately, BUT it is breaking my $element somehow, and I'm not getting my #children to be rendered as normal. The #title and #description of the checkboxes element is being rendered twice, and the checkboxes themselves are not being rendered at all.
(BESIDES the debugging info in my custom theme functions, currently the other code inside there is default from the current API functions)

I'm simply a bit stuck, as there's no real documentation on how to use #theme on the form element level. (only on the $form itself)

Any help would be greatly appreciated. I think I've been staring at this too long.

You shouldn't define a

dvessel - June 21, 2009 - 00:43

You shouldn't define a function since that's handled automatically while the registry is being processed. Notice there's no mention of defining a function for hook_theme.

<?php
function taxonomy_rockstar_theme() {
  return array(
   
'taxonomy_rockstar_layout_checkboxes' => array(
      
'arguments' => array('element' => NULL),
      
'file' => 'taxonomy_rockstar.theme.inc',
    ),
   
'taxonomy_rockstar_layout_radios' => array(
      
'arguments' => array('element' => NULL),
      
'file' => 'taxonomy_rockstar.theme.inc',
    ),
  );
}
?>

The "theming hook" is that key in the upper most level in the array. It is used to find the real function as it will be prefixed with 'theme_' automatically for the default implementation and allow theme overrides with 'THEMENAME_taxonomy_rockstar_layout_checkboxes'.

edit: oh, and the problems related to using #theme in a form I'm not sure about. I just wanted to let you know on the obvious error on hook_theme.

And it looks like defining a function is valid from the hook_theme api so I was wrong, but I never seen it done and I wouldn't recommend anyone use that since it would get really confusing with theme overrides.

And don't forget to add the arguments in a pair. 'argument' => DEFAULT_VALUE What you have doesn't have the key, only a value which is wrong.

joon park
www.dvessel.com

Good info....

himerus - June 21, 2009 - 01:05

Thanks for the reply. It does still work the same if I remove the function => 'function_name'.

the functions ARE being called appropriately, but it seems now that it just isn't sending my $element appropriately. I added in the 'element' => NULL, which was there before, with no change.

The actuall tr_layout_radios function looks like this

<?php
function theme_tr_layout_radios($element) {
   
//krumo($element);
   
drupal_set_message('using <strong>theme_taxonomy_rockstar_layout_radios()</strong>');
 
$class = 'form-radios';
  if (isset(
$element['#attributes']['class'])) {
   
$class .= ' '. $element['#attributes']['class'];
  }
 
$element['#children'] = '<div class="'. $class .'">'. (!empty($element['#children']) ? $element['#children'] : '') .'</div>';
  if (
$element['#title'] || $element['#description']) {
    unset(
$element['#id']);
    return
theme('form_element', $element, $element['#children']);
  }
  else {
    return
$element['#children'];
  }
}
?>

It just breaks somewhere when I'm ONLY applying it to my elements. If I were to rename the hook in hook_theme to just radios, it works and works for ALL radios...

Some of this type of theming still puzzles me. Something I could do in 10 minutes with jQuery baffles me the "Drupal Way".

Thanks again for the reply!

Jake Strawn
Personal Blog - http://himerus.com

the actual output

himerus - June 21, 2009 - 01:08

The actual output being given using this theme function is:

Layout Type for bojereu: * <------ LABEL
Layout Type for bojereu: * <------ LABEL
Selecting an option here will make this layout the default rendering type for the specified vocabulary. <----- DESCRIPTION
Selecting an option here will make this layout the default rendering type for the specified vocabulary. <----- DESCRIPTION

quite odd. I did some debugging, and it is hitting the return theme('form_element', $element, $element['#children']);, just not doing anything with the children. And the $element array being sent to the function is IDENTICAL as to when it DOES work when I apply it to all radios...

Jake Strawn
Personal Blog - http://himerus.com

You are clearing the

dvessel - June 21, 2009 - 01:16

You are clearing the registry, right? Looks like your version of the function is very similar to the default so it should just take over once the registry is cleared.

If you don't clear it, when processing that form, it won't know about your changes and do nothing with it..

joon park
www.dvessel.com

Yep, theme registry is cleared...

himerus - June 21, 2009 - 01:38

yeah, it is cleared... I used a VERY similar function in another module recently, but I declared the theme functions using theme_registry_alter, and it was a situation where it was applying to EVERY implementation of a specific form element. I got around it by looking for the path in my theme function, and if it wasn't the one I wanted, I would just call the default function from there. very hacky....

I was going at it this way as it seems the most appropriate way to do it, and make sure it's only being called on the sections/forms I want it to be used for.

The biggest thing that I think is my hangup is the theming on the element level. I can't find a single post on d.o that relates to that specifically.

And as of now, the function being called is exactly the same as the default. AND it does work if I stop trying to make it specific and let it apply to EVERY call of checkboxes or radios, it works like a champ.... so stoopid... sometimes I wanna go work at a construction site, and give up code forever! :D

Jake Strawn
Personal Blog - http://himerus.com

It's been a while since I

dvessel - June 21, 2009 - 02:06

It's been a while since I themed something on a form element. I vaguely remember overriding the default and checking from the form ID (or was it that $element['#id'] ?) to redirect the processing and that was done from the theme. Doing it from a module can get especially nasty if a theme were to do something with that.

If there was a cleaner alternative, I'm just not aware of it.

Don't give up! Drupal is getting better all the time. :)

joon park
www.dvessel.com

Change the 'radios' key in taxonomy_rockstar_theme()

jbrinley - June 27, 2009 - 19:08

You might try changing the 'radios' key in taxonomy_rockstar_theme() to something else.

Be default, Drupal looks for the function theme_YOUR_KEY() (in this case, theme_radios()). But you're telling it to use theme_taxonomy_rockstar_layout_radios() instead of theme_radios(). Try:

<?php
function taxonomy_rockstar_theme() {
  return array(
   
'taxonomy_rockstar_layout_checkboxes' => array(
      
'arguments' => array('element'),
      
'file' => 'taxonomy_rockstar.theme.inc',
    ),
   
'taxonomy_rockstar_layout_radios' => array(
      
'arguments' => array('element'),
      
'file' => 'taxonomy_rockstar.theme.inc',
    ),
  );
}
?>

 
 

Drupal is a registered trademark of Dries Buytaert.