Skip to content

Instantly share code, notes, and snippets.

@mattschaff
Created December 12, 2019 22:31
Show Gist options
  • Save mattschaff/b6894d12be56926084a3773618f1a133 to your computer and use it in GitHub Desktop.
Save mattschaff/b6894d12be56926084a3773618f1a133 to your computer and use it in GitHub Desktop.
Drupal 8: CRUD admin form (AJAX add, delete)
<?php
namespace Drupal\example\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class ExampleCrudAdminForm.
*/
class ExampleCrudAdminForm extends ConfigFormBase {
/**
* Property: Number of items in form
*
* @var integer
*/
protected $itemTotal = 1;
/**
* Temporary config, to be used by the Remove button.
*
* @var array
*/
protected $tempConfig = [];
/**
* Item id to remove.
*
* @var integer
*/
protected $itemToRemove;
/**
* Form state
*
* @var FormStateInterface
*/
protected $formState = NULL;
/**
* Request stack
*
* @var RequestStack
*/
protected $request;
/**
* Class constructor.
*/
public function __construct(ConfigFactoryInterface $config_factory, RequestStack $RequestStack) {
parent::__construct($config_factory);
$this->tempConfig = $this->config('example.config')->get('items');
$this->request = $RequestStack;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('request_stack')
);
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'example.config',
];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'example_config_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
if (is_null($this->formState)) {
$this->formState = $form_state;
}
// Get config.
$items = $this->tempConfig;
// Make sure to use tree.
$form['#tree'] = TRUE;
// Disable caching on this form.
$form_state->setCached(FALSE);
// Get number of items already loaded into config.
if (!empty($items) && !$form_state->get('ajax_pressed')) {
$this->itemTotal = count($items) > 0 ? count($items) : 1;
}
// Build items container.
$form['items'] = [
'#type' => 'table',
'#header' => [
$this->t('Text'),
$this->t('Select'),
'',
],
'#empty' => $this->t('No privileges.'),
'#tableselect' => FALSE,
'#attributes' => ['id' => 'items-container'],
];
for ($i = 0; $i < $this->itemTotal; $i++) {
$form['items'][$i] = [
'#type' => 'fieldset',
];
// Path.
$form['items'][$i]['text'] = [
'#type' => 'textfield',
'#attributes' => ['placeholder' => $this->t('Path to rewrite')],
'#size' => 50,
'#required' => TRUE,
'#default_value' => empty($items[$i]['text']) ? '' : $items[$i]['text'],
];
// View.
$form['items'][$i]['select'] = [
'#type' => 'select',
'#options' => [
'First',
'Second',
'Third',
],
'#default_value' => empty($items[$i]['select']) ? null : $items[$i]['select'],
'#required' => TRUE,
];
// Remove button.
$form['items'][$i]['remove_item_' . $i] =[
'#type' => 'submit',
'#name' => 'remove_' . $i,
'#value' => $this->t('Remove'),
'#submit' => ['::removeItem'],
// Since we are removing an item, don't validate until later.
'#limit_validation_errors' => [],
'#ajax' => [
'callback' => [$this, 'ajaxCallback'],
'wrapper' => 'items-container',
],
];
}
// Add item button.
$form['items']['actions'] = [
'#type' => 'actions',
'add_item' => [
'#type' => 'submit',
'#value' => $this->t('Add a new item'),
'#submit' => ['::addItem'],
'#ajax' => [
'callback' => [$this, 'ajaxCallback'],
'wrapper' => 'items-container',
],
]
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* Implements callback for Ajax event
*
* @param array $form
* From render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Current state of form.
*
* @return array
* Container section of the form.
*/
public function ajaxCallback($form, $form_state) {
// Set new values if remove was pressed.
if ($this->getCurrentRequestVariable('remove_pressed')) {
// Get input values;
$values = $form_state->getUserInput();
// Remove the removed item;
unset($values['items'][$this->itemToRemove]);
$values['items'] = array_combine(range(0, count($values['items']) - 1), array_values($values['items']));
// Set new values;
for ($i = 0; $i < $this->itemTotal; $i++) {
$form['items'][$i]['text']['#value'] = empty($values['items'][$i]['text']) ? '' : $values['items'][$i]['text'];
$form['items'][$i][]['#value'] = empty($values['items'][$i]['select']) ? '' : $values['items'][$i]['select'];
}
}
// This is Request-specific variable necessary because a 'removePressed'
// class property would persist between requests because of form caching.
$this->setCurrentRequestVariable('remove_pressed', FALSE);
return $form['items'];
}
/**
* Adds an item to form
*
* @param array $form
* @param FormStateInterface $form_state
*/
public function addItem(array &$form, FormStateInterface $form_state) {
$form_state->set('ajax_pressed', TRUE);
$this->itemTotal++;
$form_state->setRebuild();
}
/**
* Removes an item from form
*
* @param array $form
* @param FormStateInterface $form_state
*/
public function removeItem(array &$form, FormStateInterface $form_state) {
$form_state->set('ajax_pressed', TRUE);
// This is Request-specific variable necessary because a 'removePressed'
// class property would persist between requests because of form caching.
$this->setCurrentRequestVariable('remove_pressed', TRUE);
$this->itemTotal--;
// Get triggering item id;
$triggering_element = $form_state->getTriggeringElement();
preg_match_all('!\d+!', $triggering_element['#name'], $matches);
$item_id = (int) $matches[0][0];
$this->itemToRemove = $item_id;
// Remove item from config, reindex at 1, and set tempConfig to it.
unset($this->tempConfig[$item_id]);
$this->tempConfig = array_combine(range(0, count($this->tempConfig) - 1), array_values($this->tempConfig));
// Rebuild form;
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
// add to config
$items_values = $form_state->getValue('items');
unset($items_values['actions']);
foreach ($items_values as $key => &$value) {
unset($value['remove_item_' . $key]);
}
$this->config('example.config')
->set('items', $items_values)
->save();
}
/**
* Set volatile variable, specific to current request time
*
* @param string $name
* @param mixed $value
*/
protected function setCurrentRequestVariable($name, $value) {
$vars_identifier = sha1($this->request->getCurrentRequest()->server->get('REQUEST_TIME'));
$vars = $this->formState->get($vars_identifier) ? $this->formState->get($vars_identifier) : [];
$vars[$name] = $value;
$this->formState->set($vars_identifier, $vars);
}
/**
* Get volatile variable, specific to current request time
*
* @param mixed|null $name
*/
protected function getCurrentRequestVariable($name) {
$vars_identifier = sha1($this->request->getCurrentRequest()->server->get('REQUEST_TIME'));
if (($vars = $this->formState->get($vars_identifier)) && isset($vars[$name])) {
return $vars[$name];
}
return NULL;
}
}
@Nadeemp
Copy link

Nadeemp commented May 18, 2021

good example. see this article and sample code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment