The Definitive Guide to Drupal 7

Manipulating the Output of Render Elements

As mentioned, having a structured array to work with is far more flexible than a bunch of HTML. This allows you to make only the changes you want to make with ease, whether big or small, without having to re-write code from scratch.

The prospect of using render arrays to generate markup and using alter hooks in general are completely new concepts to Drupal theme developers. It’s very different than what you are used to, in a good way, but it takes some getting used to. In a lot of ways it’s easier than creating templates and theme functions for one-off implementations. The biggest issues front-end developers face when using the Render API are:

  1. Thinking about generating markup differently.
  2. Figuring out how to modify the content of a render array.
  3. Getting comfortable with implementing alter hooks.

Unlike theme hooks, render arrays are modified using alter hooks, not preprocess functions and templates. This can be confusing at first because render arrays are similar to theme hooks in that their purpose is to ultimately generate HTML markup, and they use templates and theme functions to do so. With render arrays, the #theme property, which allows you to define which theme function or template should be used to render the element, is just one of many properties used and can be changed at any time. In general, you’ll use templates and theme functions to modify the markup itself, and you’ll use alter hooks to modify contents, structure, or placement of the elements before it’s rendered.

The following sections contain a few examples of things you can do with render arrays.

Generate New Content on the Fly

Generating new content is as simple as adding a new element to the page array. Listing 16–14 shows the addition of a new element called “new_stuff” to the pre-existing Highlighted region by implementing hook_page_alter() in a theme’s template.php.

Listing 16–14. Adding a new element to the highlighted region
  1. <?php
  2. /**
  3.  * Implements hook_page_alter().
  4.  */
  5. function mytheme_page_alter(&$page) {
  6. $page['highlighted']['new_stuff'] = array(
  7. '#type' => 'container',
  8. '#attributes' => array('class' => 'my-container'),
  9. );
  10. $page['highlighted']['new_stuff']['heading'] = array(
  11. '#type' => 'html_tag',
  12. '#tag' => 'h2',
  13. '#value' => t('Heading'),
  14. '#attributes' => array('id' => 'my-heading'),
  15. );
  16. $page['highlighted']['new_stuff']['list'] = array(
  17. '#theme' => 'item_list',
  18. '#items' => array(
  19. 'First item',
  20. 'Second item',
  21. 'Third item',
  22. ),
  23. );
  24. }

The first thing you did was name your new element “new_stuff,” gave it a #type of container, and defined a class attribute of my-container. Note that container is an element, defined in system_element_info(), which uses the theme_container() theme function as a theme wrapper by default. This means the children of your element (heading and list) will be run through theme_container(). The resulting markup is shown in Listing 16–15.

Listing 16–15. The output generated for $page['highlighted']['new_stuff'] by theme_container()
  1. <div class="my-container">
  2. ...
  3. </div>

Then you added a subelement called “heading” and specified the #type element property as html_tag. This will cause the element to use theme_html_tag() when rendering. You also specified #tag, #value, and #attributes properties. These are parameters of the theme_html_tag() function as you can see at http://api.drupal.org/api/function/theme_html_tag/7. The resulting markup is shown in Listing 16–16.

Listing 16–16. The output generated for $page['highlighted']['new_stuff']['heading'] by theme_html_tag()
  1. <h2 id="my-heading">Heading</h2>

Finally, you added a subelement called “list.” Here you specified item_list as the #theme property and included an array containing your #items, which is a required parameter for theme_item_list(). The resulting markup is shown in Listing 16–17.

Listing 16–17. The output generated for $page['highlighted']['new_stuff']['list'] by theme_item_list()
  1. <div class="item-list">
  2. <ul>
  3. <li class="first">First item</li>
  4. <li>Second item</li>
  5. <li class="last">Third item</li>
  6. </ul>
  7. </div>

When the Highlighted region is rendered, the code in Listing 16–14 produces the final result shown in Listing 16–18.

Listing 16–18. The final rendered result of Listing 16-14.
  1. <div class="my-container">
  2. <h2 id="my-heading">Heading</h2>
  3. <div class="item-list">
  4. <ul>
  5. <li class="first">First item</li>
  6. <li>Second item</li>
  7. <li class="last">Third item</li>
  8. </ul>
  9. </div>
  10. </div>

Move Content from One Region to Another

Inside a hook_page_alter() implementation, you can move the content of regions around at will. Listing 16–19 contains a few simple lines of code that move the contents of the entire first sidebar to the second sidebar, which results in the layout changing from a left sidebar layout to a right sidebar layout on full node pages. In Listing 16–19, you’ve also moved the breadcrumbs to the bottom of the footer region.

Listing 16–19. Relocate the sidebar_first region to sidebar_second and add breadcrumbs to a new element in the footer region.
  1. <?php
  2. /**
  3.  * Implements hook_page_alter().
  4.  */
  5. function dgd7_page_alter(&$page) {
  6. // Check that you are viewing a full page node.
  7. if (node_is_page(menu_get_object())) {
  8. // Assign the contents of sidebar_first to sidebar_second. $page['sidebar_second'] = $page['sidebar_first'];
  9. // Unset sidebar_first.
  10. unset($page['sidebar_first']);
  11. }
  12.  
  13. // Add the breadcrumbs to the bottom of the footer region.
  14. $page['footer']['breadcrumbs'] = array(
  15. '#type' => 'container',
  16. '#attributes' => array('class' => array('breadcrumb-wrapper', 'clearfix')), '#weight' => 10,
  17. );
  18. $page['footer']['breadcrumbs']['breadcrumb'] = array(
  19. '#theme' => 'breadcrumb',
  20. '#breadcrumb' => drupal_get_breadcrumb(),
  21. );
  22.  
  23. // Trigger the contents of the region to be re-sorted.
  24. $page['footer']['#sorted'] = FALSE;
  25. }

Altering Content Inside a Render Array

Altering the contents of a render array to change bits and pieces of the actual content is where you get into a very gray area. It could be argued that a change like this belongs inside a module. When making changes like this, it’s important to ask yourself whether or not the changes you are making should still apply when the theme you are developing is not active. Listing 16–20 changes the "View" and "Edit" tabs to read "Profile" and "Edit profile" on user profile pages.

Listing 16–20. Implements hook_menu_local_tasks_alter() to change tab names on user profile pages.
  1. <?php
  2. /**
  3.  * Implements hook_menu_local_tasks_alter().
  4.  */
  5. function dgd7_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  6. if ($root_path == 'user/%') {
  7. // Change the first tab title from 'View' to 'Profile'.
  8. if ($data['tabs'][0]['output'][0]['#link']['title'] == t('View')) {
  9. $data['tabs'][0]['output'][0]['#link']['title'] = t('Profile');
  10. }
  11. // Change the second tab title from 'Edit' to 'Edit profile'.
  12. if ($data['tabs'][0]['output'][1]['#link']['title'] == t('Edit')) {
  13. $data['tabs'][0]['output'][1]['#link']['title'] = t('Edit profile');
  14. }
  15. }
  16. }

You are reading content from two chapters on Theme Development from The Definitive Guide to Drupal 7, written by Jacine Luisi and published by Apress on July 19, 2011. All rights reserved.