Skip to content

sujanshresthanet/Custom-field-types-widgets-and-formatters

Repository files navigation

Donate via PayPal Please consider supporting this project by making a donation via PayPal

Description

I have created a "basic" custom field in Drupal 8. I won't go into detail about PSR–4, annotations or plugins or this tutorial will be huge.

Instead, I'll add links to other websites that explain the concept further.

That being said, if you're looking for detailed documentation on the Field API in Drupal 8.

In Drupal 8, fields are not implemented using hooks like they are in Drupal 7. Instead, they are created using Drupal 8's new Plugin API. This means that instead of implementing hooks, we define a class for a widget, formatter and field item. Most Drupal 7 field hooks like hook_field_schema, hook_field_is_empty and more; are now methods in classes.

Getting started:-

Step 1: Implement Field Item

The first bit of work we need to do is define a field item class called CustomFieldsItem that extends the FieldItemBase class.

1. In Drupal 8 classes are loaded using PSR-4.

So, to define the CustomFieldsItem class, we need to create a CustomFieldsItem.php file and place it in "module"/src/Plugin/Field/FieldType/CustomFieldsItem.php

/**
 * @file
 * Contains \Drupal\custom_fields\Plugin\Field\FieldType\CustomFieldsItem.
 */

namespace Drupal\custom_fields\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;

Then in the file we add a namespace Drupal\custom_fields\Plugin\Field\FieldType and three use statements:

  • Drupal\Core\Field\FieldItemBase.
  • Drupal\Core\Field\FieldStorageDefinitionInterface .
  • Drupal\Core\TypedData\DataDefinition.

2. Now we need to define the actual field details like the field id, label, default widget and formatter etc.. This is equivalent of implementing hook_field_info in Drupal 7.

In Drupal 8 a lot, if not all, of the info hooks have been replaced by annotations.

/**
 * Plugin implementation of the 'custom_fields' field type.
 *
 * @FieldType(
 *   id = "custom_fields_code",
 *   label = @Translation("CustomFields field"),
 *   description = @Translation("This field stores code custom_fields in the database."),
 *   default_widget = "custom_fields_default",
 *   default_formatter = "custom_fields_default"
 * )
 */
class CustomFieldsItem extends FieldItemBase { }

So instead of implementing hook_field_info, we define the field as an annotation inside of a comment above the class.

The annotation attributes are quite self-explanatory. Just make sure that the default_widget and default_formatter reference the widget and formatter annotation ID and not the class.

If you want to learn more about annotations, check out the Annotations-based plugins documentation page on drupal.org.

3. Now that we have our field item class, we need to define a few methods. The first one we'll look at is schema().

In Drupal 7, when you create a custom field you define its schema using hook_field_schema. In Drupal 8, we define the schema by adding a schema() method to the CustomFieldsItem class.

The Schema API documentation provides a description of schema array structure and possible values. 

/**
 * {@inheritdoc}
 */
public static function schema(FieldStorageDefinitionInterface $field) {
  return array(
    'columns' => array(
      'source_description' => array(
        'type' => 'varchar',
        'length' => 256,
        'not null' => FALSE,
      ),
      'source_code' => array(
        'type' => 'text',
        'size' => 'big',
        'not null' => FALSE,
      ),
      'source_lang' => array(
        'type' => 'varchar',
        'length' => 256,
        'not null' => FALSE,
      ),
    ),
  );
}

4. Now we need to add the isEmpty() method and define what constitutes an empty field item. This method is the same as implementing hook_field_is_empty in Drupal 7.

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  $value = $this->get('source_code')->getValue();
  return $value === NULL || $value === '';
}

5. The final method we'll add to the class is the propertyDefinitions() method.

/**
 * {@inheritdoc}
 */
static $propertyDefinitions;

/**
 * {@inheritdoc}
 */
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['source_description'] = DataDefinition::create('string')
      ->setLabel(t('CustomField description'));

    $properties['source_code'] = DataDefinition::create('string')
      ->setLabel(t('CustomField code'));

    $properties['source_lang'] = DataDefinition::create('string')
      ->setLabel(t('Programming Language'))
      ->setDescription(t('CustomField code language'));

    return $properties;
  }

This method is used to define the type of data that exists in the field values. The "CustomFields field" has just three values: description, code and language. So I just added those values to the method as strings.

Go to the How Entity API implements Typed Data API documentation on drupal.org to learn more about this.

Note: it needs to be updated to the PSR-4 specification, see https://www.drupal.org/node/2128865 for more details._

Step 2: Implement Field Widget

Now that we've defined the field item, let's create the field widget. We need to create a class called CustomFieldsDefaultWidget that extends the WidgetBase class.

1. So create a CustomFieldsDefaultWidget.php file and add it to "module"/src/Plugin/Field/FieldWidget/CustomFieldsDefaultWidget.php.

/**
 * @file
 * Contains \Drupal\custom_fields\Plugin\Field\FieldWidget\CustomFieldsDefaultWidget.
 */

namespace Drupal\custom_fields\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;

Make sure the file namespace is Drupal\custom_fields\Plugin\Field\FieldWidget and add the following three use statements:

  • Drupal\Core\Field\FieldItemListInterface.
  • Drupal\Core\Field\WidgetBase.
  • Drupal\Core\Form\FormStateInterface.

2. Next, we need to define the widget using an annotation. This is the equivalent of using hook_field_widget_info in Drupal 7.

/**
 * Plugin implementation of the 'custom_fields_default' widget.
 *
 * @FieldWidget(
 *   id = "custom_fields_default",
 *   label = @Translation("CustomFields default"),
 *   field_types = {
 *     "custom_fields_code"
 *   }
 * )
 */
class CustomFieldsDefaultWidget extends WidgetBase { }

Just a heads up, make sure that the field_types attribute in the annotation references the field types using their ID. For this module, it is custom_fields_code because we added id = "custom_fields_code", to the @FieldType annotation.

3. And finally, we need to define the actual widget form. We do this by adding a formElement() method to the CustomFieldsDefaultWidget class. This method is the same as using hook_field_widget_form in Drupal 7.

/**
 * {@inheritdoc}
 */
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {

  $element['source_description'] = array(
        '#title' => $this->t('Description'),
        '#type' => 'textfield',
        '#default_value' => isset($items[$delta]->source_description) ? $items[$delta]->source_description : NULL,
      );
  $element['source_code'] = array(
        '#title' => $this->t('Code'),
        '#type' => 'textarea',
        '#default_value' => isset($items[$delta]->source_code) ? $items[$delta]->source_code : NULL,
      );
  $element['source_lang'] = array(
        '#title' => $this->t('Source language'),
        '#type' => 'textfield',
        '#default_value' => isset($items[$delta]->source_lang) ? $items[$delta]->source_lang : NULL,
      );
  return $element;
}

Note: it needs to be updated to the PSR-4 specification, see https://www.drupal.org/node/2128865 for more details._

Step 3: Implement Field Formatter

The final piece to the puzzle, is the field formatter, and we create it by defining a class called CustomFieldsDefaultFormatter that extends the FormatterBase class.

1. Create a CustomFieldsDefaultFormatter.php file and add it to "module"/src/Plugin/Field/FieldFormatter/CustomFieldsDefaultFormatter.php.

/**
 * @file
 * Contains \Drupal\custom_fields\Plugin\field\formatter\CustomFieldsDefaultFormatter.
 */

namespace Drupal\custom_fields\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;

Make sure the file namespace is Drupal\custom_fields\Plugin\Field\FieldFormatter and add the following use statements:

  • Drupal\Core\Field\FieldItemListInterface.
  • Drupal\Core\Field\FormatterBase.

2. Next, we need to define the formatter as an annotation. The same as we did for the widget and field type, this is the equivalent of using hook_field_formatter_info.

/**
 * Plugin implementation of the 'custom_fields_default' formatter.
 *
 * @FieldFormatter(
 *   id = "custom_fields_default",
 *   label = @Translation("CustomFields default"),
 *   field_types = {
 *     "custom_fields_code"
 *   }
 * )
 */
class CustomFieldsDefaultFormatter extends FormatterBase { }

3. Now the only thing left to do is add the viewElements() method and define the actual field formatter. Again, this method is the same as using hook_field_formatter_view in Drupal 7.

/**
 * {@inheritdoc}
 */
public function viewElements(FieldItemListInterface $items, $langcode) {
  $elements = array();
  foreach ($items as $delta => $item) {
    // Render output using custom_fields_default theme.
    $source = array(
      '#theme' => 'custom_fields_default',
      '#source_description' => $item->source_description,
      '#source_code' => $item->source_code,
    );

    $elements[$delta] = array('#markup' => \Drupal::service('renderer')->render($source));
  }

  return $elements;
}

4. Create a custom_fields.module file and add code that defines twig templates. The key of each item in the array is what you will need to call the template.

/**
 * Implements hook_theme().
 */
function custom_fields_theme() {
  return array(
    'custom_fields_default' => array(
      'variables' => array('source_description' => NULL, 'source_code' => NULL),
      'template' => 'custom-fields-default',
    ),
  );
}

5. In our module, inside of the templates folder, create a twig template. The name of the file has to match what you put into hook_theme() (make sure replace underscores with dashes). In this case, the file name would be custom-fields-default.html.twig. The reason for this is I didn't want to put a lot of logic or HTML code in the viewElements() method.

{% if source_description %}
  <div class="snippets-description">{{ source_description }}</div>
{% endif %}
{% if source_code %}
  <pre>{{ source_code }}</pre>
{% endif %}

Conclusion

As stated earlier the biggest change in Drupal 8 is that fields are created using the Plugin API and not hooks. Once you understand that, the concept of creating a field is very similar to Drupal 7. A lot of the methods in Drupal 8 match the hooks in Drupal 7.

About

Drupal 9 Module: Provides custom field types, widgets, and formatters for content types and entities

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published