Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll

9.8. Working with forms in Drupal. Add configuration form programmatically.

27/09/2019, by mikhail

In this tutorial, we will deal with the Drupal Form API and create a settings form for the module. We have already created modules for displaying the page and block, let's now create a configuration form in which we will store data for connecting to a conditional service. Let's say that we need to store the API Key and the API Client ID on the site, for example, for the Google Maps API.

Code examples can be viewed on github:
https://github.com/levmyshkin/drupalbook8

We can store this data in settings.php and add these settings to git. But it will not be safe, stores access to services better in the database.

Settings form

Let's add another route for our form:

modules/custom/drupalbook/drupalbook.routing.yml

drupalbook.settings:
  path: '/admin/structure/drupalbook/settings'
  defaults:
    _form: '\Drupal\drupalbook\Form\DrupalbookSettingsForm'
    _title: 'DrupalBook Settings form'
  requirements:
    _permission: 'administer site configuration'

Unlike previous routes in defaults, we specify not _controller, but _form. The fact is that we will not create a Controller class for the form, but a form class. Let's create a file for the form class:

modules/custom/drupalbook/src/Form/DrupalbookSettingsForm.php

You will need to create a separate Form folder in the src folder for your forms. This allows you to separate the module code into separate folders and you can also easily find the code you need based on the name of the folders.

Add the following form code and we will analyze each of the blocks of code and how it works:

<?php

namespace Drupal\drupalbook\Form;
 
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
 
/**
 * Configure example settings for this site.
 */
class DrupalbookSettingsForm extends ConfigFormBase {
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'drupalbook_admin_settings';
  }
 
  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [
      'drupalbook.settings',
    ];
  }
 
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('drupalbook.settings');
 
    $form['drupalbook_api_key'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('API Key'),
      '#default_value' => $config->get('drupalbook_api_key'),
    );
 
    $form['drupalbook_api_client_id'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('API Client ID'),
      '#default_value' => $config->get('drupalbook_api_client_id'),
    );
 
    return parent::buildForm($form, $form_state);
  }
 
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Retrieve the configuration
    $this->configFactory->getEditable('drupalbook.settings')
      // Set the submitted configuration setting
      ->set('drupalbook_api_key', $form_state->getValue('drupalbook_api_key'))
      // You can set multiple configurations at once by making
      // multiple calls to set()
      ->set('drupalbook_api_client_id', $form_state->getValue('drupalbook_api_client_id'))
      ->save();
 
    parent::submitForm($form, $form_state);
  }
}

We have already dealt with namespace and use operators, and that Drupal uses them to connect automatically only those classes that are needed:

\drupalbook\Form;
 
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

To create a configuration form, you need to inherit from the ConfigFormBase class, ConfigFormBase implies that you will save data from the form to the configuration.

extends ConfigFormBase {

Next, specify the Form ID, it must be unique for each form. If you write the id of your form starting with the name of the module, then for sure your id will be unique:

function getFormId() {
  return 'drupalbook_admin_settings';
}

Specify the group of configs in which we will store the data:

function getEditableConfigNames() {
  return [
    'drupalbook.settings',
  ];
}

Now let's see how we create the form fields themselves. You can evaluate the capabilities of the Form API and what fields you can display by reading the documentation:

https://api.drupal.org/api/drupal/elements/8.5.x

We used so far only textfield:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Textfield.php/class/Textfield/8.5.x

But you often have to deal with drop-down lists:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Select.php/class/Select/8.5.x

By checkboxes and radio buttons:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Checkboxes.php/class/Checkboxes/8.5.x

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Radios.php/class/Radios/8.5.x

Try adding your fields to the form, not limited to text fields.

function buildForm(array $form, FormStateInterface $form_state) {
  $config = $this->config('drupalbook.settings');

We form a form array in $ form and then build HTML from it. The variable $ form_state stores in itself all the data from the form that we submit, here and the values ​​of all fields, the form id, CSRF token to protect against automatic form submission. $ form_state also allows you to transfer data between the steps of a multi-step form, we will use this in one of the following lessons. Also, every time a form is submitted via AJAX, the form is rebuilt again, and $ form_state allows you to build a copy of the form that was before clicking the submit button. And if the form is not submitted due to an error, for example, some field is not filled, then all the fields will be saved with the values ​​that were stored in $ form_state. Therefore, $ form and $ form_state always go together.

Here we also load data from configs. Perhaps you already saved something in drupalbook.settings and $ config is no longer empty, this will allow you to set the current values ​​in the fields using #default_value in each of the text fields, receiving data from the configs using the get() method.

['drupalbook_api_key'] = array(
  '#type' => 'textfield',
  '#title' => $this->t('API Key'),
  '#default_value' => $config->get('drupalbook_api_key'),
);
 
$form['drupalbook_api_client_id'] = array(
  '#type' => 'textfield',
  '#title' => $this->t('API Client ID'),
  '#default_value' => $config->get('drupalbook_api_client_id'),
);

And at the end of the method, we return $ form and $ form_state so that the form is built.

::buildForm($form, $form_state);

Next, we have the submitForm() method, it fires if the form was submitted and no errors occurred. If nevertheless you had an unfilled required field and Drupal throws an error, then submitForm will not work. If you want to check the values ​​of the submitted data from the form, then you need to use validateForm(), validate will work even if there is an error in the form and using validate you can cancel the form submission and cause an error if something does not suit you in the data. We will cover validate in one of the following forms tutorials.

In the submitForm() method, we go through all the fields, collect their values ​​and update the drupalbook.settings configuration:

function submitForm(array &$form, FormStateInterface $form_state) {
  // Retrieve the configuration
  $this->configFactory->getEditable('drupalbook.settings')
    // Set the submitted configuration setting
    ->set('drupalbook_api_key', $form_state->getValue('drupalbook_api_key'))
    // You can set multiple configurations at once by making
    // multiple calls to set()
    ->set('drupalbook_api_client_id', $form_state->getValue('drupalbook_api_client_id'))
    ->save();
 
  parent::submitForm($form, $form_state);
}

We also call the parent method submitForm, which displays a message about the successful submission of the form. You can comment out this line and write your message:

//parent::submitForm($form, $form_state);
drupal_set_message($this->t('My Cool Form have been saved!'));

Remember to clear the cache for your route to apply. Now you can try your form in action. When you need to load the API Key, you can use this code:

$config = \Drupal::config('example.settings');
$api_key =$config->get('drupalbook_api_key');
$api_client_id = $config->get('drupalbook_api_client_id');

This code will work in any module or preprocess function, because there is a single configuration system in Drupal.

That's all in the next lesson on forms, we will analyze how to make multi-step forms.

Code examples can be viewed on github:
https://github.com/levmyshkin/drupalbook8