Drupal 7 Tutorial: Creating Custom Formatters with the Field API

Custom formatters are a great way to control the output of your fields. In this tutorial I'm going to use an example that takes a user's Facebook URL as the input and outputs a button of the Admin's choosing, along with an option to open the link in a new window. To get started, we'll need to get a module skeleton sketched out. You'll need to create these two files:

  • facebooklink.info
  • facebooklink.module

The first file, facebooklink.info, can be fairly simple:

name = Facebook Link
description = "Add a formatter to textfields that allows Admins to determine how links will be displayed"
package = Fields
core = 7.x
files[] = facebooklink.module

We give it a name and a description and specify Drupal 7 core. We also add it to the fields group. This will group it with other field modules. The last line tells drupal where the guts are and that's the module file.

The facebooklink.module file will be where we do the coding. There are four hooks we'll need to get our custom formatter working.

  • hook_field_formatter_info() - This tells Drupal what fields it applies to and what settings are available.
  • hook_field_formatter_settings_form() - Here we'll tell Drupal how to generate the form that collects the options.
  • hook_field_formatter_settings_summary() - This displays the chosen settings on the 'Manage Display' page
  • hook_field_formatter_view() - This is the hook where we actually do the formatting

When you start the file make sure you give it a standard module header and description:

/**
 * @file
 * adds a formatter for text fields that creates a facebook button
 *
 */

Then we start our first hook: facebooklink_field_formatter_info

/**
 * Implements hook_field_formatter_info().
 */
function facebooklink_field_formatter_info() {
  return array(
    'facebooklink_formatter' => array( //Machine name of the formatter
      'label' => t('Facebook Link'),
      'field types' => array('text'), //This will only be available to text fields
      'settings'  => array( //Array of the settings we'll create
        'pic_size' => 'small', //give a default value for when the form is first loaded
        'tooltip' => 'Link to user Facebook page', //ditto
      ),
    ),
  );
}

Here we're making Drupal aware of the new formatter by giving it a machine name and setting the label, field types it will be available to, and what settings we plan to allow the admin to set. Your module could define any number of custom formatters with any number of settings.

Next up is the facebooklink_field_formatter_settings_form:

/**
 * Implements hook_field_formatter_settings_form().
 */
function facebooklink_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  //This gets the view_mode where our settings are stored
  $display = $instance['display'][$view_mode];
  //This gets the actual settings
  $settings = $display['settings'];
  //Initialize the element variable
  $element = array();
  //Add your select box
  $element['pic_size'] = array(
    '#type'           => 'select',                           // Use a select box widget
    '#title'          => t('Button Size'),                   // Widget label
    '#description'    => t('Select what size of FB button'), // Helper text
    '#default_value'  => $settings['pic_size'],              // Get the value if it's already been set
    '#options'        => array(
      'small'  => 'Small',
      'medium' => 'Medium',
      'large'  => 'Large',
    ),
  );
  $element['tooltip'] = array(
    '#type'           => 'textfield',                        // Use a textbox
    '#title'          => t('Tool Tip'),                      // Widget label
    '#description'    => t('This text will appear when a user mouses over.'),  // helper text
    '#default_value'  => $settings['tooltip'],               // Get the value if it's already been set
  );
  return $element;
}

We're now telling drupal what our form should look like when someone clicks the settings button. The form will contain a drop down labeled "Button Size" with three options (defaulted to small) and a text box labeled Tool Tip.

The next hook is facebooklink_field_formatter_settings_summary:

/**
 * Implements hook_field_formatter_settings_summary().
 */
function facebooklink_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $summary = t('Use a @size Facebook button with the tooltip of "@tooltip"', array(
    '@size'     => $settings['pic_size'],
    '@tooltip'  => $settings['tooltip'],
  )); // we use t() for translation and placeholders to guard against attacks
  return $summary;
}

This hook is solely responsible for providing a summary of the formatting in the "Manage Display" page for the chosen content type. Our summary will now reflect the settings that we've chosen.

Now the real meat. This hook will do the actual formatting of the link.

/**
 * Implements hook_field_formatter_view().
 */
function facebooklink_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array(); // Initialize the var
  $settings = $display['settings']; // get the settings
  $size = $settings['pic_size']; // The Size setting selected in the settings form
  $tooltip = $settings['tooltip']; // The tool tip assigned in settings
  // Create the image - Note that I'm storing the images in our module but they could be anywhere
  $image = '<img src="/' . drupal_get_path('module', 'facebooklink') . 'fb-' . $size . '.png">';
  foreach ($items as $delta => $item) {
    $fb = $item['safe_value']; // Getting the actual value
  }
  $options = array(
    'html'        => TRUE, // This tells Drupal that we're sending HTML, not plain text, otherwise it would encode it
    'attributes'  => array(
      'title' => $tooltip, // This sets our tooltip
      ),
    );
  if(isset($fb)) {
    $link = l($image, $fb, $options); // Create the Link
    $element[0]['#markup'] = $link; // Assign it to the #markup of the element
  }
  return $element;
}

This function collects all of our settings and does the actual formatting. We could even take this a step farther and create a theme function that could then be called from elsewhere, but these are the basics needed to create the formatter. You've now turned over the ability to format the end-user's Facebook link to the client. They are pleased and give you money.

Email Dan Linn


Great tutorial, it's amazing how many people don't take advantage of formatters, so anything that helps is a good thing :)

You also might want to try out the Custom Formatters module (http://drupal.org/project/custom_formatters), while it doesn't yet have a D7 stable it is being actively re-ported now, and both D6 and D7 will have support for Formatter Settings in the 2.x release.


Hi, thanks for this great tutorial but i must say a had a problem showing the facebook icon, i don't know if i did something wrong, but i fixed it changing the $image value in the facebooklink_field_formatter_view hook
global $base_url;
$image = '<img src="' . $base_url . '/' . drupal_get_path('module', 'facebooklink') . '/fb-' . $size . '.png">';

thanks again.


Thanks for that. While my image worked on my set up, what you propose would work in other set ups, too.


I'm trying to do something similar. Could you spot what I'm doing wrong?

Sandbox.Info

name = Sandbox
description = "Sandbox"
core = 7.x
package = Fields
files[] = sandbox.module

Sandbox.Module

/**
 * Implements hook_field_formatter_info().
 */
function sandbox_field_formatter_info() {
  $info = array();
  $info['sandbox_formatter'] = array(
    'label' => t('Inline file'),
    'description' => t('Display the file within an iframe.'),
    'field types' => array('file'),
  );
  return $info;
}
 
/**
 * Implements hook_field_formatter_view().
 */
function sandbox_field_formatter_view($entity_type, $entity, $field, $instance,
  $element = array();
 
  // Create the proper markup for each item within the field instance
  foreach ($items as $delta => $item) {
    $path = file_create_url($item['uri']);
    $element[$delta] = array(
      '#markup' => '<iframe src="' . $path . '"></iframe>',
    );
  }
  return $element;
}

It's giving me this error: http://i.imgur.com/vNbM3.jpg


Just wanted to point out a minor mistake in your list of hooks: hook_formatter_field_formatter_info() has an extra formatter in it, and should just be hook_field_formatter_info()


... but it causes a php error notice while opening an existing node: Notice: Undefined variable: fb in facebooklink_field_formatter_view() (line 90 of /www/htdocs/*myroot*/sites/all/modules/facebooklink/facebooklink.module).

The node was created before and I h ave atteched another field and the new facebook format to it. Now I wanted to fill out the field for testing purposes and the Notice occures by pressing "edit node".

Do we need to put some extra code here for the case that we attech the foramt to an existing node and content-type fullfiled with content?


we don't have an if-statement proofing if the field is fullfilled with value. In other words we need a "required field or not" handler?


Another point is a missing "/" after facebooklink and before ">" in the image path setting-up code on line 80. First slash is a must to lead into the facebooklink folder and the second is because most D7 installations and themes are standart XHTML formatted html output.

Here my short rework with an proper alt-tag included:

//line 80
$image = '<img src="/' . drupal_get_path('module', 'facebooklink/images/') . 'fb-' . $size . '.png" alt="FACEBOOK" />';


I'm glad that it's generating this much discussion.

The main point of the post was to provide a brief overview of field formatters, not to necessarily provide a production-ready module, so I'm not surprised that you're finding many ways to improve it.

Thanks for the feedback!


It's usually preferable to use theme('image'), instead of assembling an image tag manually. Don't forget the height and width attributes.

Also, base_path() instead of "/" is more universal.


indeed. Im missed that. And of course this not been meant for production use, but if people want to follow step by step and find out it is not working that way, would be confused. field formatting isn't realy good docucmented (as many other parts of drupal). But it is very powerful. Let's try to think of text field and what else it can be without installing tons of text field alikes with the controll over formatting.

I still can't get away that PHP NOTICE ERROR. I have compared your forearch instance with others but it seems to ba all like it should. But still I wonder how $fb can be defined without having a value or how we stop the process if we don't have any values in some of the $items.


I added this:
if(isset($fb)) {
before the $link = l($image, $fb, $options); // Create the Link
and updated the code above. Don't forget the trailing bracket.

Thanks,
Dan


Thanks for helping out, Dan. It is just my hang-over. But I solved the small issue by myself later on by not using those "in-between" variables and by more extending foreach all over the last code before return. Since $element has been set in the beginning of the function, $element already exists and can even be empty without causing "Notice Messages".

foreach ($items as $delta => $item){
$options = array(
'html' => TRUE,
'attributes' => array('title' => $tooltip
 )
);
$element[0]['#markup'] = l($image, $item['safe_value'], $options);
}
return $element;
}


This is a really useful tutorial. I've used it as a base for a YouTube format and Twitter link format. Good work.


Thanks dude, helped me write a youtube field formatter module. I'll release it on drupal.org shortly.


I have a large text field that I want to be scrollable when viewing the field. Could I create a scrollable text box using a formatter?


OK. I am pretty new to this and have never touched css. I will get reading up on that. Any further pointers that could save me time appreciated. Thanks.


It's a great tutorial. Thankyou for it.
You might want to do one on widgets also. So one can learn even more. :)


Is it possible to override _field_formatter_view() from another module (ie field->number module)? I don't actually want to create another formatter, just alter the current output without hacking core. Thanks!


Correct me if I'm wrong, but the source code above of facebooklink_field_formatter_view() seems to be problematic with multivaluable fields.

Actually the foreach loop (foreach ($items as $delta => $item) {) should cover everything until the final return statement (return $element;). And you shouldn't assign $link to $element[0]['#markup'], but to $element[$delta]['#markup'].

The fixed function would therefore be as follows:

/**
 * Implements hook_field_formatter_view().
 */
function facebooklink_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array(); // Initialize the var
  $settings = $display['settings']; // get the settings
  $size = $settings['pic_size']; // The Size setting selected in the settings form
  $tooltip = $settings['tooltip']; // The tool tip assigned in settings
  // Create the image - Note that I'm storing the images in our module but they could be anywhere
  $image = '<img src="/' . drupal_get_path('module', 'facebooklink') . 'fb-' . $size . '.png">';
  foreach ($items as $delta => $item) {
    $fb = $item['safe_value']; // Getting the actual value
    $options = array(
      'html'        => TRUE, // This tells Drupal that we're sending HTML, not plain text, otherwise it would encode it
      'attributes'  => array(
        'title' => $tooltip, // This sets our tooltip
      ),
    );
    if(isset($fb)) {
      $link = l($image, $fb, $options); // Create the Link
      $element[$delta]['#markup'] = $link; // Assign it to the #markup of the element
    }
  }
  return $element;
}

My 2c...


Great tutorial, thanks so much!

I set my set's language preferences up so that language can be determined by a request/session parameter, the name of which is 'language'. When I add this to any URL, it works, and the content is displayed in the relevant language.

However, when I override the hook_field_formatter_view, the $langcode that is passed in is always 'en', no matter what value is sent in on the URL. All other content respects the 'language' parameter on the URL, but the $langcode field is always 'en'.

Any ideas would be much much appreciated.

Thanks!


Nice tutorial for custom fields formatter implementation.
Keep it up!!!


Thanks for this tutorial.
Do you have an idea how i can validate the User Input on node edit form for the textfield.

If i implement hook_field_validate() it is never called - i think because of

'field types' => array('text'), 


Hi,
I think this might be the solution I need for my project but because I'm not that good a php coder I'm battling to link it all up.

My problem:
I have a field that can have multiple submissions - why? It's a day to day itinerary that users can list what they'll be up to for the trip they're planning. I'd like the submissions to have a header per submission - DAY 1: blah blah blah Day 1 being wrapped in a <h2> tag and the description in just a <p> tag.

Can I use a field formatter to dynamically add the DAY & number of submission and control the look via CSS? So that the user wont have to style the header using HTML ?

I'm really stumped. My users are not HTML literate and the styling is driving me up the wall. I'd like the system to do most of it automatically once I've setup the CSS.

Any help greatly appreciated. Thanks


There are a multitude of ways to solve this problem. Here are a few:

  • Views: Create a view block that displays the results with a label and a counter.
  • Templates: Use field or node templates and php to create the listings.
  • Preprocessing: Using custom module or theme function code

I think you may want to investigate one of those methods, as it might be hard to keep the counter going in the field formatter.

Thanks,
Dan


Awesome tutorial! Thanks!


but my formatter is not showing in list for Field Link ...

may be you can help...

/**
 * @file
 * adds formatters for Link fields
 *
 */
 
/**
 * Implements hook_field_formatter_info().
 */
function linkformatter_field_formatter_info() {
  return array(
    'linkformatter_small' => array( //Machine name of the formatter
      'label' => t('link Formatter: Small screenshot'),
      'field types' => array('link_field'),
    ),
  );
}


I'm assuming caches were cleared :) I'd probably try adding the settings array, even if empty.  You will also need to make sure you have the other files available, too.  If it's still not working, maybe send a link to the module that I could download and test.

Thanks, Dan


I can see that the tooltip is asigned directly to title : 'title' => $tooltip.

Does the tooltip pass through a t function at some point ?

Thanks.


This was a huge help! Quick & easy tutorial to help me thrash out a solution in no time. Thanks! :)


This was perfect, thanks. It allowed me to quickly write a beautiful little formatter that changes a URL into a button with configurable text per field and per content type.

About the Author

Dan Linn, Director of Development

Dan is a Ninjaneer with over a decade of experience in creating and leading the creation of interactive websites. With a background in print, scripting, animation and application development he has worked with a number of well-known brands including Daimler/Chrysler, US Department of Energy, the Emmys, DC Comics and Technicolor. Dan is also the (un)official Fun Master of Metal Toad.

While he attempts normality, his ambitions for his off-hour time are many: he still dreams of fronting a band, writing/drawing his comic book, and fighting crime in a costume. His wife and two kids are his superheroes.

He is currently meditating on the next Metal Toad Media implementation of CSS3, HTML5, and jQuery.

Interested? Let's talk.