Created
December 12, 2019 22:31
-
-
Save mattschaff/b6894d12be56926084a3773618f1a133 to your computer and use it in GitHub Desktop.
Drupal 8: CRUD admin form (AJAX add, delete)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
good example. see this article and sample code