0

I have an issue with changing / obtaining variables in form element's ajax callback function. (Sorry for typos, I just placed a pieces of whole code).

I have form with input field, where user can select how many pieces of each product he want

$form['wrapper_amount']['value'] = [
        '#type' => 'number',
        '#prefix' => '<div class="d-flex align-items-end form-element">',
        '#suffix' => '<span class="form-element-unit">pcs</span></div>',
        '#name' => "amount--product-1",
        '#ajax' => [
            'callback' => '::calculatePricePerUnit',
            'event' => 'change',
            'disable-refocus' => TRUE,
            'progress' => [
                'type' => 'fade',
                'message' => NULL,
            ],
        ],
    ];

after choosing a number I calculate price using AJAX callback function and using

public function calculatePricePerUnit(array &$form, FormStateInterface $form_state)
{
    // ... price calculation for specific product
    // $new_price = (amount * price_per_unit);

    $total_price = (float)$form_state->get('total_price_with_vat') + $new_price;
    $response = new AjaxResponse();
    $response->addCommand(
        new InvokeCommand(
            '#total-price',
            'html', [$form_state->get('total_price_with_vat') . ' €']
        )
    );
}

The problem is that when used add amount to second product the $form_state->get('total_price_with_vat') is 0 again. It means that I cant store $total_price variable in $form_state, because it's not persistent (I've already read this somewhere, that I can't store variables to $form_state in AJAX callback function). So I tried to store it to $form_state->setStorage($my_values) but it didn't work either.

Please note that all AjaxResponse commands are working and I only need to find best approach how to store data, not how to re-render form's elements.

My next step is to store data in PrivateTempStore, but question is, if it's good approach? I don't need data on any other Wizard page, neither in Controller or any further step. I just need to calculate $total_price and some other data that I need to use multiple times in AJAX callback function, and at the end use webform API to send webform submission results via email.

2
  • Don't do form logic in an ajax callback. Move the code to form build and check $form_state->getTriggeringElement(). About storing values, $form_state works, but is a bit tricky to get started because the initial value isn't stored in the form cache.
    – 4uk4
    Mar 9, 2020 at 8:01
  • Thanks for your reply. I'll redesign my form functionality and post answer, when it's done. Mar 9, 2020 at 8:46

2 Answers 2

0

Ok, according to answers, I updated my question. Can I update $form_state values when triggering AJAX event on form element that is not type of 'submit' / 'button'?

$form['wrapper_amount']['value'] = [
    '#type' => 'number',
    '#title' => $this->t('Enter amount of product'),
    '#ajax' => [
        'callback' => '::calculatePricePerUnit',
        'event' => 'change',
        'disable-refocus' => TRUE,
        'progress' => [
        'type' => 'fade',
        'message' => NULL,
        ],
    ],
];

This is code for my input type="number" and every time user changes amount of product I want to call AJAX and update $total_price. I'm summing up $total_price each time user updates amount of product in form (there are multiple product in form). But I can't update $total_price correctly, because I need to store it somewhere to obtain it next time, when user changes amount of product, so I can sum another price to it.

Is this possible somehow without having user to click every time on button to submit form to calculate price?

1
  • Whatever happened to store it inside $form ?
    – No Sssweat
    Mar 9, 2020 at 8:55
0

PrivateTempStore is not the right fit for this. You want to store the value in the $form_state, but you do not store it in the AJAX callback, you store it in the submit handler, then set the form to rebuild (which will persist the values in the storage. You can set a custom #submit handler on your ajax submit button that is only called when that button is clicked:

$form['wrapper_amount']['value'] = [
  '#type' => 'number',
  '#prefix' => '<div class="d-flex align-items-end form-element">',
  '#suffix' => '<span class="form-element-unit">pcs</span></div>',
  // note - #name is commented out, as you should never
  // need to change it in Drupal, and doing so will have
  // unexpected consequences. 
  // '#name' => "amount--product-1",
  '#ajax' => [
    'callback' => '::calculatePricePerUnit',
    'event' => 'change',
    'disable-refocus' => TRUE,
    'progress' => [
      'type' => 'fade',
      'message' => NULL,
    ],
  ],
  '#submit' => ['::customSubmit'],
];

Then your custom submit will look like this:

public function customSubmit(array &$form, FormStateInterface $form_state) {
  $total_price = (float)$form_state->get('total_price_with_vat') + $new_price;
  $form_state->set('total_price', total_price);
  $form_state->setRebuild(TRUE);
}

After playing around with this a little, I can see that a submit button is required to be able to call a submit handler where rebuildForm() can be recalled. If you set the #ajax on a non-submit button, ajax is called, but rebuildForm() is not able to be called.

As such, I'd say the hack for this is to create an #ajax submit button as I've shown above, but hide it. Then add some custom JS on the number field, that on blur triggers a click of the hidden submit button.

10
  • I do not have a button. I want to change $total_price on ['#ajax']['event'] = 'change'. If possible. As far as I know, I cant use ['#submit'] attribute on ['#type'] = 'number'. Mar 8, 2020 at 23:36
  • You can put this in the ‘submitForm()’ method of the form then, instead of a custom submit handler.
    – Jaypan
    Mar 8, 2020 at 23:37
  • is there any way how to calculate price and display it to user without any need to click some kind of button? Mar 8, 2020 at 23:40
  • Is what you've done above not doing that?
    – Jaypan
    Mar 8, 2020 at 23:48
  • No properly. I can calculate partial prices for each product but therefore I cant sum & store partial prices to one $total_price I cant reach wanted functionality. Mar 8, 2020 at 23:50

Your Answer

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

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