Problem/Motivation
I built a form that has a custom composite element that has an "add more" button to add additional items. There's also a computed twig element that adds up values from the computed composite element and is configured to refresh its value automatically via AJAX.
There is some strange behavior that can occur sometimes when using "Add more". It works well the first time, but doing it a second time will sometimes remove elements that were previously added via the add more.
Here's a video demonstrating the issue. The first part of the video shows how it should work. I added 3 elements to the composite, then added an additional element, which created a total of 5 elements. Great. Then, I refreshed the page and tried again. This time the problem occurred. I added 3 elements, then added an additional element, and instead of 5 total being shown, now there are just 2. If actual data was entered in the existing rows, it would have been lost.
Note that I can repeat this experiment many times and reproduce it some of the time. In the above case, I happened to reproduce it on the 2nd attempt.
Steps to reproduce
I cannot reproduce it reliably, so I think there's a race condition with the various AJAX callbacks executing.
- Use the webform provided below.
- Use "add more" button to add 3 elements and wait for them to show up in the form.
- Use the "add more" button and input to add 1 additional element. I've had the best results reproducing it when I very quickly click "Add more" after decrementing the input from 3 to 1.
- Observe that instead of the expected 5 total elements, there are now just 2. The 3 that were added previously were removed.
Simple webform:
uuid: 2ecbb497-60a9-4aa7-ae4b-a6d2f1efa3fb
langcode: en
status: open
dependencies: { }
weight: 0
open: null
close: null
uid: null
template: false
archive: false
id: sdspx
title: 'Computed twig issue with add more button'
description: ''
categories: { }
elements: |-
custom_composite_test_with_add_more:
'#type': webform_custom_composite
'#title': 'Custom composite test with add more'
'#multiple': 20
'#title_display': before
'#multiple__sorting': false
'#element':
desc:
'#type': textfield
'#title': Description
amount:
'#type': number
'#title': Amount
total_amount:
'#type': webform_computed_twig
'#title': 'Total amount'
'#template': |-
{% set sum1 = 0 %}
{% for value in data.custom_composite_test_with_add_more %}
{% if value.amount %}
{% set sum1 = sum1 + value.amount %}
{% endif %}
{% endfor %}
${{ sum1|number_format(2, '.', ',') }}
'#whitespace': trim
'#ajax': true
css: ''
javascript: ''
settings:
ajax: false
ajax_scroll_top: form
ajax_progress_type: ''
ajax_effect: ''
ajax_speed: null
page: true
page_submit_path: ''
page_confirm_path: ''
page_theme_name: ''
form_title: both
form_submit_once: false
form_open_message: ''
form_close_message: ''
form_exception_message: ''
form_previous_submissions: true
form_confidential: false
form_confidential_message: ''
form_disable_remote_addr: false
form_convert_anonymous: false
form_prepopulate: false
form_prepopulate_source_entity: false
form_prepopulate_source_entity_required: false
form_prepopulate_source_entity_type: ''
form_unsaved: false
form_disable_back: false
form_submit_back: false
form_disable_autocomplete: false
form_novalidate: false
form_disable_inline_errors: false
form_required: false
form_autofocus: false
form_details_toggle: false
form_reset: false
form_access_denied: default
form_access_denied_title: ''
form_access_denied_message: ''
form_access_denied_attributes: { }
form_file_limit: ''
form_attributes: { }
form_method: ''
form_action: ''
share: false
share_node: false
share_theme_name: ''
share_title: true
share_page_body_attributes: { }
submission_label: ''
submission_exception_message: ''
submission_locked_message: ''
submission_log: false
submission_excluded_elements: { }
submission_exclude_empty: false
submission_exclude_empty_checkbox: false
submission_views: { }
submission_views_replace: { }
submission_user_columns: { }
submission_user_duplicate: false
submission_access_denied: default
submission_access_denied_title: ''
submission_access_denied_message: ''
submission_access_denied_attributes: { }
previous_submission_message: ''
previous_submissions_message: ''
autofill: false
autofill_message: ''
autofill_excluded_elements: { }
wizard_progress_bar: true
wizard_progress_pages: false
wizard_progress_percentage: false
wizard_progress_link: false
wizard_progress_states: false
wizard_start_label: ''
wizard_preview_link: false
wizard_confirmation: true
wizard_confirmation_label: ''
wizard_auto_forward: true
wizard_auto_forward_hide_next_button: false
wizard_keyboard: true
wizard_track: ''
wizard_prev_button_label: ''
wizard_next_button_label: ''
wizard_toggle: false
wizard_toggle_show_label: ''
wizard_toggle_hide_label: ''
wizard_page_type: container
wizard_page_title_tag: h2
preview: 0
preview_label: ''
preview_title: ''
preview_message: ''
preview_attributes: { }
preview_excluded_elements: { }
preview_exclude_empty: true
preview_exclude_empty_checkbox: false
draft: none
draft_multiple: false
draft_auto_save: false
draft_saved_message: ''
draft_loaded_message: ''
draft_pending_single_message: ''
draft_pending_multiple_message: ''
confirmation_type: page
confirmation_url: ''
confirmation_title: 'Thank you!'
confirmation_message: '<p>Your submission has been received.</p>'
confirmation_attributes: { }
confirmation_back: true
confirmation_back_label: ''
confirmation_back_attributes: { }
confirmation_exclude_query: false
confirmation_exclude_token: false
confirmation_update: false
limit_total: null
limit_total_interval: null
limit_total_message: ''
limit_total_unique: false
limit_user: null
limit_user_interval: null
limit_user_message: ''
limit_user_unique: false
entity_limit_total: null
entity_limit_total_interval: null
entity_limit_user: null
entity_limit_user_interval: null
purge: none
purge_days: null
results_disabled: false
results_disabled_ignore: false
results_customize: false
token_view: false
token_update: false
token_delete: false
serial_disabled: false
access:
create:
roles:
- anonymous
- authenticated
users: { }
permissions: { }
view_any:
roles: { }
users: { }
permissions: { }
update_any:
roles: { }
users: { }
permissions: { }
delete_any:
roles: { }
users: { }
permissions: { }
purge_any:
roles: { }
users: { }
permissions: { }
view_own:
roles: { }
users: { }
permissions: { }
update_own:
roles: { }
users: { }
permissions: { }
delete_own:
roles: { }
users: { }
permissions: { }
administer:
roles: { }
users: { }
permissions: { }
test:
roles: { }
users: { }
permissions: { }
configuration:
roles: { }
users: { }
permissions: { }
handlers: { }
variants: { }
Proposed resolution
I suspect there's a race condition here with the AJAX callbacks. I think what may be happening is if you trigger a field change to any of the fields and then quickly click "Add more" to add additional fields, something is confused in the backend.
One thing that may help is to remove the "Add more" number input from the list of computed twig trigger elements. There's no reason that computed twig elements would need to be refreshed when this form input is changed. When I removed it from the list of triggers, I was unable to reproduce the problem. But, I'm not sure if the other inputs in the composite element being refreshed quickly before the "Add more" button is clicked will still trigger the problem.
Remaining tasks
User interface changes
API changes
Data model changes