States JavaScript should use the once library, causes problems with Big Pipe

Created on 29 August 2023, 10 months ago
Updated 15 October 2023, 9 months ago

Problem/Motivation

Form API's States js behavior is initialized twice when BigPipe is enabled (and it is enabled by default), that causes misbehaviors when a user loads a form with already filled data from the server.

Steps to reproduce

Option 1: How it was initially discovered:
- ensure the BigPipe module is installed
- install webform
- create a form using:

first:
  '#type': likert
  '#title': First
  '#questions':
    Q1: Q1
    Q2: Q2
    Q3: Q3
  '#answers':
    A1: A1
    A2: A2
    A3: A3
container:
  '#type': container
  '#states':
    visible:
      - ':input[name="first[Q1]"]':
          value: A3
      - or
      - ':input[name="first[Q2]"]':
          value: A3
      - or
      - ':input[name="first[Q3]"]':
          value: A3
  markup:
    '#type': markup
    '#markup': '<p>Second Question text</p>'
  q1:
    '#type': radios
    '#title': Q1
    '#options':
      A: A
      B: B
      C: C
  q2:
    '#type': radios
    '#title': Q2
    '#options':
      A: A
      B: B
      C: C
  q3:
    '#type': radios
    '#title': Q3
    '#options':
      A: A
      B: B
      C: C

- go to the webform test tab
- refresh until first table has 2 A3 selected (right most column)
- change the A3 -> A2 answer for one of the questions
- expected: second section should still be visible
- actual: second section is hidden

Option 2: Using webtools to see that state.Dependent are initialized twice
- ensure the BigPipe module is installed
- in core/misc/states.js
- add debugger / log statement for the initialization of a states.Dependent
- expected: initialized once
- actual: initialized twice

Proposed resolution

- rewrite the behavior attach function using the the once helper as in:

  Drupal.behaviors.states = {
    attach(context, settings) {
      once('drupal-states-init', '[data-drupal-states]', context)
        .forEach(function (element) {
        const $element = $(element);
        const config = JSON.parse(
          element.getAttribute('data-drupal-states'),
        );
        Object.keys(config || {}).forEach((state) => {
          new states.Dependent({
            element: $element,
            state: states.State.sanitize(state),
            constraints: config[state],
          });
        });
      });

      // Execute all postponed functions now.
      while (states.postponed.length) {
        states.postponed.shift()();
      }
    },
  };

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

🐛 Bug report
Status

Closed: duplicate

Version

11.0 🔥

Component
Form 

Last updated about 5 hours ago

Created by

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

Production build 0.69.0 2024