Drupal 7 Tutorial: Creating Custom Formatters with the Field API
by Dan Linn, Technical Lead/Fun Master
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
Posted by Stuart Clark on . [Reply]
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.
Awesome!
Posted by Dan Linn on . [Reply]
I looked at that module before I got started with custom formatters. Glad to hear it's getting ported.
Problems
Posted by Johnny gamba on . [Reply]
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.
Good clarification
Posted by Dan Linn on . [Reply]
Thanks for that. While my image worked on my set up, what you propose would work in other set ups, too.
Something Similar
Posted by WernerCD on . [Reply]
I'm trying to do something similar. Could you spot what I'm doing wrong?
Sandbox.Info
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
missing PHP tag
Posted by Dylan Tack on . [Reply]
I think you are missing the opening <?php tag.
Typo
Posted by Brock Boland on . [Reply]
Just wanted to point out a minor mistake in your list of hooks:
hook_formatter_field_formatter_info()has an extraformatterin it, and should just behook_field_formatter_info()Corrected
Posted by Dan Linn on . [Reply]
Thanks for the catch, it has been fixed.
Awesome Explanation! Thanks for this short overview!
Posted by Britta on . [Reply]
... 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
Posted by Britta on . [Reply]
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 ...
Posted by Britta on . [Reply]
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:
No problem!
Posted by Dan Linn on . [Reply]
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!
use theme('image')
Posted by Dylan Tack on . [Reply]
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
Posted by Britta on . [Reply]
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.
This should fix you
Posted by Dan Linn on . [Reply]
I added this:
if(isset($fb)) {before the
$link = l($image, $fb, $options); // Create the Linkand updated the code above. Don't forget the trailing bracket.
Thanks,
Dan
Thanks for helping out Dan, but
Posted by Britta on . [Reply]
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
Posted by Andy on . [Reply]
This is a really useful tutorial. I've used it as a base for a YouTube format and Twitter link format. Good work.
Thanks
Posted by Mr T on . [Reply]
Thanks dude, helped me write a youtube field formatter module. I'll release it on drupal.org shortly.
Scrollable text box
Posted by Paul on . [Reply]
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?
overflow: auto;
Posted by Dylan Tack on . [Reply]
You can do this with CSS: Set a height property and use overflow: auto;.
Thanks
Posted by Paul on . [Reply]
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
Posted by jayendra on . [Reply]
Hi,
thanks to share this tutorial. This is really a good tutorial.
Great tutorial.
Posted by Raf on . [Reply]
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?
Posted by Martijn on . [Reply]
Hi, would love to see a example like this to have a tinyurl formatter.
Greetings, Martijn
Is it possible to override
Posted by katie on . [Reply]
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
Posted by gibus on . [Reply]
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$linkto$element[0]['#markup'], but to$element[$delta]['#markup'].The fixed function would therefore be as follows:
My 2c...
Fantastic tutorial.
Posted by RobW on . [Reply]
Fantastic tutorial.
Add new comment