Created
May 4, 2021 11:53
-
-
Save ao5357/16b746ffedadfc46482e0e5c6b9c5c21 to your computer and use it in GitHub Desktop.
Stash a complex AJAX form
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\nypl_ip_checker\Form; | |
| /** | |
| * @file | |
| * Contains \Drupal\contact_info\Form\NyplIpCheckerAdminForm. | |
| */ | |
| use Drupal\Core\Config\ConfigFactoryInterface; | |
| use Drupal\Core\Form\ConfigFormBase; | |
| use Drupal\Core\Form\FormStateInterface; | |
| use Drupal\Core\Render\Element; | |
| /** | |
| * Class AdminForm. | |
| */ | |
| class NyplIpCheckerAdminForm extends ConfigFormBase { | |
| /** | |
| * Constructs a new NyplIpCheckerAdminForm. | |
| * | |
| * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | |
| * The factory for configuration objects. | |
| */ | |
| public function __construct(ConfigFactoryInterface $config_factory) { | |
| parent::__construct($config_factory); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| protected function getEditableConfigNames(): array { | |
| return [ | |
| 'nypl_ip_checker.nypl_ip_checker_admin_form', | |
| ]; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getFormId(): string { | |
| return 'nypl_ip_checker_admin_form'; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function buildForm(array $form, FormStateInterface $form_state): array { | |
| // Get the config this form saves to, for default_values. | |
| $config = $this->config('nypl_ip_checker.settings'); | |
| // Determine if an AJAX callback is rebuilding the form. | |
| $addElement = $form_state->get('addElement'); | |
| $removeElement = $form_state->get('removeElement'); | |
| // Make that form a tree. | |
| $form['#tree'] = TRUE; | |
| $form_state->setCached(FALSE); | |
| // Form description. | |
| $form['description'] = [ | |
| '#type' => 'item', | |
| '#markup' => $this->t('This form stores the RegEx patterns associated with various NYPL locations.'), | |
| ]; | |
| // Fieldset with one or more fields holding onsite RegEx patterns. | |
| $form['onsite'] = [ | |
| '#type' => 'fieldset', | |
| '#title' => $this->t('On-site patterns'), | |
| '#description' => $this->t('Broad RegEx patterns indicating whether an IP is within or outside NYPL property.'), | |
| ]; | |
| // Fill in onsite pattern field if there is no existing value. | |
| $form['onsite']['pattern'][0] = [ | |
| '#type' => 'textfield', | |
| '#title' => $this->t('RegEx pattern'), | |
| '#default_value' => '', | |
| ]; | |
| // Fill in saved config values, if present. | |
| $onsite_patterns = $config->get('onsite.pattern'); | |
| if (isset($onsite_patterns) && count($onsite_patterns)) { | |
| $form['onsite']['pattern'] = []; | |
| foreach ($onsite_patterns as $i => $val) { | |
| $form['onsite']['pattern'][$i] = [ | |
| '#type' => 'textfield', | |
| '#title' => $this->t('RegEx pattern'), | |
| '#default_value' => $val, | |
| ]; | |
| } | |
| } | |
| // Check if the AJAX triggered an add. | |
| if (isset($addElement) && $addElement === ['onsite', 'pattern']) { | |
| $form['onsite']['pattern'][] = [ | |
| '#type' => 'textfield', | |
| '#title' => $this->t('RegEx pattern'), | |
| '#default_value' => '', | |
| ]; | |
| } | |
| // Check if the AJAX triggered a remove. | |
| if (isset($removeElement) && $removeElement === ['onsite', 'pattern']) { | |
| array_pop($form['onsite']['pattern']); | |
| } | |
| $form['onsite'] = $this->appendAddRemoveActions($form['onsite']); | |
| // Fieldset of fieldsets holding match patterns for specific locations. | |
| $form['mappings'] = [ | |
| '#type' => 'fieldset', | |
| '#title' => $this->t('Location patterns'), | |
| ]; | |
| // Create one blank fieldset if no saved values present. | |
| $blankMapping = [ | |
| '#type' => 'fieldset', | |
| '#title' => $this->t('Location'), | |
| 'pattern' => [ | |
| 0 => [ | |
| '#type' => 'textfield', | |
| '#title' => $this->t('RegEx pattern'), | |
| '#default_value' => '', | |
| ], | |
| ], | |
| ]; | |
| // Include a zeroth element in order to make AJAX play nice. | |
| $form['mappings']['location'][0] = [ | |
| '#type' => 'item', | |
| '#markup' => $this->t('Each location has a require node reference, plus one or more patterns.'), | |
| ]; | |
| $form['mappings']['location'][] = [ | |
| '#type' => 'item', | |
| '#markup' => $this->t('Neither zero nor 1 are good.'), | |
| ]; | |
| $form['mappings']['location'][] = $blankMapping; | |
| $form['mappings']['location'][] = $blankMapping; | |
| // Fill in saved config values, if present. | |
| $mappings = $config->get('mappings'); | |
| if (isset($mappings) && count($mappings)) { | |
| $form['mappings']['location'] = []; | |
| foreach ($mappings as $i => $val) { | |
| $form['mappings']['location'][$i] = [ | |
| '#type' => 'fieldset', | |
| '#title' => $this->t('Location'), | |
| 'pattern' => [ | |
| 0 => [ | |
| '#type' => 'textfield', | |
| '#title' => $this->t('RegEx pattern'), | |
| '#default_value' => $val[0]['pattern'], | |
| ], | |
| ], | |
| ]; | |
| } | |
| } | |
| // Check if the AJAX triggered an add for a whole mapping. | |
| if (isset($addElement) && count($addElement) === 2 | |
| && $addElement === ['mappings', 'location'] | |
| ) { | |
| $form['mappings']['location'][] = $blankMapping; | |
| } | |
| // Check if the AJAX triggered a remove for a whole mapping. | |
| if (isset($removeElement) && count($addElement) === 2 | |
| && $removeElement === ['mappings', 'location'] | |
| ) { | |
| array_pop($form['mappings']['location']); | |
| } | |
| //$form['mappings'] = $this->appendAddRemoveActions($form['mappings']); | |
| // Check if the AJAX triggered add/remove on patterns within a location. | |
| if (isset($addElement) && count($addElement) === 4 | |
| && in_array('mappings', $addElement) | |
| && in_array('location', $addElement) | |
| ) { | |
| $form['mappings']['location'][$addElement[2] - 1]['pattern'][] = [ | |
| '#type' => 'textfield', | |
| '#title' => $this->t('RegEx pattern'), | |
| '#default_value' => '', | |
| ]; | |
| } | |
| if (isset($removeElement) && count($removeElement) === 4 | |
| && in_array('mappings', $removeElement) | |
| && in_array('location', $removeElement) | |
| ) { | |
| array_pop($form['mappings']['location'][$addElement[2] - 1]['pattern']); | |
| } | |
| // Add in actions for all the mappings/locations after the parent. | |
| foreach ($form['mappings']['location'] as $i => $set) { | |
| if ($i >= 1) { | |
| $form['mappings']['location'][$i] = $this->appendAddRemoveActions($form['mappings']['location'][$i]); | |
| } | |
| } | |
| return parent::buildForm($form, $form_state); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function submitForm(array &$form, FormStateInterface $form_state) { | |
| $values = $form_state->getValues(); | |
| $config = $this->configFactory->getEditable('nypl_ip_checker.settings'); | |
| $config->set('onsite.pattern', $values['onsite']['pattern']); | |
| $config->save(); | |
| parent::submitForm($form, $form_state); | |
| } | |
| /** | |
| * Add the add/remove buttons to elements that take such AJAX toggles. | |
| * | |
| * @param array $element | |
| * A form element with its constituent parts. | |
| * | |
| * @return array | |
| * The element with the buttons appended. | |
| */ | |
| public function appendAddRemoveActions(array $element): array { | |
| $element['actions'] = [ | |
| '#type' => 'actions', | |
| 'add' => [ | |
| '#type' => 'submit', | |
| '#value' => $this->t('Add'), | |
| '#submit' => ['::addCallback'], | |
| '#ajax' => [ | |
| 'callback' => '::addRemoveAjaxCallback', | |
| 'wrapper' => 'nypl-ip-checker-admin-form', | |
| ], | |
| ], | |
| 'remove' => [ | |
| '#type' => 'submit', | |
| '#value' => $this->t('Remove'), | |
| '#submit' => ['::removeCallback'], | |
| '#ajax' => [ | |
| 'callback' => '::addRemoveAjaxCallback', | |
| 'wrapper' => 'nypl-ip-checker-admin-form', | |
| ], | |
| ], | |
| ]; | |
| return $element; | |
| } | |
| /** | |
| * Callback for both ajax-enabled buttons. | |
| * | |
| * Selects and returns the fieldset with the names in it. | |
| */ | |
| public function addRemoveAjaxCallback(array &$form, FormStateInterface $form_state) { | |
| return $form; | |
| } | |
| /** | |
| * AJAX callback for adding an element within the form. | |
| * | |
| * @param array $form | |
| * The form. | |
| * @param \Drupal\Core\Form\FormStateInterface $form_state | |
| * The form's state, as defined in Drupal core. | |
| */ | |
| public function addCallback(array &$form, FormStateInterface $form_state) { | |
| // Get the array keys of the triggering element within $form. | |
| $trigger = $form_state->getTriggeringElement()['#parents']; | |
| // Add/remove actions add two levels, which we remove here. | |
| $parent = array_splice($trigger, 0, -2); | |
| // Use the helper method to get the parent element in the form. | |
| $parentElement = $this->addressFormFromArray($form, $parent); | |
| // Get the non-hashtagged array elements of the parent. | |
| $elements = Element::children($parentElement); | |
| // Assuming the actions are last, get the element key for the field to add. | |
| $addElement = $elements[count($elements) - 2]; | |
| // Append the add element to the overall form keys. | |
| $parent[] = $addElement; | |
| // Pass the information needed to add a row via $form_state. | |
| $form_state->set('addElement', $parent); | |
| $form_state->setRebuild(); | |
| } | |
| /** | |
| * Submit handler for the "remove one" button. | |
| * | |
| * Decrements the max counter and causes a form rebuild. | |
| */ | |
| public function removeCallback(array &$form, FormStateInterface $form_state) { | |
| // Get the array keys of the triggering element within $form. | |
| $trigger = $form_state->getTriggeringElement()['#parents']; | |
| // Add/remove actions add two levels, which we remove here. | |
| $parent = array_splice($trigger, 0, -2); | |
| // Use the helper method to get the parent element in the form. | |
| $parentElement = $this->addressFormFromArray($form, $parent); | |
| // Get the non-hashtagged array elements of the parent. | |
| $elements = Element::children($parentElement); | |
| // Assuming the actions are last, get the element key for the field to add. | |
| $addElement = $elements[count($elements) - 2]; | |
| // Append the add element to the overall form keys. | |
| $parent[] = $addElement; | |
| // Pass the information needed to add a row via $form_state. | |
| $form_state->set('removeElement', $parent); | |
| $form_state->setRebuild(); | |
| } | |
| /** | |
| * Helper to get a form element from an array of keys. | |
| * | |
| * @param array $form | |
| * The form. | |
| * @param array $addresses | |
| * Array keys for an element in the form of count($addresses) depth. | |
| * | |
| * @return array | |
| * The $form element referenced from the addresses. | |
| */ | |
| public function addressFormFromArray(array $form, array $addresses): array { | |
| $addressed = $form; | |
| foreach ($addresses as $i => $key) { | |
| $addressed = $addressed[$key]; | |
| } | |
| return $addressed; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment