Here is an example of how to theme the layout of a node form using the theme engine in Drupal 7. We will theme the article content type provided by default with Drupal 7.

Suppose that the article content type has a taxonomy field field_tags - I want to put the taxonomy field in a separated region called sidebar.

Also the form buttons will be extracted and printed as $buttons variable in a separate region.

Please note that Node form columns module can be used as an alternative method for this process.

First of all let's implement hook_theme(). You can implement it in your theme's template.php or your custom module. In our case we will do it in a custom module. Let's call it MYMODULE.

Our theme output will be through the template file article-node-form.tpl.php.

<?php
/**
 * Implements hook_theme().
 */
function MYMODULE_theme($existing, $type, $theme, $path) {

  return array(
    'article_node_form' => array(
      'render element' => 'form',
      'template' => 'article-node-form',
      // this will set to module/theme path by default:
      'path' => drupal_get_path('module', 'MYMODULE'),
    ),
  );
}
?>

Then, we should define the preprocess function, for the theme hook article_node_form.
Here it will prepare the variables, sent to the template file.

<?php
/**
 * Preprocessor for theme('article_node_form').
 */
function template_preprocess_article_node_form(&$variables) {

  // nodeformcols is an alternative for this solution.
  if (!module_exists('nodeformcols')) {

    $variables['sidebar'] = array();   // Put taxonomy fields in sidebar.

    $variables['sidebar'][] = $variables['form']['field_tags'];
    hide($variables['form']['field_tags']);

    // Extract the form buttons, and put them in independent variable.
    $variables['buttons'] = $variables['form']['actions'];
    hide($variables['form']['actions']);
  }
}
?>

After that, create the template page in the module directory.
In our case the template page will be: modules/MYMODULE/article-node-form.tpl.php. And its code :

<?php echo drupal_render_children($form)?>

And you can edit the file such as:

<div class="node-add-wrapper clear-block">
  <div class="node-column-sidebar">
    <?php if($sidebar): ?>
      <?php print render($sidebar); ?>
    <?php endif; ?>
  </div>
  <div class="node-column-main">
    <?php if($form): ?>
      <?php print drupal_render_children($form); ?>
    <?php endif; ?>
   
    <?php if($buttons): ?>
      <div class="node-buttons">
        <?php print render($buttons); ?>
    </div>
  <?php endif; ?>
  </div>
  <div class="clear"></div>
</div>

Please note that we used drupal_render_children($form); in Drupal 7 to render the rest of the elements of the form. It is essential to call this function as the form submit won't work otherwise.

The output of "drupal_render_children($form);" for the form with all the hidden elements looks like this:

<input type="hidden" name="form_build_id" value="form-9DoBKF-JQBgOvH7V6ygTI_-E9FiD77VGM5tzz6KFB_0" />
<input type="hidden" name="form_token" value="Ilw23wpvQ_NZ-wAV_VeMOgE-tfJ3wf4PBQ_XHIL18cI" />
<input type="hidden" name="form_id" value="my_super_form_id" /> 

And it's the musthave part of html for the form to work.

Finally, create your own CSS styling :)

Comments

mlncn’s picture

shouldn't template_preprocess_article_node_form() be THEMENAME_preprocess_article_node_form()? Or should we consider the first definition of a theme function / template file to be where "template_" is used for the preprocess function?

benjamin, Agaric

rashad612’s picture

Yes. But this example is implemented in custom module, not template.php. Also in hook_theme you can define your own preprocess functions.

kingqueenyun’s picture

Hi rashad612,I have a doubt of naming the theme hook,why be it must "article_node_form",not "article_add_node_form","article_node_form_add" and "article_n_form"?
I think there is a function somewhere,like hook_[node-type]_node_form,but I can't find it.
I ever themed the pages by using the template files from customed moudules,and the hook name's rule is free.
Why name it as this here???Help me,I just am confused at this.
Thanks.

rashad612’s picture

I think it's the form ID for node form.

Oliver Huynh’s picture

For reference on this, http://drupal.org/node/1200216

No woman no cry

nonoan’s picture

Thanks!
This worked great for a form i.e when editing or adding my custom content-type node.

How can I achive the same, but when viewing the node. So I can call a preprocess function, and have my own tpl file in the module (not in the theme dir)

Cheers!

psn_zonup’s picture

 $elements = drupal_get_form('user_login_block');
 
  $rendered = drupal_render($elements);
  
  $elements['#action']='/YOUR-SITE/?q=node/YOUR-NODE-NUMBER?destination=user/'.$user->uid;

echo '<form action="' . $elements['#action'] .
                              '" method="' . $elements['#method'] .
                              '" id="' . $elements['#id'] .
                              '" accept-charset="UTF-8">
							  
							  <table border="0" cellpadding="0" cellspacing="0">
	<tr>
		<td>
			User :
		</td>
		<td>
			<input type="text" name="name" id="edit-name" class="form-text required" />
		</td>
	</tr>
	<tr>
		<td>
			Password :
		</td>
		<td>
			<input type="password" name="pass" id="edit-pass" class="form-text required" />
		</td>
	</tr>
	<tr>
		<input type="hidden" name="form_build_id" id="form-672874fd779fc88bee41ffbd690df686" value="form-672874fd779fc88bee41ffbd690df686"  />
<input type="hidden" name="form_id" id="edit-user-login-block" value="user_login_block"  />
		<td colspan="2"><input type="submit" name="op" id="edit-submit"  value="Log in" class="form-submit"/></td>
	</tr>
</table>
</form>';




Just Past this code in a node and create a link to that node in page.tpl.php of your theme.
That's it.

Oliver Huynh’s picture

This is not good bro :)

No woman no cry

rashad612’s picture

Not good and not Drupal :)

psn_zonup’s picture

Thanks for comments. Please help me to use custom templates for Default 'Login' and 'Request new Password' pages.

Oliver Huynh’s picture

Here is how I override commerce check out page
http://drupal.org/node/1200216

No woman no cry

alexandre_fs’s picture

very nice topic. thanks for sharing...

however i've been trying without success to remove or hide from the $form, the panel of Revision Information, Path URL Settings, and all other settings that i don't want to show in the Content Type Form, so that when i "print drupal_render_children($form)" it does not show up.

How can i do this? I have to find the variable name, but i can't find it!

thank you

kaido.toomingas’s picture

If you would use this you could try this..
Unset isnt the good idea at this point.. becouse then you wouldnt get any nicelooking path even if youre use pathauto.

unset($vars['form']['path']);
or better hide($vars['form']['path']);

But in your case I would try out simplify node add module :)
http://drupal.org/project/simplify_node_add

And maybe this would also work
$vars[form]['path']['type'] = 'hidden';
);

andyceo’s picture

In this example, we have two features (or bugs, as you wish).

1. We can't create souch template in our theme folder. If we do so, it don't work. Only module template always used, also the another template exists in theme folder.
2. The key in returned array in function MYMODULE_theme MUST be exactly equal to 'article_node_form', because this is the form id we theming. There is possible variations, for example, for another content type: 'story_node_form', but NOT the 'node_story_form' or 'form_story_node' - because the core node module produce names for forms their form in hardcoded way.

damonatodd’s picture

I hate when I fully don't understand things, I can't figure per the instructions what code does in what file name and exactly where they go. Could someone please provide a code structure example as the instructions don't explain clearly. Do I have to make a .module file somewhere?

rashad612’s picture

This documentation shows you how to do it in a custom module. Functions: MYMODULE_theme() and template_preprocess_article_node_form() are supposed to be written in a module file replacing "MYMODULE" with your module name. The last template code is to be written in tpl file added by the custom module.
Please note you need to replace "article" with your node/content type machine name.
A quick overview of "hook_theme" will be useful.

ataxia’s picture

I really didn't want to write a separate module, and I knew how to do this in D6, so after much experimenting, here's what I came up with for a custom content type of "faq" using the fusion_starter theme:

I added these lines to template.php:

function fusion_starter_theme($existing, $type, $theme, $path) {
	return array(
		'faq_node_form' => array(
			'arguments' => array('form' => NULL),
			'template' => 'node--add--faq',
			'render element' => 'form',
			),
	);
}

Then I created a template file called node--add--faq.tpl.php (same as the 'template' field in template.php.)

This is node--add--faq.tpl.php:

<?php
?>


<?php hide($form['field_postedby']); ?>
<?php hide($form['field_ans']);  ?>
<?php hide($form['field_disp_date']);  ?>
<?php hide($form['field_faq_stat']);  ?>

<table >
  <tr>
    <td><?php print drupal_render_children($form['field_topic']); ?></td>
    <td><?php print drupal_render_children($form['field_type']);  ?></td>
  </tr>
</table>


<?php print drupal_render_children($form);  ?>

I'm hiding some fields that I don't want users to see using the 'hide' function, because style "display:none" still allowed them to be viewed.

I'm using the table to get two selection lists to be displayed next to each other. There must be a better way to do that, but this was easy and it worked.

The other fields in the content type could be listed individually using the 'drupal_render_children' the way I did in the table, but the last line takes care of all unspecified fields, so that worked for me.

Note: Since the Administrator is using a different theme, they will see all of the fields if they choose to create a FAQ of their own, or if they want to fill in the answer field, etc.

Roi Danton’s picture

Thanks for that excellent, short example and your additional notes! The only change I'd made is to use the recommended template naming (node--NODETYPE--edit.tpl.php respectively page--*.tpl.php), see https://drupal.org/node/1089656.

List of additional variables: https://api.drupal.org/api/drupal/modules%21node%21node.tpl.php/7

Instead of drupal_render_children($form['field_topic']); one should use drupal_render($form['field_topic']); so that the surrounding div container will be rendered at place instead at the bottom of the page.

r1lita’s picture

Your tips saved my day. Thank you.

blastoise’s picture

For some reason I didn't managed to get code for custom module to work, I don't know why. (cleared cache, module enabled, preprocess is working when debugging but new template file is not used)
After that I try your example and it works. Now I'm using preprocess function from custom module to hide some form elements and your theme hook from template file and tpl file for styling :) Thanks

EugeneChechel’s picture

I have

print drupal_render_children($form['field_artists']);

field_artists is a file field to upload images
It renders fine. Shows browse button and upload. But when I upload an image it disapears. It sends only 2-3 ajax queries instead of 9-10 as node/%/edit form from default page.

Same with node reference "add new item" button. It searches nodes, and after click to add new, nothing happens.
I use seperate theme.
I declared hook_menu and hook_theme and create tpl file where I call render functions

Do you ever faced this problem before?

rashad612’s picture

How about this:

print render($form['field_artists']);
print drupal_render_children($form);
narnua’s picture

The "write your own module" option worked instantly, thanks for clear howto on that.

But before this, I spent a couple of hours trying to make this work without writing yet another module, including with what this thread had to offer:
http://drupal.org/node/1092122#comment-5705876

Could you consider also providing a functional example for the template.php alternative referred to in the original post and throughout the thread, including exact naming of functions and variables in template.php and node form .tpl.php file, and which Drupal versions it has been tested to work on?

(IMHO, affecting form layout without intention of changing the node form functionality should really be in the theme, not in a module - templating maintenance for my content type being one big reason, encapsulating what I'm working on for potential collaborators another - so if there's a way to get it done in the theme instead of yet another module, would surely appreciate the pointers)

rashad612’s picture

Time ago we wrote this solution originally for drupal 6 in theme's template.php
(http://drupal.org/node/859392), when Drupal 7 released, we chose to write it again, but as module.
Please note, hook_theme() can be written in template.php, so you can implement it easily, with minor changes.

dayofthedave’s picture

I just struggled for several hours trying to figure out how to do this with template.php, and I couldn't get it to work. Tried the module route, and it worked right off the bat.

I agree that this sort of thing really does belong in the theme, as it is a general theming issue, so a specific template.php example would be nice, if possible.

ThePeach’s picture

See if this what you were looking for.

http://bri-space.com/content/theming-contact-form-drupal-7-contactsiteform

I was hoping this page to be found on the drupal documentation :-/

DamienMcKenna’s picture

It seems that each form element should be rendered with drupal_render($form[$field]); instead of drupal_render_children($form['field']); - while the latter may appear to work, it will result in the Form API #states functionality not working correctly, so all of the JS-drive form field UX gravy will fail to work.

--
Damien McKenna | Mediacurrent