Hello. 

How I may change drupal field state with ajax request, 

Drupal 9 ajax field set and unset with ajax. 

When user change o fill an field other field need to unset o set, not simply hide. 

Thanks for help. 

Comments

wombatbuddy’s picture

ady1503’s picture

Thank you very much for such a prompt response. I have read all this information. When the form is loaded the field is invisible but when I act on the key field the field does not appear. Ajax is not executed. For example: a form with several fields when loading the form, the slave field is disabled and when changing or writing in a specific master field the slave field must be enabled by ajax.

ady1503’s picture

What I can't get is:

     if (empty($form['field_list1result22']['widget']['#default_value']))
     {
       unset($form['result_field']['widget']['#default_value']);
       $form['field_result']['#access'] = FALSE;

     } else{
       unset($form['result_field']['#disabled']);
       $form['field_result']['widget'][0]['value']['#required'] = TRUE;
       $form['field_result']['#access'] = TRUE;
     }

The first "IF" was executed when the form was loaded, when "field_list1result22" is empty by default. But when the user fills the "field_list1result22" the "else" has to be executed, I can't do this with ajax.

The problem is to rebuild the form with field "field_result" so that it is to be visible.

I don't want to just hide or unhide the field "field_result". I need it disabled and enabled.

Thanks for the help.

Jaypan’s picture

First, please use code tags. It's the button in the editor that looks like a piece of paper with is mark in it: <>

You'll need to show more code to help. Is that code in an ajax callback? What does your ajax callback look like? What does the #ajax declaration look like?

ady1503’s picture

/*
 * Implements hook_form_alter().
 */

function buangh_test_form_alter(&$form, FormStateInterface $form_state, $form_id) {
 if (isset($form["field_lista1resultado22"])) {
    $form['field_lista1resultado22']['widget'][0]['value']['#ajax'] = [
      'callback' => 'buangh_test_ajax_type_callback',
      'event' => 'focusout',
      'disable-refocus' => TRUE,
      'wrapper' => 'version-release',
      'progress' => [
        'type' => 'throbber',
        'message' => t('...'),
      ],
      //'#validated' => TRUE,
    ];

    $form['field_resultado']['#prefix'] = '<div id="version-release">';
    $form['field_resultado']['#suffix'] = '</div>';

    //$form['elements']['field_resultado']['#attributes'] = ['id' => 'version-release'];

    if (empty($form['field_lista1resultado22']['widget']['#default_value']))
    {
      unset($form['field_resultado']['widget']['#default_value']);
      $form['field_resultado']['#access'] = FALSE;
    }
    else
    {
      unset($form['field_resultado']['#disabled']);
      $form['field_resultado']['widget'][0]['value']['#required'] = TRUE;
      $form['field_resultado']['#access'] = TRUE;
    }

  }
}
/*
* ajax
*/
function buangh_test_ajax_type_callback (&$form, FormStateInterface $form_state): AjaxResponse {

  return $form['field_resultado'];

  // so it doesn't return the entire form.
  //return $elements['field_resultado'];

}

Thanks.

wombatbuddy’s picture

@ady1503 what is the field type of the master field ("field_list1result22") and the "field_resultado"?

Also, if your callback returns render array, then you don't need to specify that it returns AjaxResponse. But you can create a solution using the AjaxRespone too, see AJAX Forms.

ady1503’s picture

master field ("field_list1result22") when changing state the other field need access to true. 

wombatbuddy’s picture

Please share what types of fields do you use. 

ady1503’s picture

First thanks. 

The fields is text, simply text fields. 

When Master field is filled the slave field must been editable and accesible. 

If máster field is emty the slave field is access false, not editable. 

Gracias. 

wombatbuddy’s picture

Do you want to set/unset the "disabled" attribute of the element or to exclude/include the element from the form?

ady1503’s picture

Yes.

The slave field not appear when ajax callback is executed. 

Not appear for input and save. 

wombatbuddy’s picture

...other field need to unset o set, not simply hide. 

please explain in more details what do you mean (what is the use case)?

ady1503’s picture

Simply.

When the form is loaded a first time the slave field is unset "disabled" by if conditional.

Then if user fill o focuson the Master field, the slave field must appear for edit in ajax mode, without reload form.

Gracias 

ady1503’s picture

I sorry, I not read carefully. 

I need exclude/include the element from the form with ajax response. 

Thanks. 

wombatbuddy’s picture

Updated 

The screenshot of the example:
https://www.drupal.org/files/add-or-remove-dependent-field-with-ajax-1.png

The code: 

use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Html;

/**
 * Implements hook_form_BASE_FORM_ID_alter() for node_form.
 */
function my_module_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  $form['#id'] = Html::cleanCssIdentifier($form_id);

  $form['field_master']['widget'][0]['value']['#ajax'] = [
    'callback' => 'my_module_ajax_callback',
    'event' => 'input',
    'wrapper' => $form['#id'],
  ];

  if (empty($form_state->getValue('field_master'))) {
    // It can be the the "Edit" form and we need to check the stored value.
    $value = $form['field_master']['widget'][0]['value']['#default_value'];
  }
  else {
    $value = $form_state->getValue('field_master')[0]['value'];
  }
  
  // Here we can add the additional validation of the entered value. 
  // For example, let's check that the entered value has at least 3 letters.
  if (mb_strlen($value, 'utf8') < 3) {
    $form['field_slave']['#access'] = FALSE;
    $form['field_slave']['widget'][0]['value']['#default_value'] = NULL;
  }
}

/**
 * Custom Ajax callback.
 */
function my_module_ajax_callback($form, FormStateInterface $form_state) {
  return $form;
}
ady1503’s picture

I just tried it. It works perfectly.

Thanks a lot.

ady1503’s picture

The usefulness of this functionality is if you have no more fields in the form. If you have more fields it is not useful and you cannot use it. Because each time a new form ID is generated. I don't know if it is possible to separate, or not to return the complete form, if it is possible only this specific ajax response to return it. So that the other fields are not influenced.

Is possible change this functionality "Html::cleanCssIdentifier" by other? 

wombatbuddy’s picture

If you have more fields it is not useful and you cannot use it.

Why I can't use it for more fields? More info is needed. 

ady1503’s picture

Hello. After researching all weekend, I came to the conclusion, when the result is returned in form. The Form API adds a new ID to each field in the form. With this issue the other fields do not perform their function.

As an example also investigate the ´´examples´´ modules. If you go to see the execution query you will see that when the form is loaded the first time, the fields are of type: ¨<input readonly="" disabled="disabled" tabindex="-1" data-drupal-selector="edit -result-field-0-value" type="number" id="edit-result-field-0-value" name="result_field[0][value]" value="" step="1" placeholder=" " class="form-number form-element form-element--type-number form-element--api-number">¨. Which is in without any ID.

Now I execute by IF condition, that the field is activated and becomes required, and we have: ¨<input readonly="" data-drupal-selector="edit-field-result-0-value" type="number" id ="edit-field-result-0-value--Otl6DXAuLos" name="field_result[0][value]" value="" step="1" placeholder="" class="form-number required form-element form -element--type-number form-element--api-number" required="required" aria-required="true">¨.

You see that the ID of the field has changed to ¨ id="edit-field-result-0-value--Otl6DXAuLos"¨. Added this alphanumeric code ¨Otl6DXAuLos¨.

The problem is that these alphanumeric codes are added to all the fields of the form, for example: ¨<input class="js-text-full text-full form-text form-element form-element--type-text form- element--api-textfield" data-drupal-selector="edit-field-list1result22-0-value" type="text" id="edit-field-list1result22-0-value--inIoEQbKPLA" name="field_list1result22[ 0][value]" value="ss" size="60" maxlength="255" placeholder="" data-once="drupal-ajax">¨. Here you go: ¨inIoEQbKPLA¨

With this issue, all the functionality of the form stops working, for example: dependent fields, sum or arithmetic fields, reference fields, etc.

And most importantly the other AJAX responses stop working.

So I came to a conclusion:

1. You can't return the entire form, it has to be a pure ajax response.

2. All field changes that consist of ¨unset and disable fields¨ are very problematic to act on, always the form is reloaded, and the IDs change.

I would like to see a concrete example that works with ¨unset disable hide fields¨, pure AJAX. Not return the form, but an ajax response with any type of conditionality: checkbox, radiobox, input, conditional if.

Or how it consisted, the question of this post.

How to act on a field with the states ¨unset disable hide fields¨, with an IF conditionality, of any type.

But the answer be pure AJAX.

And it returns alone, this field. It acts only on this field and not the whole form.

Thanks.

Jaypan’s picture

1. You can't return the entire form, it has to be a pure ajax response.

You can. It just takes a bit of trickery. Below is a scaled down example that explains how to do it. The main point to note is that #ajax['wrapper'] is not set here in the form definition. It will be set in the next step.

class ExampleAjaxForm extends FormBase {

  public function getFormId() {
    // This will be relevant in hook_form_FORM_ID_alter().
    return 'example_ajax_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    // Only the submit button is shown for this example. 
    ...

    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
      '#ajax' => [
        // Note - wrapper is NOT set. It will be set later
        // in hook_form_FORM_ID_alter().
        'callback' => '::ajaxSubmit',
      ],
    ];

    return $form;
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Required for ajax forms. It tells the form to rebuild.
    $form_state->setRebuild(TRUE);
  }

  public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
    // Return the entire form array.
    return $form;
  }

}

At this point, the #ajax will not work yet, as the ajax wrapper has not been set. The next thing to do is implement  hook_form_FORM_ID_alter(), where the form wrapper is created, and #ajax['wrapper'] is set.

function hook_form_example_ajax_form_alter(array &$form, FormStateInterface $form_state) {
  // Get the time of the current request.
  $request_time = \Drupal::service('datetime.time')->getRequestTime();
  // Use the request time to build a unique (to the current user) wrapper ID.
  $wrapper_id = 'example_ajax_form_wrapper-' . $request_time;
  // Use the wrapper ID to add a wrapper around the entire form. This will be used as
  // the #ajax wrapper. Note that other modules may have used alter functions to add
  // their own wrapper to the form, so the div is added around any potential existing
  // wrapper.
  $form['#prefix'] = '<div id="' . $wrapper_id . '">' . ($form['#prefix'] ?? '');
  $form['#suffix'] = ($form['#suffix'] ?? '') . '</div>';

  // Finally, set the value of the #ajax wrapper to the wrapper ID that is no wrapping
  // the entire form. Now the entire form can be returned from the ajax callback.
  $form['actions']['submit']['#ajax']['wrapper'] = $wrapper_id;
}

I think it will work at this point, but it's been a few years since I did it. It's possible you may need to do this (see the additional lines at the bottom):

function hook_form_example_ajax_form_alter(array &$form, FormStateInterface $form_state) {
  // Get the time of the current request.
  $request_time = \Drupal::service('datetime.time')->getRequestTime();
  // Use the request time to build a unique (to the current user) wrapper ID.
  $wrapper_id = 'example_ajax_form_wrapper-' . $request_time;
  // Use the wrapper ID to add a wrapper around the entire form. This will be used as
  // the #ajax wrapper. Note that other modules may have used alter functions to add
  // their own wrapper to the form, so the div is added around any potential existing
  // wrapper.
  $form['#prefix'] = '<div id="' . $wrapper_id . '">' . ($form['#prefix'] ?? '');
  $form['#suffix'] = ($form['#suffix'] ?? '') . '</div>';

  // Finally, set the value of the #ajax wrapper to the wrapper ID that is no wrapping
  // the entire form. Now the entire form can be returned from the ajax callback.
  $form['actions']['submit']['#ajax']['wrapper'] = $wrapper_id;

  // You may need to clone the submit button as well, giving it a unique key.
  // First, copy the submit button to a new element.
  $form['actions']['submit-' . $request_time] = $form['actions']['submit'];
  // Set the original submit button to not be displayed.
  $form['actions']['submit']['#access'] = FALSE;
}
ady1503’s picture

Thanks for the help.

From what you said, I understand that have to add in the custom module, the creation of a custom form and:

1. Create an individual ID wrapper for the form.
2. Always return the form name "'example_ajax_form'".
3. Rebuild the form as required.
4. Prefix and suffix is unique too.

The concept is clear, but not very clear is how to use it?

For example with the model with fields that the "wombatbuddy" exposed.

You expose with the submit button of the form submission.

What is the utility with the fields?

Thanks.

Jaypan’s picture

Nothing specific to the fields. You just return the entire form.

ady1503’s picture

Thanks for all. 

I found the function what generate this:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Util...

I was right, when an Ajax response is executed, the id of the field changes.

How to act without so much code, or override this function, in form_mode_alter.

It will be easier and more correct without going through so much code.

wombatbuddy’s picture

The Form API adds a new ID to each field in the form. With this issue the other fields do not perform their function.

@ady1503  could you please, provide steps to I can reproduce the issue you mean?  

ady1503’s picture

Sorry for not much attention on my part.

The answer was Jaypan, here:

https://drupal.stackexchange.com/questions/281584/ajax-callback-inside-h....

Never have to mix ajax responses, in the same field.

My mistake is mixing ajax, Traditional AJAX, and the Drupal Form API #ajax api responses.

Everything now works.

Thank you very much.

And I'm sorry, for inattention when reading.

La mejor explicacion sobra ajax y drupal, the best:

There are two types of ajax in Drupal. Traditional AJAX, and the Drupal Form API #ajax api. You are mixing the two, by using #ajax in your form, but returning and AJAX response, rather than an #ajax callback response.

To understand #ajax, you need to understand the process by which the Form API works. This is how a standard non-Ajax Form API form works in Drupal:

  1. Some function is called which builds a render array representing the form.
  2. Form alter hooks are called, allowing other modules/theme to alter the form.
  3. Drupal caches the render array in the database, to be used for comparison on submission, as a security measure to ensure that the submitted data matches the type of data expected to be submitted.
  4. The form is rendered into HTML and sent to the browser.
  5. The user submits the form.
  6. Drupal retrieves the form from the database, confirms that the submitted data is allowed, and sanitizes it.
  7. Drupal validates the form, runs submission hooks, and redirects the user to whatever page they are supposed to be on after form submission.

When #ajax is implemented, the process is the same as above, but with some additional steps in the middle.

  1. Some function is called which builds a render array representing the form.
  2. Form alter hooks are called, allowing other modules/theme to alter the form.
  3. Drupal caches the render array in the database, to be used for comparison on submission, as a security measure to ensure that the submitted data matches the type of data expected to be submitted.
  4. The form is rendered into HTML and sent to the browser.

---------- #ajax behavior start --------------

  1. The user does something that triggers #ajax
  2. The browser sends all the data in the form back to Drupal, as if the form were submitted.
  3. Drupal retrieves the form from the database, and confirms that the submitted data is allowed, and sanitizes it.
  4. Validation and submit handlers for the form are called. The form must be set to be rebuilt in the submit handler for the ajax to work.
  5. The form is rebuilt from the original form definition, passing it the #ajax submitted values.
  6. Form alter hooks are called, allowing other modules to alter the form, using the #ajax submitted values.
  7. Drupal caches the new render array in the database, to be used for comparison on submission.
  8. The ajax built form is passed to the #ajax callback handler. The handler then returns the part of the form to be injected back into the DOM on the browser. This can be part or all of the form.
  9. The new content is inserted into the DOM.

---------- #ajax behavior end --------------

  1. The user submits the form.
  2. Drupal retrieves the form from the database, confirms that the submitted data is allowed, and sanitizes it.
  3. Drupal validates the form, runs submission hooks, and redirects the user to whatever page they are supposed to be on after form submission.

Referring back to your code, your ajax callback will receive two arguments, $form and $form_state, and needs to return a section of your form. So it should look something like:

function ajax_callback($form, $form_state) {
  return $form['some_element']['of_the_form'];
}

In your form, this is likely $form['field_car_company']['und'][0]['value']. You will build the options of that form element when the form is initially rendered the way you are now, and if the form has been submitted using ajax, use the submitted values to determine the new options to show.