0

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..."},

1 Answer 1

1

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.

4
  • You are Perfect! It did exactly what I expected it to do. Mar 26, 2023 at 2:03
  • 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 }} Mar 26, 2023 at 2:05
  • 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.
    – 4uk4
    Mar 26, 2023 at 9:13
  • Sorry for the late reply. Thank you very much. I got it. Apr 2, 2023 at 1:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.