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

Comments

Custom Formatters module

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.

dan's picture

Awesome!

I looked at that module before I got started with custom formatters. Glad to hear it's getting ported.

Problems

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.

dan's picture

Good clarification

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

Something Similar

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

Typo

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()

Awesome Explanation! Thanks for this short overview!

... 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?

I think it is because

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?

sorry for commenting so frequently but ...

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" />';

dan's picture

No problem!

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!

dylan's picture

use theme('image')

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

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.

dan's picture

This should fix you

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, but

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;
}

Awesome

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

Thanks

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

Scrollable text box

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?

Thanks

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.

Thanks

Hi,

thanks to share this tutorial. This is really a good tutorial.

Great tutorial.

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

Tinyurl example?

Hi, would love to see a example like this to have a tinyurl formatter.
Greetings, Martijn

Is it possible to override

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!

A pb. with multivaluable fields

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

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 one

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

Input User validation

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'), 

adding a header to a number of multifields

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

dan's picture

Many ways to skin this cat

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

Thanks!

Awesome tutorial! Thanks!

i tried for link_field without success

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'),
    ),
  );
}

dan's picture

I'm assuming caches were

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

How do you translate the tooltip ?

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.

dan's picture

It's been a while since I

It's been a while since I wrote this tutorial, but I think you could proably add your t() function right there.

Thanks,
Dan

Add new comment