Contributing a custom action
This documentation needs review. See "Help improve this page" in the sidebar.
General
This guide is targeted at developers that are comfortable writing code, so if you want to contribute an action of your own, this is the guide for you! You will get a complete overview of all the tasks that need to be completed in order to successfully create your own action. This guide is based on one of the real actions that was ported.
Overview
- Getting started
- Before you start
- Creating the configuration form
- Making sure the form is rendered
- Adding JavaScript logic
- Testing & development
- Creating an 'issue' and a patch
- Create a documentation page
Getting started
First of all, you should install the latest development version of the Konami Code module by checking out the Drupal.org git repository. The current latest development version is 8.x-1.x.
Navigate with your terminal to your modules/contrib directory and execute the following command.
git clone --branch 8.x-1.x https://git.drupal.org/project/konamicode.git
cd konamicode
git checkout -b my_custom_actionNote that you can not push directly to this repository, instead you should create a patch once you're finished development on your local branch.
Before you start
Before you start working on the actual coding of the module you should ask yourself some questions and note some things down.
- What is a clear and descriptive name for my Action
- Write the name down and create a machine name since this is very important for all the dynamically loaded configurations. E.g. "Image Spam" and "image_spam"
- In general it's easier if you know beforehand what fields you'll need in the configuration form.
- Last but not least, ask yourself the question if anyone else would use the action. If you're not sure about this one it's probably best to first create an issue to request the action and see what others think about your idea.
- Respect all the naming conventions defined in the section below.
Creating the configuration form
All the configuration forms are defined in the src/Form directory. Start by creating a class with the following naming convention KonamicodeActionActionNameConfiguration that extends KonamicodeActionBaseConfiguration. In our Alert action example our name would be KonamicodeActionAlertConfiguration.
namespace Drupal\konamicode\Form;
use Drupal\Core\Form\FormStateInterface;
/**
* Class KonamicodeActionAlertConfiguration.
*/
class KonamicodeActionAlertConfiguration extends KonamicodeActionBaseConfiguration {
...
}
First of, all we need to set the name and machine name of the Action and let the Base configuration know about this by calling the constructor.
static protected $name = 'Alert';
static protected $machineName = 'alert';
/**
* {@inheritdoc}
*/
public function __construct(ConfigFactoryInterface $config_factory) {
parent::__construct($config_factory, self::$name, self::$machineName);
}Next up you'll need to implement the two mandatory functions buildForm and submitForm. The buildForm function is responsible for all the form elements and the submitForm will handle the saving of all the values. You don't need to define the enabled checkbox or keycode sequence field since those are handled by the KonamicodeActionBaseConfiguration class. You should be able to specify any field supported by Drupal Form API. The general structure of the buildForm function is:
- Call the buildForm function from the parent
- Load the konamicode.configuration in order to be able to load the default values
- Add an info field (recommended) with some descriptive text about what the action will do once the Konami Code sequence is entered
- Add all the fields you need to the $form array
- Return the $form array
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Load the base main configuration form.
$form = parent::buildForm($form, $form_state);
// Fetch the config.
$config = $this->config('konamicode.configuration');
$form[parent::getFieldGroupName()][$this->getUniqueFieldName('info')] = [
'#markup' => $this->t('Will open a "classic" alert window with a predefined message when the Konami Code is entered.'),
'#weight' => -10,
];
$action_message = $this->getUniqueFieldName('message');
$form[parent::getFieldGroupName()][$action_message] = [
'#type' => 'textfield',
'#title' => $this->t('Message'),
'#description' => $this->t('The message that needs to be displayed to the user.'),
'#default_value' => $config->get($action_message),
];
return $form;
}Please note the usage of parent::getFieldGroupName() and $this->getUniqueFieldName('field_name'). The getFieldGroupName function will make sure all the fields are listed under the correct tab. This function will return a value like: konamicode_action_alert. The function getUniqueFieldName will make sure there are no conflicting field names between actions. This function will return a value like: konamicode_alert_message.
It's mandatory to add the validateForm() function and call the parent. If you want you can add custom validation hooks in this function.
/**
* {@inheritdoc}
*
* phpcs:disable
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// phpcs:enable
// Make sure to call the parent, otherwise the Key Code Sequence field isn't
// being validated.
parent::validateForm($form, $form_state);
}PHPCS will complain that this is a possibly useless function, but we really need to call the parent, otherwise the Key Code Sequence field isn't validated. Therefore we added the phpcs:disable/enable lines to ignore the function declaration itself. If you added custom logic in this function please remove those two lines since then PHPCS won't be complaining about it.
The submitForm function is quite straight forward and uses some of the same functions to build the form. Basically you just need to fetch generated field name again and then get the value from the $form_state array in order to save the values. The submitForm function for the Alert action example would look like this:
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Fetch the unique field names.
$action_message = $this->getUniqueFieldName('message');
// Save the values.
$this->configFactory->getEditable('konamicode.configuration')
->set($action_message, $form_state->getValue($action_message))
->save();
parent::submitForm($form, $form_state);
}The complete class should look similar to this (example for the Alert action).
<?php
namespace Drupal\konamicode\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Class KonamicodeActionAlertConfiguration.
*/
class KonamicodeActionAlertConfiguration extends KonamicodeActionBaseConfiguration {
static protected $name = 'Alert';
static protected $machineName = 'alert';
/**
* {@inheritdoc}
*/
public function __construct(ConfigFactoryInterface $config_factory) {
parent::__construct($config_factory, self::$name, self::$machineName);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Load the base main configuration form.
$form = parent::buildForm($form, $form_state);
// Fetch the config.
$config = $this->config('konamicode.configuration');
$form[parent::getFieldGroupName()][$this->getUniqueFieldName('info')] = [
'#markup' => $this->t('Will open a "classic" alert window with a predefined message when the Konami Code is entered.'),
'#weight' => -10,
];
$action_message = $this->getUniqueFieldName('message');
$form[parent::getFieldGroupName()][$action_message] = [
'#type' => 'textfield',
'#title' => $this->t('Message'),
'#description' => $this->t('The message that needs to be displayed to the user.'),
'#default_value' => $config->get($action_message),
];
return $form;
}
/**
* {@inheritdoc}
*
* phpcs:disable
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// phpcs:enable
// Make sure to call the parent, otherwise the Key Code Sequence field isn't
// being validated.
parent::validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Fetch the unique field names.
$action_message = $this->getUniqueFieldName('message');
// Save the values.
$this->configFactory->getEditable('konamicode.configuration')
->set($action_message, $form_state->getValue($action_message))
->save();
parent::submitForm($form, $form_state);
}
}
Note that the configuration form won't be shown yet until you updated the following step.
Making sure the form is rendered
To make sure the form is rendered we need to add the class we just created to the getAllActions() function in the Main class. This step can be done as soon as you've created your class and the mandatory functions, this will make the development of the form itself easier.
/**
* Function that will maintain and return all the actions.
*
* @return array
* An array of Action classes that extend the Action Base class.
*/
public function getAllActions() {
return [
new KonamicodeActionRedirectConfiguration($this->configFactory),
new KonamicodeActionAlertConfiguration($this->configFactory),
];
}Adding JavaScript Logic
Since the Key Code detection is based on the JavaScript input detection, you'll need to create a custom JavaScript file to execute your preferred logic. Create a new file in the /js directory with the naming convention: konamicode_action_MACHINE_NAME.js. For the alert action, the filename would be konamicode_action_alert.js. Inside the JavaScript file you'll need to define a callback function like so:
/**
* @file
* JavaScript code for the Alert action.
*/
Drupal.konamicode_action_alert = function() {
'use strict';
...
};
Note that we also use the same naming convention for the function name as for the filename. Also note the 'use strict'; definition on the first line of the function. All the field data will be passed to the callback function by using drupalSettings.
/**
* @file
* JavaScript code for the Alert action.
*/
Drupal.konamicode_action_alert = function() {
'use strict';
let action_data = Drupal.get_action_field_info(drupalSettings.konamicode.actions, 'alert');
if (action_data !== false) {
// Load the message otherwise we use a default value.
let message = action_data.konamicodeAlertMessage || 'Konamicode Is Geek';
alert(message);
}
};
In order to get the specific field data for a given action, you can call the function Drupal.get_action_field_info(drupalSettings.konamicode.actions, 'alert'); where you pass the actions settings and a machine name as parameter. After that you can do whatever you like to do with the data. Field names are dynamically put into the configuration by converting the field machine name to camelCase. So in this example, the field konamicode_alert_message would be passed to the function as konamicodeAlertMessage. The fields from the base configuration are not passed to the drupalSettings in the actions array since there is no use for them in the JavaScript code.
Finally, we need to add the JavaScript file as a library to our modules. This can be done by adding an entry in the konamicode.libraries file located in the module root. Again note the naming convention used to define the key which is the same as the filename. For the alert action the library entry would look like this:
...
konamicode_action_alert:
version: VERSION
js:
js/konamicode_action_alert.js: {}
dependencies:
- core/drupalSettingsTesting & development
General testing & development flow.
- Clear the cache to make sure all the JS libraries are found by the module.
- Check the configuration form to make sure everything you defined is there.
- Enter some configuration, save the form, and enable your action.
- Test the Konami Code on the front-end by entering the defined Key Code Sequence.
Creating an 'issue' and a patch
If the actions are working as expected you can commit the code on your temporary branch. The commit message doesn't really matter since you won't be pushing and the commit will only exist on your local branch. So if you don't feel like typing a commit message it could be git add -A && git commit -m '...' .
Before we create the patch it's time to create the issue in the issue queue so we have an issue id. Make sure to respect the general issue template to describe the action as best as possible.
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
[Describe the action as best as you can here]
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
Add the action to the Konami Code module.
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
Create the action.
<h3 id="summary-ui-changes">User interface changes</h3>
A new action will be available.
<h3 id="summary-api-changes">API changes</h3>
N/A
<h3 id="summary-data-model-changes">Data model changes</h3>
N/AFeel free to add even more details to the template above as you see fit.
<a class="action-button" href="https://www.drupal.org/node/add/project-issue/konamicode?categories=3&am..." rel="nofollow">Submit your idea</a>
After you created the issue you can now create the patch by executing the following command:
git diff 8.x-1.x > ISSUENUMBER-2.patchWhere ISSUENUMBER is the id from the URL when creating the issue, and the number after the dash is the comment number in which you will attach the patch.
Create a documentation page
It would be much appreciated if you would create a documentation page containing images and text description of the action. For example, you can take a look at the existing documentation.
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion