Home Drupal Drupal 8 Third Party Settings and Pseudo-Fields – SitePoint

Drupal 8 Third Party Settings and Pseudo-Fields – SitePoint

0
4
Logotipo de Drupal 8

In the first installment of this series we started our journey towards creating some simple but powerful functionality. The goal we set was to have the possibility to load a form on each node page and to be able to choose which form type should be used on the different node bundles.

The first step is done. We created a custom plugin type called ReusableForm already featured with a base plugin class that new plugins can extend. Additionally, we saw that each plugin will interact with a form class that is defined in their annotation. And like with the plugins, we also created a base class new forms can extend.

It follows to see how we can configure the core node types to use one of the plugins defined on the site and how to render the relevant form when viewing the node. But first, in order to have something to work with, let’s create our first ReusableForm plugin that uses a very simple form.

Our first plugin

Inside the src/Form folder of our module, create a class called BasicForm.php (or whatever you want to call it). Inside, we can have this simple form definition:

php

namespace Drupalreusable_formsForm;

use DrupalCoreFormFormStateInterface;

/**
* Defines the BasicForm class.
*/

class BasicForm extends ReusableFormBase {

/**
* {@inheritdoc}.
*/

public function getFormId() {
return ‘basic_form’;
}

/**
* {@inheritdoc}.
*/

public function buildForm(array $form, FormStateInterface $form_state) {

$form = parent::buildForm($form, $form_state);

return $form;
}

/**
* {@inheritdoc}
*/

public function submitForm(array &$form, FormStateInterface $form_state) {
// Handle form submission.
}
}

This form class extends our form base and implements all the required methods. For more information on forms and how they work in Drupal 8, you can check out one of my previous articles on Sitepoint.com. But as you can see, we are calling the parent buildForm() method so that the logic we defined in the base class takes place. The rest is implementation detail (and here we don’t do much). But you can perform whatever logic you want and handle the submissions in any way you need.

This form can now be used with a plugin of our type and it will have 3 form elements inherited from the form base class.

Next, let’s create our first plugin. Inside the src/Plugin/ReusableForm folder of our module, we can have a file called BasicForm.php with the following:

php

namespace Drupalreusable_formsPluginReusableForm;

use Drupalreusable_formsReusableFormPluginBase;

/**
* Provides a basic form.
*
* @ReusableForm(
* id = «basic_form»,
* name = @Translation(«Basic Form»),
* form = «Drupalreusable_formsFormBasicForm»
* )
*/

class BasicForm extends ReusableFormPluginBase {}

As you can see, all we need for our purposes is in the annotation: the id, name and form class to be used with this plugin. You can add your own methods, override existing ones and handle way more complex logic if you want, though. But for us this is ok as the ReusableFormPluginBase class handles the form building already.

And that’s it. This is all it takes to now define new ReusableForm plugins. We create a form class with all the fields and logic it needs and then reference it inside a simple plugin class.

Node type configuration

Now that we have a ReusableForm plugin, we can proceed with configuring the node type entities to make use of it. For this, we’ll need to first turn our plugin manager into a service so that we can access it and load plugins.

Inside the reusable_forms.services.yml file of our module we can have this:

services:
plugin
.manager.reusable_forms:
class: Drupalreusable_formsReusableFormsManager
parent
: default_plugin_manager

Now we’ll be able to access the plugin manager using the plugin.manager.reusable_forms service id. And by using the parent key in the definition, we specified that all its dependencies can be looked up from the parent. That’s cool!

Next, let’s turn to our .module file and do a couple of things in there. First, we want to alter the node type edit form and add some extra information about our form plugins. This is so that when users edit a node bundle they can select which form plugin should be used with the nodes of that type:

/**
* Implements hook_form_BASE_FORM_ID_alter().
*/

function reusable_forms_form_node_type_form_alter(&$form, FormStateInterface $form_state) {

$form[‘reusable_forms’] = array(
‘#type’ => ‘details’,
‘#title’ => t(‘Reusable forms’),
‘#group’ => ‘additional_settings’,
);

// Load the current node type configuration entity.
$node_type
= $form_state->getFormObject()->getEntity();

$form[‘reusable_forms’][‘reusable_forms_enabled’] = array(
‘#type’ => ‘checkbox’,
‘#title’ => t(‘Enable reusable forms’),
‘#description’ => t(‘Check this box if you would like a reusable form on this node type.’),
‘#default_value’ => $node_type->getThirdPartySetting(‘reusable_forms’, ‘enabled’, 0),
);

$form_plugins = Drupal::service(‘plugin.manager.reusable_forms’)->getDefinitions();
$options
= [];
foreach ($form_plugins as $name => $plugin) {
$options
[$name] = $plugin[‘name’];
}

$form[‘reusable_forms’][‘reusable_forms_enabled’] = array(
‘#type’ => ‘radios’,
‘#title’ => t(‘Available forms’),
‘#default_value’ => $node_type->getThirdPartySetting(‘reusable_forms’, ‘plugin’, ‘basic_form’),
‘#options’ => $options,
‘#description’ => t(‘The available forms you can choose from for this node type.’),
‘#states’ => array(
‘visible’ => array(
‘:input[name=»reusable_forms_enabled»]’ => array(‘checked’ => TRUE),
),
),
);

$form[‘#entity_builders’][] = ‘reusable_forms_form_node_type_form_builder’;
}

Implementing hook_form_BASE_FORM_ID_alter will do the trick perfectly for this. Though we mustn’t forget to use the FormStateInterface class at the top:

use DrupalCoreFormFormStateInterface;

So what happens here? We are creating a new fieldset to group two form fields relevant for our purpose: a checkbox to enable the reusable forms and a select list to choose from the existing plugins. As options to the latter we are building an array of all the plugin names after we load our plugin manager and request from it all the available definitions. And using the #states magic we make sure that this latter field is only visible if the checkbox to enable the forms is checked.

Right at the end we are adding an extra callback function to the #entity_builders group that will be triggered when the entity is saved and that has the purpose of mapping values to an entity. So let’s see that function now:

/**
* Entity form builder for the node type form to map some values to third party
* settings
*/

function reusable_forms_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) {
if ($form_state->getValue(‘reusable_forms_enabled’) === 1) {
$type
->setThirdPartySetting(‘reusable_forms’, ‘enabled’, 1);
$type
->setThirdPartySetting(‘reusable_forms’, ‘plugin’, $form_state->getValue(‘reusable_forms_enabled’));
return;
}

$type->unsetThirdPartySetting(‘reusable_forms’, ‘enabled’);
$type
->unsetThirdPartySetting(‘reusable_forms’, ‘plugin’);
}

And again, let’s make use of the NodeTypeInterface at the top:

use DrupalnodeNodeTypeInterface;

In this function we are doing something simple but awesome. If the admin has enabled the use of reusable forms on this bundle, we make use of the configuration entity’s third party settings space to store our data. Otherwise we simply unset it if it exists.

I purposefully ignored this last point when talking about the form alter we implemented before. Now you can see how the form element default values are populated by making a request to the third party settings object on the node type configuration entity.

Configuration schema

Before moving on to actually displaying the forms, we need to also add the schema definition for the new configuration we are storing. Inside the config/schema/reusable_forms.schema.yml file of our module we can add the following:

node.type.*.third_party.reusable_forms:
type
: mapping
label
: ‘Reusable Forms’
mapping
:
reusable_forms_enabled
:
type
: boolean
label
: ‘Whether to enable the reusable forms on this node type’
reusable_forms_enabled
:
type
: sequence
label
: ‘Available forms’
sequence
:
type
: string
label
: ‘Plugin name’

Node view

Now that we are storing the user preferences on the node type config entity, let’s see how we can render our chosen form plugins on the node pages of the enabled types.

The first thing we’re going to do is define a content entity pseudo field that will be configurable from the node Manage display interface. Still inside our .module file we can have this:

/**
* Implements hook_entity_extra_field_info().
*/

function reusable_forms_entity_extra_field_info() {
$extra
= array();

$bundles = NodeType::loadMultiple();
$bundles
= array_filter($bundles, function($bundle){
return $bundle->getThirdPartySetting(‘reusable_forms’, ‘enabled’) === 1;
});

foreach ($bundles as $bundle) {
$extra
[‘node’][$bundle->Id()][‘display’][‘reusable_form’] = array(
‘label’ => t(‘Reusable form’),
‘description’ => t(‘Reusable form’),
‘weight’ => 100,
‘visible’ => TRUE,
);
}

return $extra;
}

And again we have to use NodeType at the top:

use DrupalnodeEntityNodeType;

What happens here is simple. We load all the node bundles and filter out the ones for which the reusable forms are not enabled. Then for each one we define an extra display component in a very self-explanatory way.

By clearing the cache will be able to already see the new pseudo field in place on the configuration page – though it won’t yet do anything. Which brings us to the last piece of the puzzle, rendering the relevant form.

To make the pseudo field we just defined useful, we need to implement hook_entity_view (or a variant of it) and render its content:

/**
* Implements hook_ENTITY_TYPE_view().
*/

function reusable_forms_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
if ($display->getComponent(‘reusable_form’)) {
$plugin_manager
= Drupal::service(‘plugin.manager.reusable_forms’);
$node_type
= NodeType::load($entity->bundle());
$plugin
= $node_type->getThirdPartySetting(‘reusable_forms’, ‘plugin’);
if (!$plugin) {
return;
}
$build
[‘reusable_form’] = $plugin_manager->createInstance($plugin)->buildForm($entity);
}
}

And let’s not forget the use statements at the top:

use DrupalCoreEntityDisplayEntityViewDisplayInterface;
use DrupalCoreEntityEntityInterface;

First, we check if the reusable_form component exists on this node’s display (whether it is made visible in the UI). If it is, we add the reusable_form key to the render array that is building this node view.

We start by loading the plugin manager and the node type configuration object of the current node entity. Then we use the former to instantiate a plugin with the ID defined in the third party settings of the latter. And in doing so, as you remember, we are passing as argument the current entity object so that the form being built can be aware of the entity it is showing up with (though we are not really taking advantage of this in our example).

And that’s it. What’s left is to install the module from scratch (since we added the config schema), edit a node type and select our default plugin (or another one if you created it). Then you can configure the display of this node type to show the reusable form which will then do so when viewing a node of that type. Neat.

Conclusion

As we’ve seen in this article series, Drupal 8 provides us with the tools to do some powerful stuff. With the plugin system we set up the basis for our reusable functionality. And then we hooked into various points of Drupal core to make use of this logic. We’ve seen how any configuration entity that implements the ThirdPartySettingInterface can be extended to include new data. And lastly, we’ve displayed relevant data when viewing content entities with the help of pseudo fields.

But this is just the beginning. You can take what we did here and extend it to your needs. For instance, you can create a new content entity type that will model form submissions. The possibilities are endless.

NO COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here

GDPR Cookie Consent with Real Cookie Banner
Index