0

I'm trying to create a form that has two services injected (for access to an API) and a textfield with an AJAX callback. Here it is:

public function buildForm(array $form, FormStateInterface $form_state) {
    $form['vat_number'] = [
        '#title' => $this->t('VAT Number'),
        '#type' => 'textfield',
        '#default_value' => isset($address['vat_number']) ? $address['vat_number'] : '',
        '#states' => [
          'visible' => [
            ':input[name="is_business"]' => ['checked' => TRUE],
          ],
          'required' => [
            ':input[name="is_business"]' => ['checked' => TRUE],
          ],
        ],
        '#ajax' => [
          'callback' => '::validateVatAjax',
          'event' => 'change',
          'url' => Url::fromRoute('shop_product.digital_river_payment_form'),
          'options' => [
            'query' => [
              FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
            ],
          ],
        ],
        '#suffix' => '</div>',
      ];
    }
.....
}
public function validateVatAjax(array &$form, FormStateInterface $form_state) {$response = new AjaxResponse();
    $message = '<div id="errors" class="alert alert-danger" role="alert">';

    if (!$this->customerService->validateCustomerTaxFormat($form_state->getValue('vat_number'))) {
      $message .= $this->customerService->$schema['properties']['value']['description'];
      $message .= '</div>';
    }
    else {
      $message = '<div id="errors" class="alert alert-danger hide" role="alert"></div>';
    }
    $response->addCommand(new ReplaceCommand('#errors', $message));
    $form_state->setRebuild(FALSE);
    return $response;
  }

public function validateForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('is_business') == 1) {
        $vat_number = $form_state->getValue('vat_number');
        if (empty($vat_number)) {
          $form_state->setErrorByName('vat_number', $this->t('Please enter your tax number.'));
        }
        elseif (!$this->customerService->validateCustomerTaxFormat($form_state->getValue('vat_number'))) {
          $schema = $this->customerService->getSchema();
            $form_state->setErrorByName('vat_number', $schema['properties']['value']['description']);
          }
          else {
            $form_state->setErrorByName('vat_number', $this->t('Please enter your VAT number including the two letter country code.'));
          }
        }
      }
}

I omitted some of the code but my problem is that on the second AJAX call to the callback, it fails because the customerService is null. Aside from that, I don't understand why validateForm() is called on the textfield input change. Is this normal for Drupal?

Thanks in advance.

2
  • As hinted by the leading ::, the form callback must be a static function. You cannot use $this->something in a form callback.
    – Hudri
    Commented Jul 20, 2020 at 9:37
  • Hey! Thank you for your reply. That would render the method a bit useless in many cases. Looking into FormState::prepareCallback I can't see any reason why the method wouldn't run as expected as this is the same as calling call_user_func_array([$this, $method], $arguments). Anyway, still need to understand how to persist the DI services in my form class. Commented Jul 20, 2020 at 10:45

1 Answer 1

1

For anyone running into this problem or AJAX call that fails on the second call intermittently, check the properties used on your AJAX form class for the right access. It seems a trivial thing but change the properties of DInjected services to protected. Refrain from using private as the dependency serialization from Drupal won't be able to get those for caching purposes.

Check this link for more information.

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.