Make all form fields in AI Chatbot required

Hey! I am having this issue. I have added the script to the Custom JS and my name and email fields are not being required on the AI Chat Bot form. Thanks!

1 Like

Welcome to the Community, @Roben-Marie_Smith :waving_hand:

I’ve forwarded your request to the devs and they’ll check the script. I’ll keep you updated :slightly_smiling_face:

Hi there, @Roben-Marie_Smith :waving_hand:

The previous script works only if of all form fields are used in the widget. Your widget doesn’t have a Phone field, so we’ve adjusted the script for you:

(function() {
  const WIDGET_WINDOW_SELECTOR = '.es-window-container';
  const FORM_SELECTOR = 'form';
  const SUBMIT_BTN_SELECTOR = 'button[type="submit"]';
  const REQUIRED_FIELDS = ['name', 'email', 'phone'];
  const FIELD_CONTAINER_SELECTOR = '[class*="FormFieldLayout__Element"]';
  const PLACEHOLDER_SELECTOR = '[class*="TextControlBase__TextControlBasePlaceholder"]';
  const ERROR_MSG_CLASS = 'es-custom-error-message';
  const EMPTY_ERROR_TEXT = 'This field is required';

  const createErrorElement = (text) => {
    const el = document.createElement('div');
    Object.assign(el.style, {
      color: 'rgb(255, 56, 56)',
      fontSize: '12px',
      lineHeight: '14px',
      marginTop: '4px',
    });
    el.className = ERROR_MSG_CLASS;
    el.innerText = text;
    return el;
  };

  const getContainer = (input) => input.closest(FIELD_CONTAINER_SELECTOR) || input.parentNode;

  const showInputError = (input) => {
    const container = getContainer(input);
    if (!container) return;

    if (!container.querySelector(`.${ERROR_MSG_CLASS}`)) {
      container.appendChild(createErrorElement(EMPTY_ERROR_TEXT));
    }
    input.style.borderColor = 'rgb(255, 56, 56)';
  };

  const hideInputError = (input) => {
    const container = getContainer(input);
    if (!container) return;

    const errorEl = container.querySelector(`.${ERROR_MSG_CLASS}`);
    if (errorEl) errorEl.remove();
    input.style.borderColor = '';
  };

  const addAsteriskToPlaceholder = (input) => {
    const container = getContainer(input);
    const placeholder = container?.querySelector(PLACEHOLDER_SELECTOR);
    if (placeholder && !placeholder.textContent.includes('*')) {
      placeholder.textContent = `${placeholder.textContent.trim()} *`;
    }
  };

  const processForm = (form) => {
    if (form.dataset.isValidatorAttached === 'true') return;
    form.dataset.isValidatorAttached = 'true';

    const submitBtn = form.querySelector(SUBMIT_BTN_SELECTOR);
    if (!submitBtn) return;

    const activeInputs = [];

    REQUIRED_FIELDS.forEach(fieldName => {
      const input = form.querySelector(`input[name="${fieldName}"]`);
      if (input) {
        activeInputs.push(input);
        addAsteriskToPlaceholder(input);
        input.addEventListener('input', () => hideInputError(input));
      }
    });

    if (activeInputs.length === 0) return;

    submitBtn.addEventListener('click', (e) => {
      let hasError = false;

      activeInputs.forEach(input => {
        const val = input.value.trim();
        if (!val) {
          showInputError(input);
          hasError = true;
        }
      });

      if (hasError) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
      }
    }, true);
  };

  const watchWidgetContent = (widgetNode) => {
    const existingForm = widgetNode.querySelector(FORM_SELECTOR);
    if (existingForm) processForm(existingForm);

    const widgetObserver = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (mutation.addedNodes.length) {
          mutation.addedNodes.forEach(node => {
            if (node.nodeType !== 1) return;

            if (node.matches(FORM_SELECTOR)) {
              processForm(node);
            } else {
              const nestedForm = node.querySelector(FORM_SELECTOR);
              if (nestedForm) processForm(nestedForm);
            }
          });
        }
      }
    });

    widgetObserver.observe(widgetNode, {
      childList: true,
      subtree: true
    });
  };

  const globalObserver = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      if (mutation.addedNodes.length) {
        mutation.addedNodes.forEach(node => {
          if (node.nodeType !== 1) return;

          if (node.matches(WIDGET_WINDOW_SELECTOR)) {
            watchWidgetContent(node);
          } else if (node.querySelector(WIDGET_WINDOW_SELECTOR)) {
            watchWidgetContent(node.querySelector(WIDGET_WINDOW_SELECTOR));
          }
        });
      }
    }
  });

  const existingWidget = document.querySelector(WIDGET_WINDOW_SELECTOR);
  if (existingWidget) {
    watchWidgetContent(existingWidget);
  }

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


Please try it out and let me know if it’s working fine on your end now :wink: