The first step to defining a custom field is telling Drupal that our module provides a field. This is done by implementing hook_field_info(), hook_field_formatter_info(), and hook_field_widget_info(). The combination of which provides some basic information about our field including a label, description, default settings, and basic information about how the field will be formatted and what widgets can be used for data collection.
In this lesson we'll implement the basics for the following hooks:
Doing so will allow us to enable our module and see our new field type appear in the list of available fields to add to a content type. The field won't do much beyond that yet, but it's a good start towards telling Drupal about our custom RGB field type.
If you want to just follow along and look at the already written code you can grab a copy in the companion files section of this page and use that to follow along.
Before we can actually get our field to store data for us we need to define what the data that we're going to store looks like. The Field API does this with hook_field_schema(), which uses a very similar syntax to what is used by the hook_schema() that modules can use to define database tables. In this particular case though we're only defining what the column, or columns, that store our specific data will be and allowing the Field Storage API to decide what the structure of the created table, or tables, should be. This allows our field to remain mostly storage system agnostic and frees us from having to worry about things like how the stored field data is connected back to the entity that it belongs to, or how to format our table for proper handling of revision data or translations.
- Documentation for hook_field_schema - https://api.drupal.org/api/drupal/modules%21field%21field.api.php/funct…
- Schema API docs - https://drupal.org/developing/api/schema
Example hook_field_schema()
implemenatation.
/**
* Implements hook_field_schema().
*/
function rgb_field_schema($field) {
$columns = array(
'rgb' => array(
'type' => 'varchar',
'length' => 6,
'not null' => FALSE,
),
'label' => array(
'type' => 'varchar',
'length' => 128,
'not null' => FALSE,
),
);
$indexes = array(
'rgb' => array('rgb'),
);
return array(
'columns' => $columns,
'indexes' => $indexes,
);
}
Additional resources
The term widget refers to the form element, or elements, that are presented to the user when they are entering data for a field. For example, the file upload field on the Article content type is the widget for the image field attached to that content type. When a field instance is attached to a bundle and an admin is creating or editing an entity of that bundle type the Field Attach API calls out to each individual field and asks it for the widget it would like to use to collect data. Adding a widget for a custom field type is a combination of implementing hook_field_widget_info() and hook_field_widget_form().
Examples:
/**
* Implements hook_field_widget_info().
*/
function rgb_field_widget_info() {
return array(
'rgb_textfield' => array(
'label' => t('RGB Textfields'),
'field types' => array('rgb_color'),
),
);
}
/**
* Implements hook_field_widget_form().
*/
function rgb_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if ($field['cardinality'] == 1) {
$element['#type'] = 'fieldset';
}
$element['rgb'] = array(
'#type' => 'textfield',
'#field_prefix' => t('RGB: #'),
'#size' => 6,
'#default_value' => isset($items[$delta]['rgb']) ? $items[$delta]['rgb'] : '',
);
$element['label'] = array(
'#type' => 'textfield',
'#field_prefix' => t('Color name: '),
'#default_value' => isset($items[$delta]['label']) ? $items[$delta]['label'] : '',
);
return $element;
}
Additional resources
Before our field can save user provided data we need to use hook_field_validate() and hook_field_is_empty() to perform validation on field data. In certain context values like 0, FALSE, and NULL can all be a valid value. In fact, even a blank space could be valid input for a field. As such, it's not possible for Drupal to know what constitutes an empty state for a field without a little extra help. The same is true for checking if the value of a field is valid.
Examples:
/**
* Implementation of hook_field_is_empty().
*/
function rgb_field_is_empty($item, $field) {
if (empty($item['rgb']) || empty($item['label'])) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_field_validate().
*/
function rgb_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
foreach($items as $delta => $item) {
if (!empty($item['rgb'])) {
// Make sure it's 6 characters.
if (drupal_strlen($item['rgb']) !== 6) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'rgb_length',
'message' => t('%name: the hex color must be 6 characters.', array('%name' => $instance['label'])),
);
}
}
}
}
Additional resources
In order to allow for maximum flexibility in our widget we can add widget settings that apply to each individual instance of our field. By implementing hook_field_widget_settings_form() and then refactoring some of our existing code we can make it possible for a site administrator to set a custom prefix value for the label field, which the Field API will store for as part of the field instance's settings and we can use it when creating our widget.
Implementations of hook_field_widget_settings_form()
return a Form API array that represents the element or elements that you would like to add to the widget settings form. Values are automatically serialized and saved as part of the field's instance configuration and can be accessed by the passed in $instance
array's $instance['widget']['settings']
key.
Example:
/**
* Implements hook_field_widget_settings_form().
*/
function rgb_field_widget_settings_form($field, $instance) {
$element = array(
'rgb_label_text' => array(
'#type' => 'textfield',
'#title' => t('Alternate label text'),
'#description' => t('If an alternate label text is provided it will be used in place of the default "Color" title for the label field.'),
'#default_value' => isset($instance['widget']['settings']['rgb_label_text']) ? $instance['widget']['settings']['rgb_label_text'] : '',
),
);
return $element;
}
Additional resources
Displaying that data that was collected and saved for our a field requires creating a field formatter. Formatters consist of an implementation of hook_field_formatter_info() and hook_field_formatter_view(). The former provides meta-data about the formatter for the Field API and the latter does the heavy lifting of determining what the output is actually going to look like.
A module can define more than one field formatter.
Implementations of hook_field_formatter_view()
return a renderable array representing the content you would like to display to the end user. Generally this is an escaped version of content provided by a site administrator with some additional HTML formatting applied.
Depending on the values being output you would likely want to also use a theme function for your field formatter by implementing hook_theme() and providing either a theme() function that can be overriden or a template file. We're not going to cover that in this lesson since the focus here is on the technical requirements for impelementing a field formatter. Howerver, I would say that it's best practcie to always output any HTML with a theme function. You can find out more about creating themeable output by watching these videos from our library: http://drupalize.me/videos/integrating-theme-system and http://drupalize.me/videos/using-drupal-render-api
Example:
/**
* Implements hook_field_formatter_info().
*/
function rgb_field_formatter_info() {
return array(
'rgb_raw' => array(
'label' => t('Raw color value'),
'field types' => array('rgb_color'),
),
'rgb_box' => array(
'label' => t('Color block with label'),
'field types' => array('rgb_color'),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function rgb_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, &$items, $display) {
$element = array();
switch ($display['type']) {
case 'rgb_raw':
foreach ($items as $key => $value) {
$element[$key] = array(
'#type' => 'markup',
'#markup' => t('#@hex', array('@hex' => $value['rgb'])),
);
}
break;
case 'rgb_box':
foreach ($items as $key => $value) {
$element[$key] = array(
'#type' => 'markup',
'#markup' => '' . check_plain($value['label']) . '',
);
}
break;
}
return $element;
}
Additional resources
Sometimes display formatters need to allow for administrators to configure additional settings. For example choosing which image style to use when displaying an image field. The Field API allows for formatter settings and we can add them by implementing hook_field_formatter_settings_summary() and hook_field_formatter_settings_form(). This lesson shows how to add simple width and height settings for the rgb_box display formatter that will allow an admin to modify the dimensions of the block that is displayed. Then uses those entered values in the implementation of hook_field_formatter_view()
added in the previous lesson to set the CSS width and height of the HTML element being displayed. Allowing site administrators a greater amount of control over what the content looks like without having to write any code. Which, also makes are module more flexible, and more useful in a larger variety of scenarios.
Field formatter settings are access via the Manage Display tab for our Article content type. Any field which provides additional settings will display a gear icon along on the far right that once clicked will reveal the settings form. Field formatter settings are per instance settings.
In addition to providing a settings form we also need to provide a simple text sumary of the settings that can be displayed on the Manage Display tab. This summary is displayed next to our field prior to someone clicking the gear icon that reveals the settings form. This gives the administrator a quick overview of the current configuration for all fields formatters. This is done with hook_field_formatter_settings_summary()
, which despite not being documented as such is required in order to provide field display formatter settings in Drupal 7.
Example:
/**
* Implements hook_field_formatter_settings_summary().
*/
function rgb_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $instance['display'][$view_mode]['settings'];
if ($display['type'] == 'rgb_box') {
$output = t('Box size: @widthx@height', array('@width' => $settings['width'], '@height' => $settings['height']));
return $output;
}
}
/**
* Implements hook_field_formatter_settings_form().
*/
function rgb_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'rgb_box') {
$element['width'] = array(
'#type' => 'textfield',
'#title' => t('Box width'),
'#default_value' => $settings['width'],
);
$element['height'] = array(
'#type' => 'textfield',
'#title' => t('Box height'),
'#default_value' => $settings['height'],
);
}
return $element;
}
Additional resources
Sometimes we need to make use of the collected and stored field data in order to calculate additional values that can be used when displaying a field. The Amazon ASIN field for example can query the Amazon API and get additional information about a product like a thumbnail to display alongside the ASIN value. Using hook_field_load() we can perform additional operations on the stored field values at the time the Field API loads, or requests, the value of our field and present that calculated data long with the stored data.
Should the data be loaded during "view" or "load" operations? In my mind that depends on what it's needed for. A good question to ask might be, "if someone was accessing the content of this field as JSON would they want this data included?". If the answer is yes, you probably want to use hook_field_load()
to perform addition load operations.
When dealing with implementations of hook_field_load()
the most interesting paramater is probably the $items
array. An multi-dimentional array that contains a record for each value value for this particular field for the entity being viewed. Because $items can contain one or more values you'll need to loop over the values within $items
and update them accordingly.
Implementations of hook_field_load()
don't need to return any value. Instead they should update the $items
array which is passed in by reference.
In this example we'll be querying the Google Search API, which returns JSON data. The search API is accessible at URLs like the following: https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=663399. The most important part being the &q=663399
, which we'll replace with our specific search term.
Values that are added to the $items
array will be available to implementations of hook_field_formatter_view()
in the $items
parameter passed to those functions. From there, you can make use of any added data when displaying the field for end users. Because the data is added during the load operation for the entity that the field is attached to it's also available anytime you're making use of the $entity
object.
Example:
/**
* Implements hook_field_load().
*/
function rgb_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
dsm('rgb_field_load');
foreach ($items as $entity_id => $field_values) {
foreach ($field_values as $delta => $value) {
$url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=' . $value['rgb'];
$response = drupal_http_request($url);
if ($response->code == 200) {
$data = drupal_json_decode($response->data);
$links = array();
foreach ($data['responseData']['results'] as $result) {
$links[] = l($result['titleNoFormatting'], $result['url']);
}
$items[$entity_id][$delta]['google_links'] = $links;
}
}
}
}
Additional resources
Stylizer enables site editors to change the styles of panel pane backgrounds, content, text styles, borders, and heading styles. It provides an extensive settings form, including a live preview and integration with the Color module, for point-and-click color picking.
In this lesson, we will:
- Identify style options provided by Panels
- Enable Stylizer module
- Change Styles of a Panel Pane and Heading using Stylizer
By the end of this lesson you should have a good idea of whether or not you want to enable Stylizer on your Panels-based site and if you do, how to access and use it.
Stylizer module comes packaged with CTools.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Views Content Panes is a module that comes packaged with Panels. It provides a new type of Views display called a Content Pane that enables you to pass off Views configuration to the Panel Pane.
In this lesson, we will:
- Enable Views Content Panes module
- Build a View using Content Pane display
- Explore Pane Configuration in Views
By the end of this lesson, you will have a better idea of why you will want to use content panes in Views whenever you are placing Views in Panels.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
In a Views Content Pane display, it's possible to use exposed or contextual filters as panel pane configuration. We'll walk through this process and why you might want to utilize this feature of content panes.
In this lesson...
- Add an exposed filter to a view
- Use the exposed filter as panel pane configuration
- Place the same view twice with different configuration
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
We'll use Page Manager, Panels, and Views to create a customized user account page that features articles authored by the user whose account is being viewed.
In this lesson...
- Build a view of articles with a contextual filter
- Create a customized user account page
By the end of this lesson, you'll walk away with ideas for how to create your own customized user account page.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
The default taxonomy term page provided by Drupal leaves much to be desired. If a taxonomy vocabulary has multiple levels, but content is only tagged with only the child term and not the parent, parent term pages are left with no content listed on them, despite the fact that there is content tagged with terms below it.
In this lesson...
- Create a taxonomy vocabulary with two levels of hierarchy
- Enable the Taxonomy Term Template
- Build a custom term page for each level of hierarchy
By the end of this lesson, you'll know how to create better taxonomy term pages using Views, contextual filters, and Panels.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
With mini-panels, you can build portable panels components and place them as blocks in regions of your theme.
In this lesson...
- Build a 3-column mini-panel
- Place a menu in each column
- Place the mini-panel in the footer region as a block
By the end of this lesson, you will be able to build a mini-panel and understand how to place it in a region using the block administration page.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Panel Nodes module comes packaged with Panels and provides a new content type called Panel.
In this lesson...
- Enable Panel Nodes module
- Create a new node using Panel content type
- Build a simple multi-column page
By the end of this lesson, you will understand the basic functionality of Panel Nodes and why you may or may not want to use it to build one-off pages on your site.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Panelizer is a powerful module that allows you to attach panels to any entity and view mode in Drupal. You can create default templates for all content in a content type, for example, or you can create one-off pages with unique layouts and content panes.
In this lesson...
- Walk through Panelizer admin UI
- Panelize Article content
- Set up default Panelizer template
- Override versus Update Default Template
By the end of this lesson, you should be able to configure Panelizer settings, enable Panelizer for a content type, and understand the benefits and limitations of creating one-off pages that override the default template versus updating the default template.
Enabling the Panels In-Place Editor is recommended for this lesson.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
You can create your Panels layouts with HTML and CSS that can then be selected in the Panels UI.
In this lesson...
- Create a two-column, 60/40 layout
- Use existing layout to quickly get started
- Apply new layout to custom home page
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Creating pages with Panels involves a lot of configuration which can take a lot of time and effort. In order to avoid re-doing all that work on another instance of the site, we can export this configuration into code using Features and deploy it in the usual way (using git or FTP).
In this lesson, we will:
- Export a custom panels page
- Take inventory of all panes
- Create a new Feature to export configuration
By the end of this lesson, you will be able to export a basic panel page configuration that contains a View using Features.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Panelizer configuration involves several layers of configuration. It can be challenging to find all the corners of configuration to export, without needing to still perform some extra manual steps after deployment. With Strongarm module, we can export the related settings that make Panelizer work, avoiding the need for extra manual steps.
In this lesson, we will:
- Enable Strongarm module
- Create new feature
- Export and Deploy Panelizer settings
By the end of this lesson, you should understand how to export all of Panelizer's settings plus the related settings that support its functionality.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Panels provides export code that you can copy and paste into a module or directly import into another instance of the site.
In this lesson, we will:
- Export a panels page using Panels UI
- Import a panels page into another instance of site
By the end of this lesson, you will understand where to find the export code for a panel and be able to simply and quickly import it into another copy of your site.
Demo site log in:
- Navigate to /user
- Login with admin/admin