Drupal 7 View Modes: Consistently themeing content across your site

Introduction: What's a 'view mode', and why do I care?

Here at Jaypan, we have begun using a powerful new feature in Drupal 7, 'view modes', to ensure consistent presentation of data across the site. View modes actually existed in Drupal 6, however there were only two available modes, full and teaser (more on this later). And since Drupal 6 only had one Entity type, Node, these view modes only related to nodes, as there was nothing else to relate to. Drupal 7 came along and opened up an API for other modules to register Entity types other than Node. Many things have been turned into Entities as a result; Taxonomy, User, and Comment are all entities in D7, and at Jaypan we utilize custom Entity types for each project. This means that the same types of things can be done with these Entities, as used to be done with nodes. For example, taxonomy terms have image fields attached to them in D7.

To understand view modes, we need to first understand Entities. The idea of an Entity in Drupal is quite abstract. Basically each Entity represents a single 'thing'. With a user, an Entity represents a single user on the site, and holds information such as their username, their (encrypted) password, and their email address. Site builders can also attach fields to a user, collecting more information. For example, if they want to collect a user's birthday, they will add a 'date' field for the user's birthday. Each field in Drupal provides one or more ways of collecting data from users. For a date fields, we at Jaypan usually use the calendar popup provided by the Date module, letting users select a date from a popup calendar. Fields also provide methods of displaying the collected field data. For example, the birthdate could printed as text, or it could be incorporated into an image and displayed to the user, or the user could even be shown a JavaScript timer that shows the number of seconds since their birth.

So a Drupal Entity represents data, but to use this data we need to do something with it. In Drupal 6, we had two primary things we could do with the Entity (though in D6 it was just called a Node). We could wrap all (or most) of the data in HTML and show it to the user in a browser. This was the 'full' view mode. We could also show a shorter/abbreviated version of the node, called a 'teaser'. The CCK module in D6 also added the 'RSS' mode. RSS is actually just a way of wrapping data in XML tags, rather than (X)HTML tags. When the data is wrapped this way, RSS readers can understand it. Again, the data comes from the same Entity (Node), it's just a different way of doing something with this data.

On complex sites, we will often want to show data in multiple areas on a site. For example, let's consider a hypothetical real estate listings site. The main Entity (data type) for this site will be a 'listing', which represents a single house/apartment/location for sale. On our site, we will want to show this data in various different ways. For example, we will have a full view - this will show all (or at least most of) the data regarding the listing. This will probably be shown on a page dedicated specifically to that listing. Another way we will want to show data is as a highlight block - this will be piece of HTML (which I will call a block, but should not be confused with Drupal's Blocks) with a small slideshow, and some basic data to hook users. This block can show up as a highlighted listing on the side bar, in a list on the top page as a newly added item, or on a user's bookmarks page, for their bookmarked properties. And we will want another view mode for 'thumbnails', that only shows a thumbnail image with the cost, and a link to the top page. This will be used in a banner that goes on the top of the screen, and in other places around the site.

The main point of these view modes is that we want them to always look the same, no matter where they appear on the site. This is for two reasons. The first is that it ensures that our site has a consistent design to it, adding to the site's brand. The second reason is that it ensures that any time a change needs to be made, it can be made once, and will ensure that it happens consistently anywhere that display mode is used on the site.

So, with that, let's look look at a real world example.

Step 1: Create an Entity

As mentioned above, at Jaypan, we generally use custom Entities on each site. However, describing how to create a custom Entity is outside of the scope of this tutorial, so for this example, we will be using a Node, so that readers can follow along. For this example, we have created a Content Type called 'Listing'. The Listing has the following fields:

Field Name Machine name Field provided by Description
Listing name title Node entity (required element) The human readable name of the listing
Address field_listing_address Text module (D7 core) A single line for the address (kept simple for this tutorial)
Listing date field_listing_date Date, Date Popup The date at which the property was publicly listed
Picture field_listing_picture File, Image An image of the property being listed
Description body body Node entity
screenshot 1: Manage Fields

Screenshot 1: The 'manage fields' tab for our Listing entity

Screenshot 1 shows our 'Manage Fields' page for the 'Listing' content type.

Step 2: Create a new view mode for the entity

There are two main ways to add view modes for an entity. The quickest and easiest method is to use the Entity View Mode module. This module gives an admin interface, allowing for site builders to add view modes as they require. The other way is through code. At Jaypan, when developing websites, we put most of the code that we write for each site, into a single module for that site. This keeps a site lightweight, and easier to organize. As we go further into this tutorial, we will be using code, so you can assume that all code shown is done in a custom module.

In Drupal 7, modules are defined using hook_entity_info(). In this Entity definition, modules will identify the view modes that they want to provide for that Entity (if any). In the case of the Node module, the only view mode provided is 'Teaser'. We want to add our own view mode in addition to this. In our case, we will add the 'block' view mode I mentioned in the introduction. To do this, we will use hook_entity_info_alter(), which allows us to insert our own view modes to use. In our example, our module name will be 'realty'.

function realty_entity_info_alter(&$entity_info)
	$entity_info['node']['view modes']['block'] = array
		'label' => t('Block'),
		'custom settings' => TRUE,
Screenshot 2: Manage display

Screenshot 2: The 'manage display' tab for our Listing entity

In the above code, we've altered the $entity_info for 'node', and added a view mode with a machine name of 'block', and a label of t('Block'). After clearing the cache, if we click on the 'Manage display' tab for our Listing content type, we can see a new tab for our view mode (screenshot 2).

Step 2B: Add extra fields (optional)

As can be seen in screenshot 2, we are on the 'block' tab on the Manage Display page. This is the admin interface for our View Mode. This is where we can decide what fields will be shown, what 'formatter' will be used to show the fields (the 'Format' tab in the screenshot), and what order the fields will appear in. However, there is one more field we want to include with this View Mode - the user image of the realtor who posted the listing, with a link to their user page. To do this, we will go back to our code again, and implement hook_field_extra_fields(). This hook allows us to add additional pseudo-items to our view mode, that aren't provided by Drupal fields. The user image of the Listing is not a field on the listing, rather it's an element from the User Entity. Fortunately, each node comes with a UID, which is the User ID of the user who created the node. We can use this to get our image, and add it to the view mode here. So, let's look at hour hook implementation:

function realty_field_extra_fields()
	$fields['node']['listing']['display'] = array
		'user_picture' => array
			'label' => t('User image'),
			'description' => t('The image of the realtor who posted the listing. Image is pulled from the the user account of the node author.'),
			'weight' => 0,
	return $fields;
Screenshot 3: Extra Fields

Screenshot 3: The 'user_image' extra fields are shown for the view mode

In the $fields array above, the first key is the Entity type (node), the second key is the bundle/content type (listing), and for the third key, we used 'display'. This third key can be either 'form' (visible on Manage Fields page) or 'display (visible on Manage Display page). We then passed an array, the key of which (user_picture) will be used in the next function. We gave it a human readable label, 'User image', which can be seen in the top row of the table in screenshot 3.

You will also notice in screenshot 3 that the listing picture an a trimmed version of the description have been set to be visible, with the listing date and address hidden, as they will not be used in our block view.

Step 2C: Set the extra field(s) to display when rendered

In step 2b, we told the system that we had a user image to render for this view mode, but the system does not know where to get the image, how to render it, or where to put it into the content. As to how to do this, well it depends on how your entity is generated. For nodes, we can use hook_node_view(), which is called when rendering a node. This function gives us the node, and the view mode. We want to test if the view mode is 'block', and if so, we will render the user image for the user who posted the listing. We do this by loading the $user object from the UID that is attached to the node.

function realty_node_view($node, $view_mode, $langcode)
	if($node->type == 'listing' && $view_mode == 'block')
		$account = user_load($node->uid);
		$node->content['user_picture'] = array
			'#markup' => theme('user_picture', array('account' => $account)),

In the above code, we check the node type, and the view mode, and if they are 'listing' and 'block' respectively, then we add our element to the $node->content array. The content in this array is what is rendered when a node is rendered. We have to use the same key (user_picture) that we used in hook_field_extra_fields(), and if we do so, then the API will take care of deciding whether or not to render the image for us. In our case, we set it to be visible, so it will be rendered with our view mode.

Step 3: Theme the view mode

Let's recap. First, we created a content type (aka: bundle). We added some fields to this content type, then we created a custom view mode to show this content type in a certain way. On the 'manage display' tab for this bundle, we set the fields to be shown in this view mode, and the order in which those fields would be shown. Each field itself will be wrapped in HTML according to the 'formatter' chosen for that field. This is all good, but we want consistency when rendering this view mode. To get that, we can wrap the view mode in some HTML specific to this view mode. We can also add CSS and JS to be included for this view mode, ensuring that this CSS/JS is included any time this view mode is rendered. We can do this in hook_node_view(). Please note that the code in step 2b is left out in the next block of code for readability's sake, but in our actual module would be included.

function realty_node_view($node, $view_mode, $langcode)
	if($node->type == 'listing' && $view_mode == 'block')
		// First we add listing.css and listing.js
		$node->content['#attached']['css'][] = $path . '/listing.css';
		$node->content['#attached']['js'][] = $path . '/listing.js';
		// Next we set our listing to user our theme
		$node->content['#theme'] = 'realty_listing';

As you can see in the above block of code, we have used the Drupal 7 FAPI's #attached attribute to attach a CSS and JS file here. One other thing has been done here. The #theme attribute of the $node->content has been set to 'realty_listing'. As can be seen in the code, the CSS, JS and theme are only applied when we are looking at a $listing with a view mode of 'block'. This means that any time a listing node is rendered anywhere on the site, as long as it is rendered using proper Drupal APIs, these files will be added, and the given theme will be applied.

Step 4: Create the theme function and apply the relevant CSS/JS

The only thing left to do is make our block look the way we want. In this case, the contents of the block do not need to be changed at all, as the order has been decided on the Manage Display for this content type. For our case, we just want to wrap the data in some HTML, and add some CSS. In step 3, we set the node to be rendered with the theme 'realty_listing'. So we have to register this theme function with Drupal, so that we can use it. We do this in hook_theme(). Please note that the explanation for the theme system is out of the scope of this tutorial, so if you aren't sure what's happening here, spend some time studying how hook_theme() works and what it does.

function realty_theme()
	return array
		'realty_listing' => array
			'render element' => 'node',

Now that we have registered the theme function, we have to define it:

function theme_realty_listing(&$variables)
  // First we isolate the node object	
  $node = $variables['node']['#node'];
  // Then we create a wrapper div that gives a unique ID as well as a common class to each listing
  $output = '<div id="listing_"' . $node->nid . '" class="listing">';
  // We add the title, to ensure that it's outputted
  $output .= '<h2>' . check_plain($node->title) . '</h2>';
  // Next, we render the content as it has been defined on the Manage display page
  $output .= drupal_render_children($variables['node']);
  // And lastly, we close off our wrapper div
  $output .= '</div>';
  return $output;

The above code wraps the content in a div so that each listing (for this view mode) has a wrapper with a class of .listing. This allows us to set some CSS on the listings, in listing.css which we added earlier:

	border:solid black 1px;
	box-shadow:2px 2px 2px #000;
.listing .user-picture

Epilogue: Outputting the view mode

So now we've given specific CSS to the view mode we have created for listings. Anywhere we render a listing using the 'block' view mode, our listing will be rendered with the wrapper div given, as well as the CSS attached. This really ends the tutorial. However, we haven't shown how these view modes can be used, and what makes them so powerful.

Elements can be rendered with a given view mode as follows (example given for nodes):

  $listing_block = render(node_view(node_load($nid), 'block'));

That's all great, as it gives us an easy function to consistently render nodes in code. But what makes the view modes even more powerful is that they tie into Views, and most site builders are using the Views module. For this example that we've given, to render the nodes using the 'block' view mode, on the Views page, under Format -> Show, set the mode to 'content' (not 'fields' or anything else). On the settings for this view mode, use 'block'. Now all the items that this view finds will be rendered using the view mode we have created in this tutorial! Please note that if you are using a custom entity type, not Node, then you will not be able to use the 'content' mode (as this is only for nodes). If your entity is tied into the Entity API module there will be an option for 'rendered object' that serves the same purpose. If you have not used the Entity API module, you will likely need to create a new Views Plugin to render your element with view modes.

And that concludes our tutorial. In this tutorial, we added a new view mode to the Node entity. We added some fields to that view mode (image, address etc), and we added some extra fields (the realtor's image), and set the order for the output to be displayed. We then tied into the display of the Entity (node) and added our own CSS and JS scripts, and set the content to be outputted using a custom theme. Finally we tied into Views to use this view mode in content. And finally, an example of how one of our listings would look using the above method:

Screenshot 4: A listing node rendered using the 'listing' view mode

个人分类: CMS
上一篇Drupal commerce checkout流程学习笔记
下一篇Drupal Commerce alter checkout form and custom validate
想对作者说点什么? 我来说一句

Drupal 6 Content Administration

2009年08月27日 10.19MB 下载


2015年09月23日 12.1MB 下载

drupal 7 themes

2012年03月28日 10.55MB 下载