Dependent dropdowns on signup form

Hi.

I’ve been able to create a code with the help of GROK and ChatGPT, that I inserted into components JS tab, after I created some custom user fields in the signup menu.

I have managed to get the functionality of the dependent dropdown menu to work, but, but submiting the form is not possible.

The code is currently available for review at foorum.maarahvas.ee/signup

Here’s the JS code that is currently in use there:

// dependent-dropdown.js
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer("0.8", api => {
  if (window.__DEPENDENT_DROPDOWN_INITIALIZED) {
    console.debug("Dependent dropdown script already initialized, skipping.");
    return;
  }
  window.__DEPENDENT_DROPDOWN_INITIALIZED = true;

  const landParishMap = {
    "Virumaa": ["Ebavere (Väike-Maarja)", "Haljala", "Jõvi (Jõhvi)", "Katkuküla (Simuna)", "Lüganuse", "Mahu (Viru-Nigula)", "Rakvere", "Rõhu (Viru-Jaagupi)", "Torvastvere (Kadrina)", "Tärevere (Iisaku)", "Vaivara"],
    "Järvamaa": ["Ambla", "Keika (Järva-Jaani)", "Koeru", "Kullamäe (Järva-Madise)", "Nõstvere (Anna)", "Paide", "Türi", "Ämbra (Peetri)"],
    "Harjumaa": ["Hageri", "Juuru", "Jõelähtme", "Keila", "Kose", "Kuusalu", "Külaselja (Risti)", "Nissi", "Padise (Harju-Madise)", "Rapla", "Sahataguse (Harju-Jaani)", "Vaskjala (Jüri)"],
    "Läänemaa": ["Emaste (Emmaste)", "Hanila", "Karuse", "Kirbla", "Kullamaa", "Käina", "Lihula", "Märjamaa", "Noarootsi", "Pöhelepe (Pühalepa)", "Pööna (Lääne-Nigula)", "Reigi", "Rõdali (Ridala)", "Soontaga (Mihkli)", "Umra (Martna)", "Varbla", "Vigala", "Vormsi"],
    "Saaremaa": ["Anseküla", "Jämaja", "Kaarma", "Karja", "Kihelkonna", "Kärla", "Muhu", "Mustjala", "Para (Jaani)", "Pöide", "Püha", "Ruhnu", "Valjala"],
    "Pärnumaa": ["Aliste (Halliste)", "Audru", "Häädemeeste", "Karksi", "Kõrve (Pärnu-Jaagupi)", "Pärnu", "Ruhja", "Saarde", "Salatsi", "Soontaga (Mihkli)", "Tori", "Tõstamaa", "Vändra"],
    "Wiljandimaa": ["Helme", "Kolga (Kolga-Jaani)", "Kõpu", "Paistu", "Pilistvere", "Põltsamaa", "Tarvastu", "Valula (Suure-Jaani)", "Viljandi"],
    "Tartumaa": ["Kambja", "Kodavere", "Kursi", "Kõrenduse (Maarja-Magdaleena)", "Laiuse", "Nõo", "Otepää", "Palamuse", "Puhja", "Rannu", "Rõngu", "Sangaste", "Tartu (Tartu-Maarja)", "Tõrma (Torma)", "Võnnu", "Äksi"],
    "Võrumaa": ["Hargla", "Kanepi", "Karula", "Luke", "Põlva", "Rõugu (Rõuge)", "Räpinä (Räpina)", "Urvaste", "Vahtseliina (Vastseliina)", "Valga"],
    "Setomaa": ["Setomaa"]
  };

  const LAND_FIELD_NAME = "user_fields[6]";
  const PARISH_FIELD_NAME = "user_fields[8]";

  function findSignupForm() {
    return (
      document.querySelector(".user-signup-form") ||
      document.querySelector("form[action*='signup']") ||
      document.querySelector("form[id*='new-account']") ||
      document.querySelector("form:has(.user-field)")
    );
  }

  function resetSelectKitState(header, body, fieldName) {
    header.classList.remove("is-expanded", "is-invalid", "is-disabled");
    header.classList.add("is-valid");
    header.setAttribute("tabindex", "0");
    header.setAttribute("aria-expanded", "false");
    body.style.display = "";
    body.classList.remove("is-expanded");
    console.debug(`Resetting SelectKit state for ${fieldName}:`, {
      headerClasses: header.className,
      bodyDisplay: body.style.display,
      ariaExpanded: header.getAttribute("aria-expanded")
    });
    setTimeout(() => {
      body.style.display = "";
      header.classList.remove("is-expanded");
      header.focus();
    }, 100);
  }

  function initializeDropdowns() {
    try {
      if (!window.location.pathname.includes("/signup")) {
        console.debug("Not on sign-up page, skipping initialization");
        return;
      }

      const signupForm = findSignupForm();
      if (!signupForm) {
        console.error("Sign-up form not found");
        return;
      }

      const landField = Array.from(signupForm.querySelectorAll(".user-field")).find(field =>
        field.querySelector("label")?.textContent.trim() === "Maa"
      );
      const parishField = Array.from(signupForm.querySelectorAll(".user-field")).find(field =>
        field.querySelector("label")?.textContent.trim() === "Kodukihelkond"
      );

      const landHeader = landField?.querySelector(".select-kit-header");
      const parishHeader = parishField?.querySelector(".select-kit-header");
      const landBody = landField?.querySelector(".select-kit-body");
      const parishBody = parishField?.querySelector(".select-kit-body");

      console.debug("Form and field details:", {
        signupForm: !!signupForm,
        landField: !!landField,
        parishField: !!parishField,
        landHeader: !!landHeader,
        parishHeader: !!parishHeader,
        landBody: !!landBody,
        parishBody: !!parishBody
      });

      if (!landField || !parishField || !landHeader || !parishHeader || !landBody || !parishBody) {
        console.error("Required elements not found");
        return;
      }

      const landLabel = landField.querySelector("label");
      const parishLabel = parishField.querySelector("label");
      if (landLabel) landLabel.setAttribute("for", "user_fields_6");
      if (parishLabel) parishLabel.setAttribute("for", "user_fields_8");

      function getOrCreateInputField(fieldContainer, fieldName, inputId) {
        let inputField = signupForm.querySelector(`input[name='${fieldName}']`);
        if (!inputField) {
          console.warn(`No input field found for ${fieldName}, creating one`);
          inputField = document.createElement("input");
          inputField.type = "hidden";
          inputField.name = fieldName;
          inputField.id = inputId;
          inputField.value = "";
          inputField.setAttribute("required", "required");
          inputField.setAttribute("data-select-kit", "true");
          const controls = fieldContainer.querySelector(".controls");
          if (controls) {
            controls.appendChild(inputField);
          } else {
            fieldContainer.appendChild(inputField);
          }
        }
        console.debug(`Input field details for ${fieldName}:`, {
          name: inputField.name,
          value: inputField.value,
          id: inputField.id,
          parent: inputField.parentElement?.className || inputField.parentElement?.tagName
        });
        return inputField;
      }

      const landInput = getOrCreateInputField(landField, LAND_FIELD_NAME, "user_fields_6");
      const parishInput = getOrCreateInputField(parishField, PARISH_FIELD_NAME, "user_fields_8");

      const usernameInput = signupForm.querySelector("input[name='username']");
      const emailInput = signupForm.querySelector("input[name='email']");
      if (usernameInput) usernameInput.setAttribute("autocomplete", "username");
      if (emailInput) emailInput.setAttribute("autocomplete", "email");

      function updateParishOptions(selectedLand) {
        if (!parishBody) {
          console.error("Parish SelectKit body not found");
          return;
        }

        parishBody.innerHTML = "";
        const defaultOption = document.createElement("div");
        defaultOption.className = "select-kit-row is-none";
        defaultOption.setAttribute("data-value", "");
        defaultOption.setAttribute("data-name", "Vali kihelkond");
        defaultOption.innerHTML = '<span class="name">Vali kihelkond</span>';
        parishBody.appendChild(defaultOption);

        if (selectedLand && landParishMap[selectedLand]) {
          landParishMap[selectedLand].forEach(parish => {
            const option = document.createElement("div");
            option.className = "select-kit-row";
            option.setAttribute("data-value", parish);
            option.setAttribute("data-name", parish);
            option.innerHTML = `<span class="name">${parish}</span>`;
            parishBody.appendChild(option);
          });

          parishHeader.setAttribute("data-value", "");
          parishHeader.setAttribute("data-name", "Vali kihelkond");
          const selectedName = parishHeader.querySelector(".select-kit-selected-name");
          if (selectedName) {
            selectedName.innerHTML = '<span class="name">Vali kihelkond</span>';
          }
          parishHeader.setAttribute("aria-label", "Vali kihelkond");
          parishHeader.classList.remove("is-disabled", "is-invalid");
          parishHeader.classList.add("is-valid");
          parishHeader.setAttribute("tabindex", "0");

          parishInput.value = "";
          parishInput.classList.remove("invalid");
          parishInput.dispatchEvent(new Event("change", { bubbles: true }));
          parishInput.dispatchEvent(new Event("input", { bubbles: true }));
          parishInput.dispatchEvent(new Event("blur", { bubbles: true }));
        } else {
          parishHeader.classList.add("is-disabled");
          parishHeader.setAttribute("tabindex", "-1");
          parishInput.value = "";
          parishInput.dispatchEvent(new Event("change", { bubbles: true }));
        }

        resetSelectKitState(parishHeader, parishBody, "Kodukihelkond");
      }

      parishBody.addEventListener("click", (event) => {
        const row = event.target.closest(".select-kit-row:not(.is-none)");
        if (!row) return;

        const value = row.getAttribute("data-value");
        const name = row.getAttribute("data-name");
        parishHeader.setAttribute("data-value", value);
        parishHeader.setAttribute("data-name", name);
        const selectedName = parishHeader.querySelector(".select-kit-selected-name");
        if (selectedName) {
          selectedName.innerHTML = `<span class="name">${name}</span>`;
        }
        parishHeader.setAttribute("aria-label", `Valitud: ${name}`);
        parishHeader.classList.remove("is-invalid");
        parishHeader.classList.add("is-valid");
        parishHeader.closest(".select-kit")?.classList.remove("is-invalid");

        parishInput.value = value;
        parishInput.classList.remove("invalid");
        parishInput.dispatchEvent(new Event("change", { bubbles: true }));
        parishInput.dispatchEvent(new Event("input", { bubbles: true }));
        parishInput.dispatchEvent(new Event("blur", { bubbles: true }));
        const customEvent = new CustomEvent("select-kit:change", { bubbles: true, detail: { value } });
        parishInput.dispatchEvent(customEvent);
        signupForm.dispatchEvent(new Event("change", { bubbles: true }));

        console.debug("Parish input updated:", {
          value,
          valid: parishInput.checkValidity(),
          formValid: signupForm.checkValidity(),
          parishHeaderClasses: parishHeader.className,
          parishBodyDisplay: parishBody.style.display
        });

        resetSelectKitState(parishHeader, parishBody, "Kodukihelkond");
      });

      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.attributeName === "data-value") {
            const selectedLand = landHeader.getAttribute("data-value");
            landInput.value = selectedLand || "";
            landInput.classList.remove("invalid");
            landInput.dispatchEvent(new Event("change", { bubbles: true }));
            landInput.dispatchEvent(new Event("input", { bubbles: true }));
            landInput.dispatchEvent(new Event("blur", { bubbles: true }));
            console.debug("Land input updated:", {
              value: selectedLand,
              valid: landInput.checkValidity(),
              formValid: signupForm.checkValidity()
            });
            updateParishOptions(selectedLand);
          }
        });
      });

      observer.observe(landHeader, {
        attributes: true,
        attributeFilter: ["data-value"]
      });

      const initialLand = landHeader.getAttribute("data-value");
      landInput.value = initialLand || "";
      updateParishOptions(initialLand);

      const formObserver = new MutationObserver(() => {
        if (!signupForm.querySelector(`input[name='${LAND_FIELD_NAME}']`)) {
          console.warn("Land input missing, recreating");
          getOrCreateInputField(landField, LAND_FIELD_NAME, "user_fields_6");
        }
        if (!signupForm.querySelector(`input[name='${PARISH_FIELD_NAME}']`)) {
          console.warn("Parish input missing, recreating");
          getOrCreateInputField(parishField, PARISH_FIELD_NAME, "user_fields_8");
        }
      });

      formObserver.observe(signupForm, { childList: true, subtree: true });

      signupForm.addEventListener("submit", () => {
        console.debug("Form submission attempted:", {
          landValue: landInput.value,
          landValid: landInput.checkValidity(),
          parishValue: parishInput.value,
          parishValid: parishInput.checkValidity(),
          formValid: signupForm.checkValidity(),
          formErrors: Array.from(signupForm.querySelectorAll(".invalid")).map(el => el.name || el.id)
        });
      });

      signupForm.addEventListener("ajax:error", (event) => {
        console.debug("Form submission error:", event.detail);
      });

      console.log("Dependent dropdowns initialized successfully.");
    } catch (error) {
      console.error("Error initializing dependent dropdowns:", error);
    }
  }

  const formObserver = new MutationObserver((mutations, obs) => {
    const signupForm = findSignupForm();
    if (signupForm && signupForm.querySelector(".user-field")) {
      initializeDropdowns();
      obs.disconnect();
    }
  });

  formObserver.observe(document.body, {
    childList: true,
    subtree: true
  });

  setTimeout(initializeDropdowns, 5000);
});

Does anyone have any clue what is wrong there and how can this be fixed?
Thanks

1 Like