Without question, Display Suite
 is one of the most popular modules in Drupal’s contributed modules 
history. It allows the creation of layouts, fields and exposes all sorts
 of other powerful tools we use to build the presentation layer of our 
Drupal sites.

One of the more powerful features of Display Suite (DS) is the ability to create custom fields that can be displayed inside DS layouts alongside the actual core field values. In Drupal 7, this has been a very popular way of building layouts and showing dynamic data that is not strictly related to the output of any Field API field on the node (or other) entity.
Display Suite has been ported and is being maintained for Drupal 8. Depending on another contributed module called Layout Plugin, the D8 version offers much of what we have available in Drupal 7 and probably even more.
In this article, we are going to look at how we can create our own Display Suite field in Drupal 8 using the new OOP architecture and plugin system. To demonstrate this, we are going to create a DS field available only on the Article nodes that can be used to display a list of taxonomy terms from a certain vocabulary. And we’re going to make it so that the latter can be configured from the UI, namely admins will be able to specify which vocabulary’s terms should be listed. Not much usefulness in this example, I know, but it will allow you to understand how things work.
If you are following along, the code we write is available in this repository inside the
Display Suite also uses the new plugin system to allow other modules to define DS fields. It exposes a

These will be available for selection in the UI under the

To do this, we need to implement the
Since we are referencing the

Although we don’t need it here, in most cases you’ll need to access the node entity that is currently being rendered. That is available inside the configuration array under the
I would like to mention a few more things before we take a look at the actual
Again we see classes which should be injected but were used statically to save some space. With the risk of sounding like a broken record, keep in mind that you should inject these. For now, we must use them at the top:
One of the more powerful features of Display Suite (DS) is the ability to create custom fields that can be displayed inside DS layouts alongside the actual core field values. In Drupal 7, this has been a very popular way of building layouts and showing dynamic data that is not strictly related to the output of any Field API field on the node (or other) entity.
Display Suite has been ported and is being maintained for Drupal 8. Depending on another contributed module called Layout Plugin, the D8 version offers much of what we have available in Drupal 7 and probably even more.
In this article, we are going to look at how we can create our own Display Suite field in Drupal 8 using the new OOP architecture and plugin system. To demonstrate this, we are going to create a DS field available only on the Article nodes that can be used to display a list of taxonomy terms from a certain vocabulary. And we’re going to make it so that the latter can be configured from the UI, namely admins will be able to specify which vocabulary’s terms should be listed. Not much usefulness in this example, I know, but it will allow you to understand how things work.
If you are following along, the code we write is available in this repository inside the
Demo module. So feel free to check that out.Drupal 8 plugins
Much of the functionality that used to be declared using an_info
 hook in Drupal 7 is now declared using plugins in Drupal 8. For more 
information on using plugins and creating your own plugin types, make 
sure you check out a previous Sitepoint article that talks about just that. Display Suite also uses the new plugin system to allow other modules to define DS fields. It exposes a
DsField plugin type which allows us to write and maintain all the necessary logic for such a field inside a single plugin class (+ any services we might inject into it). So we no longer implement hook_ds_field_info()
 and return an array of field information per entity type, but create a 
plugin class with data straight in its annotation and the relevant logic
 inside its methods.VocabularyTerms class
Let us start by creating our plugin class calledVocabularyTerms inside the src/plugins/DsField folder of our custom module and annotating it for our purposes:namespace Drupal\demo\Plugin\DsField;
use Drupal\ds\Plugin\DsField\DsFieldBase;
/**
 * Plugin that renders the terms from a chosen taxonomy vocabulary.
 *
 * @DsField(
 *   id = "vocabulary_terms",
 *   title = @Translation("Vocabulary Terms"),
 *   entity_type = "node",
 *   provider = "demo",
 *   ui_limit = {"article|*"}
 * )
 */
class VocabularyTerms extends DsFieldBase {
}- The annotation is quite self explanatory: it provides meta information about the plugin.
- The class extends DsFieldBasewhich provides base functionality for all the plugins of this type.
- At the time of writing, the ui_limitannotation has just been committed to HEAD so it might not be available in the release you are using. Limiting the availability of the field on content types and view modes can be done by overriding theisAllowed()method of the base class and performing the logic there.
Default configuration
We want our field to be configurable: the ability to select from a list of existing vocabularies. So let’s start off by providing some defaults to this configuration so that if the user selects nothing, theTags vocabulary which comes with core will be used. For this, we have to implement the defaultConfiguration() method:/**
 * {@inheritdoc}
 */
public function defaultConfiguration() {
  $configuration = array(
    'vocabulary' => 'tags',
  );
  return $configuration;
}Formatters
We also want to have the ability to specify from the UI if the list of taxonomy terms is a series of links to their term pages or formatter as plain text. We could implement this within the configuration realm but let’s do so using formatters instead. And it’s very simple: we implement theformatters() method and return an array of available formatters:/**
 * {@inheritdoc}
 */
public function formatters() {
  return array('linked' => 'Linked', 'unlinked' => 'Unlinked');
}These will be available for selection in the UI under the
Field
 heading of the Manage Display page of the content type. And we’ll be 
able to see the choice when we are building the actual field for 
display. But more on that in a second. Configuration summary
It’s also recommended that if we are using UI defined settings, we have a summary of what has been selected as a simple string that describes it. This gets printed under theWidget heading of the Manage Display page of the content type. To do this, we need to implement the
settingsSummary() method and return said text:/**
 * {@inheritdoc}
 */
public function settingsSummary($settings) {
  $config = $this->getConfiguration();
  $no_selection = array('No vocabulary selected.');
  if (isset($config['vocabulary']) && $config['vocabulary']) {
    $vocabulary = Vocabulary::load($config['vocabulary']);
    return $vocabulary ? array('Vocabulary: ' . $vocabulary->label()) : $no_selection;
  }
  return $no_selection;
}getConfiguration()
 method on our plugin class. What we do above, then, is check if the 
vocabulary setting has been set, we load it based on its machine name 
using the Vocabulary class and return an array of strings that need to be printed. Since we are referencing the
Vocabulary class, we also need to use it at the top:use Drupal\taxonomy\Entity\Vocabulary;Vocabulary statically here to load an entity for the sake of brevity. It is highly recommended you inject the relevant storage using dependency injection and use that to load entities. The same goes for most classes you’ll see me referencing statically below. Settings form
Now that we display which configuration has been chosen from the UI, it’s time to provide the actual form which will allow the user to do so. This will be made available by clicking the cogwheel under theOperations heading of the Manage Display page of the content type. /**
 * {@inheritdoc}
 */
public function settingsForm($form, FormStateInterface $form_state) {
  $config = $this->getConfiguration();
  $names = taxonomy_vocabulary_get_names();
  $vocabularies = Vocabulary::loadMultiple($names); 
  $options = array();
  foreach ($vocabularies as $vocabulary) {
    $options[$vocabulary->id()] = $vocabulary->label();
  }
  $settings['vocabulary'] = array(
    '#type' => 'select',
    '#title' => t('Vocabulary'),
    '#default_value' => $config['vocabulary'],
    '#options' => $options,
  );
  return $settings;
}Rendering the field
The last thing left to do is implement thebuild() method responsible for rendering the contents of our field:/**
 * {@inheritdoc}
 */
public function build() {
  $config = $this->getConfiguration();
  if (!isset($config['vocabulary']) || !$config['vocabulary']) {
    return;
  }
  $query = \Drupal::entityQuery('taxonomy_term')
    ->condition('vid', $config['vocabulary']);
  $tids = $query->execute();
  if (!$tids) {
    return;
  }
  $terms = Term::loadMultiple($tids);
  if (!$terms) {
    return;
   }
  return array(
    '#theme' => 'item_list',
    '#items' => $this->buildTermList($terms),
  );
}item_list theme to print our terms. Although we don’t need it here, in most cases you’ll need to access the node entity that is currently being rendered. That is available inside the configuration array under the
entity key. Moreover, under the build
 key you have the actual render array of the node being built. So keep 
this in mind and do inspect the other elements of the configuration 
array on your own for more information.I would like to mention a few more things before we take a look at the actual
buildTermList()
 method. First, for brevity, we used the EntityQuery service statically.
 In your project, you should inject it. Second, we used the Term
 class statically to load the taxonomy term entities. Again, you should 
inject its storage and use that for this purpose. And lastly, we should 
import the Term class at the top with use:use Drupal\taxonomy\Entity\Term;buildTermList() method:private function buildTermList(array $terms) {
  $config = $this->getConfiguration();
  $formatter = isset($config['field']['formatter']) && $config['field']['formatter'] ? $config['field']['formatter'] : 'unlinked';
  $items = array();
  foreach ($terms as $term) {
    $items[] = $this->buildTermListItem($term, $formatter);
  }
  return $items;
}item_list 
theme. As you can see, though, the individual term entity and formatter 
are passed to yet another helper method to keep things nice and tidy:private function buildTermListItem(Term $term, $formatter) {
  if ($formatter === 'linked') {
    $link_url = Url::fromRoute('entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id()));
    return \Drupal::l($term->label(), $link_url);
  }
  return SafeMarkup::checkPlain($term->label());
}buildTermListItem() method we either return the sanitized title of the term or a link to it depending on the formatter. Again we see classes which should be injected but were used statically to save some space. With the risk of sounding like a broken record, keep in mind that you should inject these. For now, we must use them at the top:
use Drupal\Core\Url;
use Drupal\Component\Utility\SafeMarkup; 
 
No comments:
Post a Comment
Thanks for your comment.