Field API - Creating your own field formatters

Drupal 7's Field API is amazing—it allows us to easily add fields to any type of entity, and customize those fields with various widgets and display formats. I'm going to walk you through two examples of how you can leverage the Field API to create your own custom field formats.

Example use cases:

  1. You're using the phone field to display phone numbers, but you'd like to customize the HTML output to make it mobile-compatible (click to call).
  2. Your nodes display full addresses via the addressfield module, but you'd like to render those addresses as google maps links.

Let's start at the beginning: you're going to be creating a custom module. Let's call is grasmash.module for vanity's (and sanity's?) sake.

We'll start by letting the Field API know that we have a new field format for it to play with.

<?php
/**
* Implements hook_field_formatter_info().
*/
function grasmash_field_formatter_info() {
  return array(
   
'grasmash_phone_mobile_call_link' => array(
     
'label' => t('Mobile Call Link'),
     
'field types' => array('phone_number'),
     
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
}
?>

What exactly did we do there? We told the Field API:

For any field of type phone_number, make available a new format called grasmash_phone_mobile_call_links with the noob name "Mobile Call Link." For fields with multiple values, just do your thing.

See hook_field_formatter_info() for more details.

Next, we'll let the Field API know what it should do when a user asks to view a field in this format. This is done via hook_field_formatter_view().

Given that we may want to define additional field formatters later, we're going to write something that will work for any custom formatter. Our implementation of hook_field_formatter_view() will cycle through all of the values for a given field (if it is multi-valued), and for each delta, it will generate markup by calling a theme() function that corresponds with the requested display format.

<?php
/**
* Implements hook_field_formatter_view().
* This code just passes straight through to a theme function.
*/
function grasmash_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
 
$elements = array();
  foreach (
$items as $delta => $item) {
   
$element = array('element' => $item, 'field' => $instance, 'display' => $display);
   
$elements[$delta] = array(
     
'#markup' => theme('grasmash_formatter_'. $display['type'], $element),
    );
  }
  return
$elements;
}
?>

You can see that, for each field delta, this will call a theme function matching the naming convention "grasmash_formatter_FORMATTER_NAME." Currently, those theme functions exist only in our imaginations, so let's make them!

We'll start by telling the Theme API that we have a function named grasmash_formatter_FORMATTER_NAME:

<?php
/**
* Implements hook_theme().
*/
function grasmash_theme() {
  return array(
   
'grasmash_formatter_grasmash_phone_mobile_call_link' => array(
     
'variables' => array('element' => NULL),
    ),
  );
}
?>

I'm going to take a brief moment to talk about the structure of hook_theme(), because when I first came across it, I found it confusing.

What exactly did we do there? We told the Theme API:

Whenever a function calls

<?php
theme
('grasmash_formatter_grasmash_phone_mobile_call_link', $element)
?>

, look for a corresponding function named theme_grasmash_formatter_grasmash_phone_mobile_call_link($element). Furthermore, expect that one variable name 'element' will be along for the ride.

So, let's define that theme function!

<?php
/**
* Theme function for grasmash_formatter_grasmash_phone_mobile_call_link.
*/
function theme_grasmash_formatter_grasmash_phone_mobile_call_link($element) {
  return
'<a class="mobile-tel" href="tel:' . $element['element']['number']  . '">Call</a>';
}
?>

Awesome! We can now select "Mobile Call Link" as a display format for our phone fields! Now that we've got the explanations out of the way, let's run through one more quick example: we will add a custom formatter that permits us to display addressfields as Google Maps links.

Add an additional row to the hook_field_formatter_info() array:

<?php
/**
* Implements hook_field_formatter_info().
*/
function grasmash_field_formatter_info() {
  return array(
   
'grasmash_phone_mobile_call_link' => array(
     
'label' => t('Mobile Call Link'),
     
'field types' => array('phone_number'),
     
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
   
'grasmash_addressfield_full_inline_gmap_link' => array(
     
'label' => t('Full Address Inline w/ Gmap Link'),
     
'field types' => array('addressfield'),
     
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
}
?>

No need to worry about hook_field_formatter_view() again, since we built it to accomodate multiple formatters. So, let's add an additional row to our hook_theme() array:

<?php
/**
* Implements hook_theme().
*/
function grasmash_theme() {
  return array(
   
'grasmash_formatter_grasmash_phone_mobile_call_link' => array(
     
'variables' => array('element' => NULL),
    ),
   
'grasmash_formatter_grasmash_addressfield_full_inline_gmap_link' => array(
     
'variables' => array('element' => NULL),
    ),
  );
}
?>

And lastly, let's define our theme function and build the markup:

<?php
/**
* Theme function for grasmash_formatter_grasmash_addressfield_full_inline.
*/
function theme_grasmash_formatter_grasmash_addressfield_full_inline_gmap_link($element) {

  // Mimic addressfield_field_formatter_view().
 
$handlers = array('address' => 'address');
 
$context = array('mode' => 'render');
 
$markup = addressfield_generate($element['element'], $handlers, $context);

  // Wrap markup in a link!
 
$options = array(
   
'query' => array('q' => $markup),
   
'attributes' => array(
     
'class' => array('addressfield-gmap-link'),
    ),
  );
 
$output = l($text, 'https://maps.google.com/maps', $options);

  return $output;
}
?>

Have fun!

Drupal Version Compatibility: 

Comments

It might be nicer to just implement hook_field_formatter_view() and make a renderable array:

'#type' => 'html_tag',
'#tag' => 'a',
'#value' => t('Call'),
'#attributes' => array('class' => 'mobile-tel', 'href' => 'tel:' . $element[$delta]['number'],),

This way everyone gets a chance to style it and it's much easier to do t('Call'). You could even throw in a #theme directive if you had to. :-)

I was actually hoping that someone would comment and provide an example of using a render array. It's something that I haven't looked into much, and I've been meaning to implement it! I'll update the snippets accordingly after some testing. Thanks.

Nice tutorial, thanks.
And for the not-too-code savvy people there's the Custom Formatters module: http://drupal.org/project/custom_formatters

grasmash_field_formatter_view seems to be buggy - a syntax error - a semicolon within an array

You have a typo in the first bit of code for grasmash_field_formatter_info(): you need a closed parenthesis for the first array and a semicolon.

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.