Blog

11.03.2014
bobo

Field API D8 - Custom Formatters & Widgets

Drupal 8 has dramatically changed the process of creation of custom formatters and widgets. If in Drupal 7 this could have been done with hooks usage, then in the new version this process is similar to Ctools plugin writing. Further in this blog we will explain why.

In Drupal 8 hooks are replaced by class methods, and this actually means that the main file of the module (.module) can remain even empty. The use of classes means inheritance and it can be clearly seen in Drupal 8 core. E.g. image formatter inherits file formatter. Further we will deal with the process of creating one’s own formatter for the field.

First a web developer needs to create a folder for module plugins and to place all of them (formatters, widgets, etc.) to the separate files. The path will be the following:

example_module/lib/Drupal/example_module/Plugin/ field/FieldFormatter/ExampleFormatter.php

A large number of directories make immutable attributes of PSR-0 standard. On drupal.org there is a separate theme devoted to the discussion of this and similar PSR-4 standard as well as their relevance.

In Drupal 7 creation of one’s own formatters begins from hook_field_formatter_info(). But in the new version the most (if not all) of the info-hooks are replaced with annotations. In this case, the annotative class \Drupal\field\Annotation\FieldFormatter is used. Here is an example of plugin-style formatter:

<?php
/**
* Plugin implementation of the 'example_formatter' formatter
*
* @FieldFormatter(
*   id = "example_formatter",
*   label = @Translation("Example formatter"),
*   field_types = {
*   "text",
*   },
*   settings = {
*   "trim_length" = "300",
*   },
*   edit = {
* "editor" = "form"
*   }
* )
*/
class ExampleFormatter extends FormatterBase { 
 }

Next you need to use hook_field_formatter_settings_form() - now it looks like FormatterInterface::settingsForm(). In fact, this function is a simple form. The only thing to consider is the fact that in this case formatters’ settings are available by writing the function $this->getSetting('settings_key'). There are also some changes in such hooks as hook_field_formatter_prepare_view(), now they look like this: FormatterInterface::prepareView() and hook_field_formatter_view() - FormatterInterface::viewElements(). In addition, methods have obtained the field value as an object \Drupal\Core\Field\FieldItemListInterface. Earlier they made a simple block $items. See topic Drupal 8 Entity API for details.

As for hook-alters they have not been changed - hook_field_formatter_settings_summary_alter(),  hook_field_formatter_info_alter() and hook_field_formatter_settings_form_alter().

The next step is to create your own widget. This process requires a little bit more work than formatter writing. Previously 4 hooks were used here. Now it is moved to methods in classes using the new Plugin API. As is shown in the example with formatter creation, the hook_field_widget_info() migrated to the annotation. Let’s create a file example_module/lib/Drupal/ example_module/Plugin/field/FieldWidget/ExampleWidget.php, which is similar to the formatter. Here is an example:

<?php
/**
* Plugin implementation of the 'example_widget' widget
*
* @FieldWidget(
*   id = "example_widget",
*   label = @Translation("Example widget"),
*   field_types = {
*   "text",
*   "text_long"
*   },
*   settings = {
*   "size" = "600",
*   }
* )
*/
class ExampleWidget extends WidgetBase { }

The very notion of "instance" disappears and settings described in the abstract can be pulled from $this->getSetting('settings_key') or $this->getSettings() without specifying the key. If you want to refer to other field properties, you can use the method $this->getFieldDefinition(), which returns an object \Drupal\Core\Entity\Field\ FieldDefinitionInterface. This interface combines things that used to be separate and were named $field and $instance. Here is an example WidgetInterface::settingsForm():

<?php
  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, array &$form_state) {
    $element = array();

    $element['size'] = array(
      '#type' => 'number',
      '#title' => t('Size of textfield'),
      '#default_value' => $this->getSetting('size'),
      '#required' => TRUE,
      '#min' => 1,
  );

  return $element;
  }

hook_field_widget_form() became WidgetInterface::formElement():

<?php
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
    $main_widget = $element + array(
      '#type' => 'textfield',
      '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL,
      '#size' => $this->getSetting('size'),
      '#placeholder' => $this->getSetting('placeholder'),
      '#maxlength' => $this->getSetting('max_length'),
      '#attributes' => array('class' => array('text-full')),
  );

  if ($this->getSetting('text_processing')) {
      $element = $main_widget;
      $element['#type'] = 'text_format';
      $element['#format'] = isset($items[$delta]->format) ? $items[$delta]->format : NULL;
      $element['#base_type'] = $main_widget['#type'];
  }
  else {
      $element['value'] = $main_widget;
  }

  return $element;
  }

Apart from this hook_field_widget_error() was replaced by WidgetInterface::errorElement().  Also some new methods such as WidgetInterface::settingsSummary() and WidgetInterface::massageFormValues(). were added. Information about them can be found on drupal.org.

Thus we can conclude that the new Plugin API makes a very handy tool for writing your own functionality, such as widgets and formatters. The very structure of the Field API in Drupal 8 is quite similar to the older version, but the impact of object oriented programming and PSR-0 is obvious. In other words, there is a strict division of the files in directories according to so-called namespaces, which categorize and group the files during the development process.