I'm building a form which should calculate some values during the form view. I've tried to use the computed twig field. The calculation is working after submission and also, when I move to the previous or next page but I want the field to be calculated immediately after the base values has been entered (jQuery/AJAX).

The calculation should look like

If theres no way of doing it automatically, how can I accomplish this using a "Calculate" button?

I hope somebody can help with this issue or give an example for a form which is using this kind of calculation.


  • 1
    use javascript to make your calculation when the input value changes – Ronnie Apr 24 '18 at 18:42

if you use the form API to build your form, then you can use the ajax form API.

In the field you want to trigger ajax method, use this :

$form['test'] = [
  '#type' => 'textfield',
  '#title' => 'A Textfield',
  '#required' => TRUE,
  '#ajax' => [
    'callback' => '::ajaxTest',
    'event' => 'keyup',
    'wrapper' => 'edit-output',
    'progress' => [
      'type' => 'throbber',
      'message' => t('Verifying entry...'),

Then create a ajaxTest function in your Form :

public function ajaxTest(array &$form, FormStateInterface $form_state) : array {
  // you can use the $form_state here to get values of all your fields
  $my_value = $form_state->getValue('my_field');
  // Make your logic code here
  $new_val = 'foo';

And then just add a AjaxCommand to your ajaxTest function :

$response = new AjaxResponse();
$response->addCommand(new InvokeCommand('#my_id_to_change', 'val', $new_val));
return $response;

You can find all those in the Form and AJAX API here https://api.drupal.org/api/drupal/core%21core.api.php/group/ajax/8.5.x, regards.

  • I'm using the webform module for this form, so I need a solution which fits into this module. – xurubo93 Apr 30 '18 at 7:15

I'm doing something similar to the above request using a computed twig. Make sure "automatically update the computed value using Ajax?" is checked, and then enter this in the computed value / markup section of the computed twig;

{{ data.weight * data.number_of_packages }}

this follows the syntax

{{ data.element_key * data.element_key }}

This might work or might not. It's super finicky. Right now I have one working computed twig with an if statement and another computed twig with the same code that isn't working! bargh! Feel free to contact me if you're dealing with drupal webforms and would like a computed element. They can be frustrating.

Also, this piece of code, when ran in a computed twig, is great for discovering the element keys of your form!

  {% for key, value in _context  %}
    <li>{{ key }}</li>
  {% endfor %}



I ended up in writing a module which provides a Composite Element for the webform module. This module defines all fields I need and loads a javascript file in which I included the automatic calculation of the fields I needed.

The total fields are set "readonly", in order to get the values stored into the submission of the form.

Looks like:

The code for the javascript file is:

(function ($, Drupal) {

'use strict';

Drupal.webform = Drupal.webform || {};

 * Find all inputs referring to a defined class in a custom composite element and register an input event.
 * If an input is detected and this input is a valid number, do the calculation of the weight and volume
 * One composite element refers to a package to be delivered. A form can consist an unlimited number of packages for now
 * @type {Drupal~behavior}
Drupal.behaviors.vacInputEvent = {
    attach: function (context, settings) {
        var container = $(context).find('#webform_composite_package_table');
        var inputs = $(context).find('[data-drupal-selector="edit-webform-composite-package-items"] input.vac-webform-composite-package-inputs[type="text"]');
        var totals = $(context).find('[data-drupal-selector="edit-webform-composite-package-items"] input.vac-webform-composite-package-totals[type="text"]');
        var grandtotalweight = $('[data-drupal-selector="edit-grand-total-weight"]');
        var grandtotalvolume = $('[data-drupal-selector="edit-grand-total-volume"]');
        inputs.once('vac-input-event').each(function () {
            $(this).on('input', function () {
                if (!isNaN(parseInt($(this).val()))) {
                    calculateInputs(inputs, totals, grandtotalweight, grandtotalvolume);
        //If we are on the right context and all elements are loaded, do an initial calculation.
        //This is also needed, when a composite package is removed from the document
        if (inputs.length && totals.length && grandtotalweight.length && grandtotalvolume.length) {
            calculateInputs(inputs, totals, grandtotalweight, grandtotalvolume);

         * iterate through the given object, calculate the values and show the values on the regarding totals textfield.
         * @var resultObj
         * - The Object which is generated in the calculateInputs function
         * @var totals
         * - The input elements, showing the calculated totals
         * @grandtotalweight
         * - The input element, showing the grand total of the calculated weight
         * @grandtotalvolume
         * - The input element, showing the grand total of the calculated volume
        function showTotals(resultObj, totals, grandtotalweight, grandtotalvolume) {
            var row;
            var weight;
            var volume;
            var total_volume ;
            var grand_total_weight = 0;
            var grand_total_volume = 0;

            $.each(resultObj.packageFigures, function (index, value) {
                //Calculation of Weight
                if (!isNaN(value.numberOfPackages) && !isNaN(value.weightOfPackage)) {
                    row = value.rowNo;
                    weight = value.numberOfPackages*(value.weightOfPackage/100);
                    grand_total_weight += weight;
                    (Math.round( weight * 100 )/100 ).toString();
                //Calculation of Volume of one collie
                if (!isNaN(value.lengthtOfPackage) && !isNaN(value.widthOfPackage) && !isNaN(value.heightOfPackage)) {
                    row = value.rowNo;
                    volume = ((value.lengthtOfPackage/10000) * (value.widthOfPackage/10000) * (value.heightOfPackage/10000));
                    volume = (Math.round( volume * 100 )/100 ).toFixed(3).replace(".",",");
                } else {
                    volume = "0,00";
                //Calculation of the total volume
                if (!isNaN(value.numberOfPackages) && !isNaN(value.lengthtOfPackage) && !isNaN(value.widthOfPackage) && !isNaN(value.heightOfPackage)) {
                    row = value.rowNo;
                    total_volume = ((value.lengthtOfPackage/10000) * (value.widthOfPackage/10000) * (value.heightOfPackage/10000)) * value.numberOfPackages;
                    grand_total_volume = grand_total_volume + total_volume;
                    total_volume = (Math.round( total_volume * 100 )/100 ).toFixed(3).replace(".",",");
                } else {
                    total_volume = "0,00";

                //Show the calculated values on the regarding textfield
                if (index === parseInt(row)) {
                    totals.each(function () {
                        if ($(this).attr('data-drupal-selector') === 'edit-webform-composite-package-items-'+row+'-item-total-weight-of-package') {
                        if ($(this).attr('data-drupal-selector') === 'edit-webform-composite-package-items-'+row+'-item-total-volume-of-package') {
                        if ($(this).attr('data-drupal-selector') === 'edit-webform-composite-package-items-'+row+'-item-total-volume-of-all-packages') {
            if (!isNaN(grand_total_weight)) {
                grand_total_weight = (Math.round( grand_total_weight * 100 )/100 ).toFixed(2).replace(".",",");
            if (!isNaN(grand_total_volume)) {
                grand_total_volume = (Math.round( grand_total_volume * 100 )/100 ).toFixed(2).replace(".",",");


         * Build an object from the given input fields
        function calculateInputs(inputs, totals, grandtotalweight, grandtotalvolume) {
            var resultObj = {};
            resultObj.packageFigures = [];
            var currentPackage = {};
            currentPackage.rowNo = "0";
            inputs.each(function () {
                var a = $(this).attr('data-drupal-selector').split('-');
                var row_number = a[5];
                var input = $(this);
                if (currentPackage.rowNo !== row_number) {
                    if (!$.isEmptyObject(currentPackage)) {
                        currentPackage = {};
                        currentPackage.rowNo = row_number;
                switch (input.attr('data-drupal-selector')) {
                    case 'edit-webform-composite-package-items-'+row_number+'-item-number-of-packages':
                        currentPackage.numberOfPackages = parseInt(input.val().replace(/\./g,""));
                    case 'edit-webform-composite-package-items-'+row_number+'-item-weight-of-package':
                        currentPackage.weightOfPackage = parseFloat(input.val().replace(",","").replace(/\./g,""));
                    case 'edit-webform-composite-package-items-'+row_number+'-item-length-of-package':
                        currentPackage.lengthtOfPackage = parseFloat(input.val().replace(",","").replace(/\./g,""));
                    case 'edit-webform-composite-package-items-'+row_number+'-item-width-of-package':
                        currentPackage.widthOfPackage = parseFloat(input.val().replace(",","").replace(/\./g,""));
                    case 'edit-webform-composite-package-items-'+row_number+'-item-height-of-package':
                        currentPackage.heightOfPackage = parseFloat(input.val().replace(",","").replace(/\./g,""));
            //After iteration is finished push the currentPackage to the resultObj and calculate all Totals
            showTotals(resultObj, totals, grandtotalweight, grandtotalvolume);
})(jQuery, Drupal);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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