Score:0

When a custom form is displayed in a custom template, ajax does not work

zm flag

What I want to do

I want to display a custom form with a custom template and use ajax in it to change values in the form.

Problem

Custom template notation prevents ajax from working. I didn't change anything in php, just twig, and tried 2 patterns as shown below. In twig1, when I changed the value of "a", the value of "b" became "hogehoge" and the log was output. However, in twig2, the value of b was not changed even if the value of a was changed, and there was no "update" in the log. !!Both of the name of templates are "sampleexample.html.twig", and both of them are rendered.

SampleForm2.php

    <?php

namespace Drupal\sampleexample\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Ajax\ReplaceCommand;

class SampleForm2 extends FormBase {

  /**
   * Current user account.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Node storage.
   *
   * @var \Drupal\node\NodeStorageInterface
   */
  protected $nodeManager;
  protected $taxonomanager;

  /**
   * {@inheritdoc}
   */
  public function __construct(
    EntityTypeManager $entity_type_manager,
    AccountProxyInterface $current_user
  ) {
    $this->currentUser = $current_user;
    $this->nodeManager = $entity_type_manager->getStorage('node');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('current_user')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(){
    return 'sampleform2';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) { 
    $form['#theme'] = 'sampleexample';
    $form['a'] = [
      '#type' => 'textfield',
      '#size' => '60',
      '#ajax' => [
        'callback' => '::myAjaxCallback', // don't forget :: when calling a class method.
        'disable-refocus' => FALSE, // Or TRUE to prevent re-focusing on the triggering element.
        'event' => 'change',
        'progress' => [
          'type' => 'throbber',
          'message' => $this->t('Verifying entry...'),
        ],
      ]
    ];
    $form['b'] = [
      '#type' => 'textfield',
      '#size' => '60',
    ];
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save data'),
    ];


    return $form;
  }

  function myAjaxCallback(array &$form, FormStateInterface $form_state, Request $request) {
    /** @var \Drupal\Core\Ajax\AjaxResponse $response */
    $response = new AjaxResponse(); 
    
    \Drupal::logger('b')->notice('update');
    $form['b']['#value'] = $this->t('hogehoge');
    $response
        ->addCommand(new ReplaceCommand(".form-item--b", $form['b']));
    return $response;

  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {

  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    
  }
}

twig1

{{ form_build_id }}
{{ form_token }}
{{ form_id }}

{{ form }}

twig2

{{ form_build_id }}
{{ form_token }}
{{ form_id }}

{{ form.a }}
{{ form.b }}
{{ form.actions.submit }}

Points of concern I checked if there is any difference in the dom displayed on the page when using twig1 and twig2 respectively. I found that

<input autocomplete="off" data-drupal-selector="form-if-9dbyv7ohy9odmuog4qlckk6aemou8w5bnljfhobi"
             type="hidden" name="form_build_id" value="form-If-9Dbyv7ohY9oDmUOG4qLCkk6AemOu8w5bnlJFHObI">
<input data-drupal-selector="edit-sampleform2-form-token" type="hidden" name="form_token"
            value="gjDpTprqywwq9hMaizsiaL6YU300vcJJ36FRW_o2sFM">
<input data-drupal-selector="edit-sampleform2" type="hidden" name="form_id" value="sampleform2">

And the callback is written differently.

twig1

"ajax":{"edit-a":{"callback":"::myAjaxCallback","disable-refocus":false,"event":"change","progress":{"type":"throbber","message":"Verifying entry..."},

twig2

"ajax":{"edit-a":{"callback":"Drupal\\sampleexample\\Form\\SampleForm2::myAjaxCallback","disable-refocus":false,"event":"change","progress":{"type":"throbber","message":"Verifying entry..."},
Score:1
cn flag

The only variable available in a #theme form template is

{{ form }}

and this is the only thing you need to render. If you render single form elements or groups then include the rest of the form variable with the help of the |without filter.

A more complex example from the core Claro theme:

core/themes/claro/templates/node-edit-form.html.twig

{#
/**
 * @file
 * Theme override for a node edit form.
 *
 * Two column template for the node add/edit form.
 *
 * This template will be used when a node edit form specifies 'node_edit_form'
 * as its #theme callback.  Otherwise, by default, node add/edit forms will be
 * themed by form.html.twig.
 *
 * Available variables:
 * - form: The node add/edit form.
 *
 * @see claro_form_node_form_alter()
 */
#}
<div class="layout-node-form clearfix">
  <div class="layout-region layout-region--node-main">
    <div class="layout-region__content">
      {{ form|without('advanced', 'footer', 'actions') }}
    </div>
  </div>
  <div class="layout-region layout-region--node-secondary">
    <div class="layout-region__content">
      {{ form.advanced }}
    </div>
  </div>
  <div class="layout-region layout-region--node-footer">
    <div class="layout-region__content">
      <div class="divider"></div>
      {{ form.footer }}
      {{ form.actions }}
    </div>
  </div>
</div>

If you don't render the entire form variable then libraries like Ajax don't work.

shibasaki_stack avatar
zm flag
You are Perfect! It did exactly what I expected it to do.
shibasaki_stack avatar
zm flag
I did the method you wrote and it added form_build_id, form_token and form_id. However, I originally included them in the template. Is it unnecessary to include the following in the template? {{ form_build_id }} {{ form_token }} {{ form_id }}
4uk4 avatar
cn flag
This is unnecessary, in this kind of template. You have probably seen this in attempts to hack the form wrapper `form.html.twig`, a different template rendering the outside of the HTML form tag. They then try to render the already rendered inside of the form again, which only works for simple forms.
shibasaki_stack avatar
zm flag
Sorry for the late reply. Thank you very much. I got it.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.