“Chaos Tool Suite (CTools) is primarily a set of APIs and tools to improve the developer experience.” - Drupal.org
Chaos Tool Suite (CTools) is a contributed project in Drupal that is oriented towards developers. It is essentially a set of helper classes that can be extended to simplify certain workflows and defining custom functionalities. Many contributed modules including Pathauto, Panels and Page Manager use the API provided by CTools.
Since Drupal 8, a major chunk of CTools has been moved into core. This includes TempStore, Configuration Management Initiative (CMI), Config Entities, Plugins, AJAX and Modals, and other miscellaneous helper classes. Kris Vanderwater, one of the maintainers for Chaos Tool, described CTools as “a testing ground for what Drupal core should be doing”.
The project is still undergoing changes and development of tools that may be moved to core in the future. Let us have a look at the features of CTools that are currently available in the 8.x-3.x version.
Wizard API
Multi-step forms are usually used when we need to take a number of inputs without overwhelming the user. They can also be used to build dynamic form inputs i.e. when the form inputs of one step can be changed on the information provided in the previous step. They also allow better organization of UI and code.
CTools provides two abstract classes – FormWizardBase and EntityFormWizardBase, which can be extended to easily make multi-step forms or wizards. The former one can be used to create custom one-off forms while the latter one to wizardify the add or edit form for Config Entities.
The individual steps of the wizard are nothing but forms. Just like every other form in Drupal, they are classes that inherit from the FormBase class. Hence, we can control the individual steps just by implementing the abstract methods – buildForm, validateForm and submitForm in the individual steps or forms.
The Wizard API ensures that the wizard temporary value of the form_state object is always available. In case of the EntityFormWizardBase, it will contain a key named same as the entity id.
Creating a multi-step form using Wizard API
To create a multi-step form using Wizard API, we need to create a custom module, and then add forms, one form for each step. In this example, we will create a wizard for a custom config entity by extending EntityFormWizardBase. Drupal Console can be used to generate the boilerplate code to save time and effort.
- Navigate to the root directory of your Drupal site
$ cd /var/www/html/drupal8
- Generate module boilerplate using Drupal Console (or create the .info.yml and the .module files manually) and answer a series of questions generated by the module generator.
$ drupal generate:module - Now, generate boilerplate code for custom config entity and answer a series of questions generated by the entity generator.
$ drupal generate:entity:config
- Now create forms to add and edit our custom config entity by extending the FormBase class and implementing getFormId, buildForm and submitForm methods. Optionally, implement the validateForm method to define custom validation. Create one form for each step.
This is where the actual magic happens. The wizard temporary value of the forms _state object is used to temporarily store the intermediate values. In case of custom one-off forms, do not forget to save the field values in the TempStore using the setTemporaryValue method.public function getFormId() { return 'wizard_test_config_entity_first_form'; } public function buildForm(array $form, FormStateInterface $form_state) { $config_entity = $form_state->getTemporaryValue('wizard')['custom_config_entity']; $form['first_value'] = [ '#title' => $this->t('First'), '#description' => $this->t('Enter first value here'), '#type' => 'textfield', '#default_value' => $config_entity->getFirst() ?: '', ]; return $form; } public function submitForm(array &$form, FormStateInterface $form_state) { $config_entity = $form_state->getTemporaryValue('wizard')['custom_config_entity']; $config_entity->set('first_value', $form_state->getValue('first_value')); }
- Create the wizard class by extending the EntityFormWizardBase class and implement the following methods.
public function getWizardLabel() { return $this->t('Custom config entity wizard'); } public function getMachineLabel() { return $this->t('custom_config_entity_wizard'); } public function getEntityType() { return 'wizard_test_config_entity'; } public function exists() { return '\Drupal\wizard_test\Entity\CustomConfigEntity::load'; } public function getOperations($cached_values) { return [ 'label' => [ 'form' => 'Drupal\wizard_test\Form\CustomConfigEntityLabelForm', 'title' => $this->t('Label and ID'), ], 'first' => [ 'form' => 'Drupal\wizard_test\Form\FirstForm', 'title' => $this->t('Step one'), ], 'second' => [ 'form' => 'Drupal\wizard_test\Form\SecondForm', 'title' => $this->t('Step two'), ], ]; }
- Add the wizard annotation in your custom entity class, CustomConfigEntity in this example.
"wizard" = { "add" = "Drupal\wizard_test\Wizard\AddEntityWizard", "edit" = "Drupal\wizard_test\Wizard\EditEntityWizard" } links = { "canonical" = "/admin/structure/custom_config_entity/{custom_config_entity}", "edit-form" = "/admin/structure/custom_config_entity/{machine_name}/{step}", "delete-form" = "/admin/structure/custom_config_entity/{custom_config_entity}/delete", "collection" = "/admin/structure/custom_config_entity" }
- Define the routes for the wizard(s). Remember to declare _entity_wizard and tempstore_id under defaults.
entity.custom_config_entity.add_form: path: '/admin/structure/custom_config_entity/add' defaults: _entity_wizard: 'custom_config_entity.add' _title: 'Add Custom config entity' tempstore_id: 'custom_config_entity.config_entity' options: _admin_route: TRUE entity.custom_config_entity.add_step_form: path: '/admin/structure/custom_config_entity/add/{machine_name}/{step}' defaults: _entity_wizard: 'custom_config_entity.add' _title: 'Add Custom config entity' tempstore_id: 'custom_config_entity.config_entity' options: _admin_route: TRUE entity.custom_config_entity.edit_form: path: '/admin/structure/custom_config_entity/{machine_name}/{step}' defaults: _entity_wizard: 'custom_config_entity.edit' _title: 'Edit Custom config entity' tempstore_id: 'custom_config_entity.config_entity' options: _admin_route: TRUE
- Enable the newly created module.
$ drush en wizard_test -y
or
$ drupal module:install wizard_test
Now visit the route of your wizard to be greeted by the custom multi-step form.
Entity Derivatives
In Drupal 8, Plugin Derivatives can be used to iterate over annotation objects. This allows us to use a foreach loop to iterate over all the entities including blocks, comments, nodes, taxonomies, etc.
CTools provides a helper class EntityDeriverBase which implements this functionality. This can be particularly helpful when going Headless with Drupal. We can create a plugin, use it along with the services API and perform CRUD operations on entities.
Implement the getDerivativeDefinitions() method to return all the entity derivatives and add the reference of the deriver class in the service definition annotation. Refer this repository for implementation details.
CTool Blocks and Views modules
The project also includes a couple of modules which provides improvements to core blocks and views. Note that these are marked as experimental. Before using them, enable these modules using any of the following methods:
Using UI
Navigate to Manage → Extend, select the Chaos tools blocks and views modules and click on “Install”.
Using terminal
$ drush en ctools_block ctools_views -y
or
$ drupal module:install ctools_block ctools_views
Now, to add the blocks provided by the Chaos tools blocks module, follow the below steps:
- Navigate to Manage → Structure → Block Layout and click on “Place Block” against the region you wish to add the block in.
- Select the Entity View block provided by the Chaos tools module.
- Give an appropriate Block title, select view mode and select the visibility conditions just like any other block. Click on “Save Block” to apply changes.
The CTools project comes with many useful tools for developers to simplify workflows and promote code reusability. You may also look at the source code of other modules including Page Manager, Panels, and Pathauto to better understand the use case of features provided by Chaos Tool Suite.
More of these features and tools are expected to make their way into Drupal core in future. A good way to keep track of all the changes in the codebase is to follow the git repository of this branch.
Let us know in the comments below how we can help you or drop a mail at [email protected].
Subscribe
Related Blogs
SDC: Integrating Storybook & Single Directory Component
Today, we will talk about about Drupal Single Directory Components or SDC and Storybook. Single Directory Components in Drupal allows you…
RFP: How To Create An RFP For Open Source Solutions?
A good Request for Proposals (RFP) or Call for Proposals (CFP) clearly states the goals and expectations of your project and sets the…
Drupal 7 End Of Life: Top Reasons You Should Migrate To Drupal 10
Drupal 10 was released in December 2022 and ever since, the community has been pushing its users to do Drupal 7 to 10 migration. As per…