The Scary Render Array

Thank you for your contribution: 

Introduction

This chapter is about the Render Array (no, really!) and what you can do with it. It will explain the Render Array from a themer's point of view. So not how your module should generate it, but how to change it inside the theming layer.

The Render Array is probably not the first theming tool you use. Most of the theming is done using the page template and CSS, some advanced theming requires function overrides, template overrides and preprocess functions. For the remaining, probably few, cases the Render Array gives you the required flexibility.

But the Render Array has its price, and that price is knowledge. The Render Array is complex, overwhelming and scary at times. With the knowledge this chapter brings you, the scary part will probably go away with it ;)

This chapter will answer questions like:

  • How to modify a node page?
  • How to move parts of a node to the sidebar?
  • How to hide form fields conditionally, based on the content of other fields?
  • When to use Render Array? (and when to run away)

You do need some knowledge and working experience with the other theming tools to get the most out of this chapter. Function overrides, template overrides, template.php and preprocess functions should be at least somewhat familiar to you and will not be explained here.

What's the Problem?

Some Examples

Many things in Drupal theming can be done by using the other tools: module configuration, templates, CSS, function overrides and preprocess functions. So, when do you need the Render Array? What kind of theming challenges are typically solved using the Render Array? Let's look at two examples.

The Node View

node-viewnode-view

The image above is a part of a Node View with Title, some file field data, some meta data fields and a description text. The most interesting part is the data wrapped in a gray border. The data inside the border is a mix of fields, and field data data derived from a field (e.g. document size). Wrapping fields in a div is a common theming problem. Some possible solutions:

  • Use a Field Group.
    
An overkill for wrapping a div around fields. In this case not sufficient because we have to add custom data (the second line).
  • Hard code the fields in a node template and wrap a div around them.
    
Commonly used. Causes confusion because newly added fields are not displayed on the node.
  • Content Template module
    
Equal to hard coded fields, but requires an extra module to do this via the admin interface.
  • Display Suite
    
Allows you to define re-usable templates in which fields can be placed freely. Usefull for this case. But not sufficient because we have to add custom data.

Want to know how to solve this with the Render Array? Read paragraph Examples: Hacking the Node

The node links

node-linksnode-links

Sometimes node links, which by default are placed below the node content, should be placed at the top of a node or even in a sidebar. Some modules are available to manipulate all links, but none of them allow moving around a single link or a selection of links around. Without using the Render Array we have these options:

  • Write a module that does some hook_links_alter and hook_node magic.
  • Write a preprocess_node function with a regular expression to take out the link.

Both options require a fair bit of PHP programming and are not typical theming tasks. Want to know how to solve this with the Render Array? Read paragraph Examples: Move that Link!

The Abstract Problem

The above theming problems can be solved using the Render Array. But what is the root of these problems and what Drupal processes produce and process this Render Array?

In 2009, Young Hahn wrote a blog post about some fundamental limitations of the Drupal 6 theming layer: Limitations of the Drupal Theme Layer. Three fundamental problems are worth looking at to understand where the Render Array can help.

The Vertical Stack

Vertical stacks are parts of the Drupal page which behave as one and cannot (easily) be separated, something taken out of or something put into. For example, the node body, a view, the user profile or the block content. It is hard to take a single field out of a the node body and move it to a different location on the page, or to put a div around the third and the fourth field of a node.

Context Awareness

The Drupal page is built in a hierarchically or nested manner. The template structure is a good example of this. The node body is included in the node template, which is included in the region template, which is included in the page template, which is included in the HTML template (Drupal 7).

structurenstructuren

A problem in this hierarchy is that the parent knows little about the children and the children know little about their parent. For example, in the page template it is unknown how many blocks are included in the left sidebar (parent - child relation). Also the node links don't not know if there is a right sidebar available on the page (child - parent relation). This lack of context awareness makes it often impossible to respond to the context. For example, it is impossible to override the theming of a block when it is the only block in the left sidebar and not override it when there are multiple blocks in the sidebar. This limited context awareness also occurs in functions and function overrides.

Early Rendering

A third limitation in the theming layer is caused by early rendering. In Drupal 6 the HTML output is rendered as soon as the output data is available; i.e. as early in the process as possible, usually in the module. For example, as soon as the User module has collected the data for the Who is online block, the output is being rendered. In the theme layer the HTML of the block is passed to the region and the HTML of the region is passed on to the page. HTML is generated by modules and the theme layer puts the HTML in the right place.

The unbreakable vertical stack and the limited context awareness would not be such big problems if it were not combined with early rendering. Due to the early rendering the flexibility is lost downstream. In the (Drupal 6) theming layer, the themer is confronted with one massive node body made up out of multiple fields. With various workarounds (the node object is available in the node template) some of the flexibility is regained some. But in Drupal 7 the approach is more fundamental.

With Drupal 7 the rendering is delayed until the data reaches the template. Modules return a structured array containing data, content, theme functions and other rendering properties. This structured array is called the Render Array. The content and the structure of the Render Array can now be changed in the theming layer. In the Render Array the full context is available and vertical stacks are still collections of loose elements. With the Render Array the themer has regained full control over the data and how it will be rendered.

How Drupal 7 Solves Your Theming Problem

Drupal 7 Theming Goodies

In Drupal 7 several improvements have been made for the themer and to the rendering process. The Render Array is a big improvement for the flexibility. But other changes should also be mentioned:

  • Render Array: Improved flexibility by late rendering.
  • Preprocess: Functions also have a preprocess in Drupal 7
  • Render functions in template: Rendering functions in the templates: render(), show(), hide().
  • Form Array: New (form) array properties introduced.

The Render Array

A First Look

The Render Array is a structured array of data, content, theme functions and rendering properties. The Render Array is created by modules. During the process of a page load all modules can add their Render Arrays to the array and one big array is formed. Inside a theme's THEME_page_alter() function you can have a look at the full Render Array as it enters the theming layer:

Array ( [#show_messages] => 1 [#theme] => page [#theme_wrappers] => Array ( [0] => html ) [#type] => page [sidebar_first] => Array ... [content] => Array ... [footer] => Array ( [system_powered-by] => Array ( [#markup] => Powered by Drupal [#contextual_links] => Array ( [block] => Array ( [0] => admin/structure/block/manage [1] => Array ( [0] => system [1] => powered-by ) ) ) [#block] => stdClass Object ... [#weight] => 1 [#theme_wrappers] => Array ( [0] => block ) ) [#sorted] => 1 [#theme_wrappers] => Array ( [0] => region ) [#region] => footer ) [page_top] => Array ... )

In the Render Array you will find elements starting with a hash or pound sign '#'. These elements are properties or parameters. Properties are well known from the Form Array and are described in the Form API reference ('e.g. #theme'). Parameters are used by the theme functions or will become available as a variable in the template (e.g. '#show_message').

[#show_messages] => 1 [#theme] => page [#theme_wrappers] => Array ... [#type] => page

Array items that do not start with '#' are children and may contain a renderable array. In the Render Array you recognize the page regions sidebar_first, content, footer, page_top each of them is a renderable array. The nested template structure is identical to the nested Render Array structure.

[sidebar_first] => Array ... [content] => Array ... [footer] => Array ( [page_top] => Array ...

Inside the page template the Render Arrays of all regions are rendered. For example, the footer region is rendered with render($page['footer']). The render() function performs the actual rendering of the array and returns rendered HTML content. The render process is described in more detail in The Render Process.

Still many theme functions return HTML, both in core and in contributed modules. For Drupal core there was not enough time during the release cycle to rewrite all theme function to return a renderable array. It is expected that this will improve in Drupal 8.

Preprocess

In Drupal 7, preprocess functions are available for both templates and functions. In a preprocess function, the parameters used by the theme function or template can be altered before the function or template is executed.

For example, theme_links($variables) (documentation).

$variables is an associative array containing:

  • links: An associative array of links to be themed.
  • attributes: A keyed array of attributes for the UL containing the list of links.
  • heading: An optional heading to precede the links.

The function THEME_preprocess_links(&$variables) can be used in a theme to modify the variables before they are passed to the theme_links() function (or if available, an override of this function). $variables in THEME_preprocess_links() contains the exact same elements as in theme_links() listed above.

Read about preprocess in the documentation of the theme() function.

Render Functions

You find some render related functions in Drupal 7 templates:

  • render(): Renders a renderable array and returns the (HTML) result.
  • hide(): A helper function to prevent rendering of a part of a renderable array. See the node.tpl.php to see this function in action.
  • show(): Does the reverse of hide() and enables (re-)rendering.

Array Properties

Elements in a renderable array that start with a hash or pound sign ('#') are properties or parameters. Some frequently used properties are listed below. The Render Array uses the same properties as the Form Array. For examples and documentation see the forms API reference and the Render Arrays in Drupal 7 page.

#theme

The theme function (without the 'theme_') is to be used to render the data. If the element has children they will have their own #theme property defined. Change this property to apply a custom theme function for a specific element in the Render Array.

#theme properties may contain one or more double-understores ('__'). This provides alternative theme function names with fallback option. Read the theme function documentation for more information.

#type

A shorthand for a set of properties as defined hook_elements_info(). If no #theme property is defined, the properties set by #type are loaded. For example, the definition of #type => 'page':

$types['page'] = array( '#show_messages' => TRUE, '#theme' => 'page', '#theme_wrappers' => array('html'), );

You will recognize these in the previously shown Render Array example.

#markup

The content of this property is considered to be rendered output and will be displayed without any change. Use #markup to insert ready and safe (!) content into the Render Array. Always use check_plain() or any other output cleaning function to escape user input.

#prefix / #suffix

These properties contain strings to be a added as prefix or suffix to rendered content. Use #prefix and #suffix to add for example an HTML tag around content. #prefix and #suffix may contain everything, but make sure it is safe! To wrap a div around two fields, add a #prefix = '

' to the first and a #suffix = '
' to the last. That's all.

#attached

Use this property to load CSS, Javascript or custom libraries. Use this to add, for example, a JavaScript library to a specific form element.

#states

This allows adding Javascript without writing Javascript. The state of elements can be set based on the state of other DOM elements. Available states are: enabled/disabled, visible/invisible, checked/unchecked, expanded/collapsed. Read more about this in the drupal_process_states() documentation.

#theme_wrappers

Theme wrappers may be counter intuitive after you have learned about the hierarchical and nested structure of the Render Array and the page output. You may think that the block content is a child of a block. But this is not the case. The below Render Array of the 'Powered By' block proves it.

[system_powered-by] => Array ( [#markup] => Powered by Drupal [#contextual_links] => Array ... [#block] => stdClass Object ... [#weight] => 1 [#theme_wrappers] => Array ( [0] => block ) )

The Render Array of the 'Powered By' block contains the block content (in '#markup'). No parent block element is available. The block is actually wrapped around the content. The value of #theme_wrapper is the theme function which will add the block around the '#markup' content. Other properties are passed as variables to the theme function. In the code above the wrapper function theme_block() uses #block and #markup as parameters to build the block with.

The Render Process

For those of you who like to know the bigger picture, let's have a closer look look at the render process. The render process is the final stage of a Drupal page call. Which starts with bootstrapping Drupal and ends with HTML being sent to the browser. Modules which output data to the browser will return a structured array. All arrays together form the Render Array. Before the Render Array is handed over to the theme layer, modules can make a final alteration using hook_page_alter(). The same hook can be implemented by a theme to alter the Render Array just after it is received from the modules.

theme-processtheme-process

Hook_page_alter() is the right place for a theme to make high level changes in the array, such as moving data from one region to another, moving data from the node to a side bar, or other decisions which involve (multiple) regions.

The render process is continued with the actual rendering of output. This is an iterative process which starts at the page and traverses down to the 'leaves'. In each step a THEME_preprocess_*() and THEME_process_*() and *.tpl.php or theme_*() function is called. For example, some of the steps when rendering a node/123 page:

THEME_page_alter() THEME_preprocess_page() THEME_process_page() page.tpl.php THEME_preprocess_region() THEME_process_region() region.tpl.php THEME_preprocess_node() THEME_process_node() node.tpl.php THEME_preprocess_field() THEME_process_field() THEME_field() ...
 THEME_preprocess_comment_wrapper() THEME_process_comment_wrapper()
 comment-wrapper.tpl.php ... THEME_preprocess_region() THEME_process_region() region.tpl.php ...

Note that in Drupal 7 the (node) content is also included in a region. In the above example, THEME can be replaced with the module name, the theme name or any other valid override.

Each renderable element of the Render Array is processed by the drupal_render() function. In templates this is called using the render() function. Further investigation of the rendering process can learn a lot about the function and use of the #-properties of a Render Array.

drupal_renderdrupal_render

Check #access and #printed

This is the first step of the drupal_render() process.

When #access is FALSE the element is not rendered, no output is returned.

When #printed is TRUE the element is already rendered and will not be rendered again, the process is terminated, no output is returned. Can be set/reset by using show() and hide().

Check Cached Version Available

If the #cache flag is set, a cached version of the rendered result will be fetched.

Load Defaults from #type

#type acts as a short-hand for a set of properties. Different types use different properties as parameters. Types are defined in hook_elements_info(). The associated properties will be loaded and used in the remaining process.

Call #pre_render

#pre_render is an array of functions which can modify the renderable element. These functions can modify #-properties, add rendered output or abort the rendering by setting #printed to TRUE.

Call #theme Recursively

If #theme is set the right theme function is called. Rendered output is return in #output. If available, the theme function will take care of any children in the renderable element.

If #theme is not set, drupal_render() will be called (iteratively) for each child and will return the rendered output in #output.

Call #theme_wrappers

One or more wrapper functions can be defined in #theme_wrapper. Each one will wrap HTML around the rendered output stored in #output.

Call #post_render

Functions defined in #post_render can filter the output or make other final changes.

Handle #attached (js, css)

When #states are defined, the associated Javascript is added.

With #attached Javascript files, css files or libraries can be attached to individual elements. The files defined in #attached are marked for addition using drupal_add_js(), drupal_add_css(), etc.

Add #prefix #suffix to Output

#prefix and #suffix are added to the rendered output.

Cache Output If #cache Is Set

If #cache is set, the final result is stored in drupal cache. ['#cache']['bin'] and ['#cache']['expire'] can be set to alter the cache behaviour.

Finally, to mark the element as being rendered, the #printed property is set to TRUE. Rendered output is now returned by drupal_render().

Examples: Debugging Tricks

First things first. A few debugging tricks to help you work with Render Array code in the theme layer.

Find a Renderable Array

Everything in hook_page_alter is a Render Array:

/** * Implements hook_page_alter(). */ function MYTHEME_page_alter(&$page) { kpr($page); }

Everything that is passed to render() is a Render Array. In node.tpl.php:

... // We hide the comments and links now so that we can render them later. hide($content['comments']); hide($content['links']); kpr($content); print render($content); ...

Devel Debug Functions

Use kpr() or dpm() or any other debug function provided by the devel module. They collapse these often deep arrays nicely. The kpr() functions print out immediately and in place. Don't use it for frequently used theme functions such as theme_link() since it will print the output all over the screen. The dpm() function displays its output in the Drupal message area but requires you to refresh the page twice to get the content displayed when working in the theming layer.

Examples: Hacking the Node

examples-nodeexamples-node

A document node type, wich contains a file with meta data and description. To display the data logically it is grouped in a subtle gray border and logically ordered. To achieve this the standard node field must be grouped and wrapped in div's. A fine job for the Render Array.

Simplified we need this HTML:

... image + link to file ...
... file type and size ...
... document type, data and source ...

The fields we have:

  • field_document_file (file field)
  • field_document_type (text list)
  • field_document_date (text field)
  • field_document_source (text list)
  • field__description (long text)

You can download source code and a feature with this content type from the Wizzlern website.

The Code

/** * Preprocess node. */ function MYTHEME_preprocess_node(&$variables, $hook) { $function = __FUNCTION__ . '_' . $variables['node']->type; if (function_exists($function)) { $function($variables, $hook); } } /** * Preprocess node override for content type 'document'. */ function MYTHEME_preprocess_node_document(&$variables, $hook) { //
around all document data. $variables['content']['field_document_file']['#prefix'] = '
'; //
around document meta data. // Plus closing divs for 'document-meta' and 'document-wrapper' $variables['content']['field_document_type']['#prefix'] = '
'; $variables['content']['field_document_source']['#suffix'] = '
'; // Put file meta data just after the field_document_file. // File type and file size. $mime = $variables['content']['field_document_file'][0]['#file']->filemime; switch ($mime) { case 'text/plain': $type = t('Text document'); $class = 'text'; break; case 'application/pdf': $type = t('PDF document'); $class = 'pdf'; break; default: $type = t('Document'); $class = 'document'; break; } $variables['content']['file_type'] = array( '#markup' => $type, '#prefix' => '
', '#suffix' => '', '#weight' => 0.1, ); // Get the file size from the file object and format is properly. $filesize = $variables['content']['field_document_file'][0]['#file']->filesize; $variables['content']['file_size'] = array( '#markup' => format_size($filesize), '#prefix' => '', '#suffix' => '
', '#weight' => 0.2, ); }

The Explanation

/** * Preprocess node. */ function MYTHEME_preprocess_node(&$variables, $hook) { $function = __FUNCTION__ . '_' . $variables['node']->type; if (function_exists($function)) { $function($variables, $hook); } }

The first function MYTHEME_preprocess_node() is a helper function (thanks to Zen theme) to call a node type specific preprocess function. MYTHEME_preprocess_node_document is where the actions takes place.

/** * Preprocess node override for content type 'document'. */ function MYTHEME_preprocess_node_document(&$variables, $hook) { //
around all document data. $variables['content']['field_document_file']['#prefix'] = '
'; //
around document meta data. // Plus closing divs for 'document-meta' and 'document-wrapper' $variables['content']['field_document_type']['#prefix'] = '
'; $variables['content']['field_document_source']['#suffix'] = '
';

This is the way to wrap divs around fields. Just add a

#prefix to the first and a
#suffix to the last field. Of course this depends on a certain order in which the fields are placed. If you change the field order in the configuration page, you are likely to break the node layout.

// Put file meta data just after the field_document_file. // File type and file size. $mime = $variables['content']['field_document_file'][0]['#file']->filemime; switch ($mime) { case 'text/plain': $type = t('Text document'); $class = 'text'; break; case 'application/pdf': $type = t('PDF document'); $class = 'pdf'; break; default: $type = t('Document'); $class = 'document'; break; } $variables['content']['file_type'] = array( '#markup' => $type, '#prefix' => '
', '#suffix' => '', '#weight' => 0.1, );

The line below the clickable file name holds an icon and text to identify the document type and the file size. Both of these can be derived from the file field meta data.

The document type can be derived from the file mime type of the document file field. The Drupal API does not have a function for this, so we have written a small switch statement, which supports pdf and text documents. The result is added to content element of the Render Array. The key ('file_type') can be any unique text string, not starting with '#'. An array is added with #markup with the HTML output and #prefix/#suffix for the wrapping divs and spans. Using #prefix/#suffix is arbitrary here, the divs and spans could just as well be added to the #markup content. The #weight is chosen to position the output just below the document file field, which has a weight of 0.

// Get the file size from the file object and format is properly. $filesize = $variables['content']['field_document_file'][0]['#file']->filesize; $variables['content']['file_size'] = array( '#markup' => format_size($filesize), '#prefix' => '', '#suffix' => '
', '#weight' => 0.2, ); }

The file size is added in a similar manner. The field_document_file's #file object holds a file size, which is formatted using the format_size() function. The result is added to the Render Array including span tags and a closing tag for the above 'file-meta' div.

Examples: Move that Link!

examples-linksexamples-links

Let's assume you use the Forward module to enable users to send an email to someone to tell about this a page. The Forward module adds a link to the node links which are by default placed below the node content. In this example we want to move the Forward link (and only this link) to the top of the page and rename it to 'Tell a friend'.

The Code

/** * Preprocess node. */ function MYTHEME_preprocess_node(&$variables, $hook) { $function = __FUNCTION__ . '_' . $variables['node']->type; if (function_exists($function)) { $function($variables, $hook); } } /** * Move the Forward link to the top of the node. */ function MYTHEME_preprocess_node_article(&$variables, $hook) { if (isset($variables['content']['links']['forward'])) { // Copy the renderable array of the foward link and hide the original. $forward = $variables['content']['links']['forward']; hide($variables['content']['links']['forward']); // Change the link tekst and title. $forward['#links']['forward_link']['title'] = t('Tell a friend'); $forward['#links']['forward_link']['attributes']['title'] = t('Tell a friend about this page.'); // Add the forward link above all content. $variables['content']['forward_link'] = $forward; $variables['content']['forward_link']['#weight'] = -99; } }

The theme which include the above code can be downloaded from the Wizzlern website.

The explanation /** * Preprocess node. */ function MYTHEME_preprocess_node(&$variables, $hook) { $function = __FUNCTION__ . '_' . $variables['node']->type; if (function_exists($function)) { $function($variables, $hook); } }

The first function MYTHEME_preprocess_node() is a helper function (thanks to Zen theme) we also used in the previous example. It calls a node type specific preprocess function. in MYTHEME_preprocess_node_article we modify the Render Array to move the link.

/** * Move the Forward link to the top of the node. */ function MYTHEME_preprocess_node_article(&$variables, $hook) { if (isset($variables['content']['links']['forward'])) { // Copy the renderable array of the foward link and hide the original. $forward = $variables['content']['links']['forward']; hide($variables['content']['links']['forward']); ...

We first check if a link is available; a good habit to prevent to break things if the link is removed or displayed conditionally. To move the link we copy the Render Array of the link and mark it hidden using the hide() function. This makes sure it is not rendered the next time render() is used to render the node content.

// Change the link tekst and title. $forward['#links']['forward_link']['title'] = t('Tell a friend'); $forward['#links']['forward_link']['attributes']['title'] = t('Tell a friend about this page.');

Before the forward link is used, we can change the link text and link title attribute. The content of $forward is printed below.

Array ( [#theme] => links [#links] => Array ( [forward_link] => Array ( [title] => Email this page [href] => forward [html] => FALSE [attributes] ( [title] => Forward this page to a friend [class] => forward-page ) [query] => Array ( [path] => node/50 ) ) ) [#attributes] => Array ( [class] => Array ( [0] => links [1] => inline ) ) )

$forward is the Render Array of a single link in node links. The #theme property has the value of 'links' which means it will be rendered using the theme_links function. The other elements of the array '#links' and '#attributes' can be recognized as parameters of the theme_links() function. Compare this with the array element the theme_links() function is called with.

// Add the forward link above all content. $variables['content']['forward_link'] = $forward; $variables['content']['forward_link']['#weight'] = -99;

Lastly the modified Render Array is added to the node content ($variables['content']) with a low weight. This weight makes sure it is rendered above any other content in the node. The 'forward_link' key used to identify the render array must be a unique value, not starting with a '#'.

Summary

By now you will recognize the problems you can solve using the Render Array:

  • Grouping parts of a node
  • Moving parts of a node, user profile, view or block to another region.
  • Override a theme function in selected situations.

The Render Array solves some fundamental theming problems which existed in Drupal 6: The unbreakable vertical stack, the lack of content awareness of themes and functions, the early rendering of output.

Remember, the Render Array is not the first tool for every job. As always, try any available module and consider other theming tools. If the Render Array is your weapon of choice, modify the array at the lowest possible level where the required context is still available. For example, if you want to change the node content, change it in THEME_preprocess_node() not in THEME_preprocess_page() or hook_page_alter().

About The Author

This chapter is based on the presentation 'The Scary Render Array' by Erik Stielstra held at the Drupal Design Camp in Berlin, June 2011. Erik is a Drupal site-builder, developer, themer and active Drupal community member since 2006. He is also known as Sutharsan. In 2009, Erik Stielstra co-founded Wizzlern and specializes in Drupal training and consultancy.

Comments

Nice write-up, but your code blocks are busted... looks like the code tag isn't getting converted to use PRE