0
votes

I'm trying to implement a hook_form_alter method to modify the behaviour of one field through ajax callback in the form display of node.

The idea is when I select one option from the select list field (field_country) modify the values of other field list (field_laws). Specificly, when I select one country, the hook method pass this value (current) through ajax callback to changeLawsData. This callback get one external service that returns one array of values filtered by the country selected previously.

The issue is inside of callback method, i can't access to $form and $form_state objects that contain the previous hook_form_alter.

My question is: Is posible to pass by arguments this objects to the callback? With this i could handler the state of form and its field, for example.

Something like this:

    $form['field_country']['widget']['#ajax'] = array(
        'callback' => [$this,'changeLawsData'],
        'event' => 'change',
        'disable-refocus' => FALSE,
        **'arguments' = array($form, $form_state)**
      );

Here is the full code of this implementation.

<?php

namespace Drupal\obs_urban_system\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\hook_event_dispatcher\HookEventDispatcherInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
/**
 * Our event subscriber class.
 */
class NodeUrbanSystemFormAlterEventSubscriber implements EventSubscriberInterface {
    public static function getSubscribedEvents() {
        return [
            HookEventDispatcherInterface::FORM_ALTER => 'hookFormAlter'
        ];
    }

    /**
     * Implements hook_form_alter
     */
    public function hookFormAlter($event) {

        if($event->getFormId() == 'node_urban_system_edit_form') {
            $form = $event->getForm();
            $country = $form['field_country']['widget']['#default_value'];
            $form['field_laws']['widget'][0]['value']['#options'] = \Drupal::service('custom_services.law')->getLawsByContent($country, 'country');
            $form['field_law_articles']['widget'][0]['value']['#options'] = \Drupal::service('custom_services.law')->getLawArticlesByCountry($country);
            $form['field_country']['widget']['#ajax'] = array(
                'callback' => [$this,'changeLawsData'],
                'event' => 'change',
                'disable-refocus' => FALSE
              );
            $event->setForm($form);
        }
    }

    /**
     * @param $form
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     * @return \Drupal\Core\Ajax\AjaxResponse
     */
    function changeLawsData(&$form, FormStateInterface $form_state) {
<!--- HERE IM USING THE $form object --->
        $country = $form['field_country']['widget']['#default_value'];
<!---                                --->
        $laws = \Drupal::service('custom_services.law')->getLawsByContent($country, 'country');

        foreach ($laws as $key => $value) {
            $option .= "<option value='" . $key . "'>" . $value . " </option>";
        }

        $response = new AjaxResponse();
        $response->addCommand(new HtmlCommand('#edit-field-laws-0-value', $option));
        return $response;
    }

}

Thanks to all very much.

1
TLDR: Basically, all the form manipulation should happen in the form_alter function. I have never seen a form_alter implemented in an OO way as you have... but generally, when wanting to add items to a form depending on submitted ajax values, you would do it all in the form_alter. When ajax is submitted, the form is rebuilt, so in the form_alter function, you would check the submitted value and alter the form as needed. Your ajax response should just return this new form item (or command in your case).2pha
Also, why are you implementing the way you have via an event subscriber? A simple hook_form_alter function like in D7 still works in D8. EDIT... ahh, I see you are using the Hook Event Dispatcher module.2pha

1 Answers

0
votes

You need to do all the form manipulation within the form_alter.
When the ajax callback is fired, the form will be rebuilt and the current values of the form will be available in the form_state.
Your ajax callback should only return what is needed on the front end, it should not actually manipulate the form array.

Here is an example with your code (example only, untested)

<?php

namespace Drupal\obs_urban_system\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\hook_event_dispatcher\HookEventDispatcherInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
/**
 * Our event subscriber class.
 */
class NodeUrbanSystemFormAlterEventSubscriber implements EventSubscriberInterface {
    public static function getSubscribedEvents() {
        return [
            HookEventDispatcherInterface::FORM_ALTER => 'hookFormAlter'
        ];
    }

    /**
     * Implements hook_form_alter
     */
    public function hookFormAlter($event) {

        if($event->getFormId() == 'node_urban_system_edit_form') {
            $form = $event->getForm();
            $country = $form['field_country']['widget']['#default_value'];

            // Get the form state object.
            $form_state = $event->getFormState();
            // Here we should check if a country has been selected.
            $country = $form_state->getValue('country');
            if ($country) {
              // populate the options from service here.
              $form['field_laws']['widget']['#options'] = \Drupal::service('custom_services.law')->getLawsByContent($country, 'country');
            } else {
              // Populate with default options.
              $form['field_laws']['widget']['#options'] = [];
            }


            $form['field_law_articles']['widget'][0]['value']['#options'] = \Drupal::service('custom_services.law')->getLawArticlesByCountry($country);
            $form['field_country']['widget']['#ajax'] = array(
                'callback' => [$this,'changeLawsData'],
                'event' => 'change',
                'disable-refocus' => FALSE
              );
            $event->setForm($form);
        }
    }

    /**
     * @param $form
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     * @return \Drupal\Core\Ajax\AjaxResponse
     */
    function changeLawsData(&$form, FormStateInterface $form_state) {
        $response = new AjaxResponse();
        $response->addCommand(new HtmlCommand('#edit-field-laws', $form['field_laws']));
        return $response;
    }

}

Please remember that the above is an example...