dga_feedback-2.0.0/js/feedback.js

js/feedback.js
(function (Drupal, once) {
  "use strict";

  Drupal.behaviors.feedbackWidget = {
    attach(context, settings) {
      once("feedbackWidget", "[data-feedback-widget]", context).forEach((root) => {
        // Check if already initialized
        if (root.hasAttribute('data-fw-initialized')) {
          return;
        }
        root.setAttribute('data-fw-initialized', 'true');

        // Elements
        const actions = root.querySelector("[data-fw-actions]");
        const btnYes = root.querySelector("[data-fw-yes]");
        const btnNo = root.querySelector("[data-fw-no]");
        const stats = root.querySelector("[data-fw-stats]");
        const btnClose = root.querySelector("[data-fw-close]");
        const questions = root.querySelector("[data-fw-questions]");
        const reasonsWrap = root.querySelector("[data-fw-reasons]");
        const feedbackEl = root.querySelector("[data-fw-feedback]");
        const genderEls = root.querySelectorAll("[data-fw-gender]");
        const submitBtn = root.querySelector("[data-fw-submit]");
        const statusIcon = root.querySelector(".fw-status-icon");
        const statusText = root.querySelector(".fw-status-text");

        // Store the original translated question text if not already stored
        if (statusText && !statusText.getAttribute('data-original-question')) {
          statusText.setAttribute('data-original-question', statusText.textContent || (typeof Drupal !== 'undefined' ? Drupal.t('Was this page useful?') : 'Was this page useful?'));
        }

        // Get initial data from attributes
        const statsTuple = (root.getAttribute("data-initial-stats") || "").split("|");
        const initialYesPct = statsTuple[0] || "0";
        const initialCount = statsTuple[1] || "0";
        const url = root.getAttribute("data-url") || window.location.pathname;
        const entityType = root.getAttribute("data-entity-type") || "";
        const entityId = root.getAttribute("data-entity-id") || "";

        const settingsObj = (typeof drupalSettings !== 'undefined' && drupalSettings.dgaFeedback) ? drupalSettings.dgaFeedback : {};
        const validationMessages = {
          yesNo: root.getAttribute('data-validation-yes-no') || settingsObj.validationYesNo || 'Please select Yes or No first.',
          reasonRequired: root.getAttribute('data-validation-reason-required') || 'Please select at least one reason',
          reasonInvalid: root.getAttribute('data-validation-reason-invalid') || root.getAttribute('data-validation-reason-required') || 'At least one valid reason must be selected.',
          feedbackRequired: root.getAttribute('data-validation-feedback-required') || 'Please provide feedback text',
          genderRequired: root.getAttribute('data-validation-gender-required') || 'Please select your gender',
          submissionFailed: root.getAttribute('data-validation-submission-failed') || settingsObj.errorMessage || 'Submission failed. Please try again.',
          unknownError: root.getAttribute('data-validation-unknown-error') || settingsObj.unknownMessage || 'Unknown error',
        };
        const buttonSubmittingText = root.getAttribute('data-button-submitting') || settingsObj.buttonSubmittingText || 'Submitting...';
        const refreshDelay = typeof settingsObj.refreshDelay === 'number' ? settingsObj.refreshDelay : 3000;
        const refreshBlockUrl = settingsObj.refreshBlockUrl || '/dga-feedback/refresh-block';

        // Get reasons from data attributes (pipe-separated)
        const reasonsYesRaw = root.getAttribute("data-reasons-yes") || "";
        const reasonsNoRaw = root.getAttribute("data-reasons-no") || "";
        const OPTIONS = {
          yes: reasonsYesRaw ? reasonsYesRaw.split("|").map(r => r.trim()).filter(Boolean) : [
            "Content is relevant",
            "It was well written",
            "The layout made it easy to read",
            "Something else",
          ],
          no: reasonsNoRaw ? reasonsNoRaw.split("|").map(r => r.trim()).filter(Boolean) : [
            "Content is not relevant",
            "Content is not accurate",
            "Content is too long",
            "Something else",
          ],
        };

        // Update stats display
        const statsTemplate = stats ? (stats.getAttribute('data-template') || "@percentage% of users said Yes from @count Feedbacks") : "@percentage% of users said Yes from @count Feedbacks";

        function updateStatsText(percentage, count) {
          if (!stats) {
            return;
          }
          const template = stats.getAttribute('data-template') || statsTemplate;
          stats.textContent = template
            .replace("@percentage", percentage)
            .replace("@count", count);
        }

        // Initial stats display
        updateStatsText(initialYesPct, initialCount);

        // Render reasons checkboxes
        function renderReasons(container, list, selected) {
          if (!container) return;
          container.innerHTML = "";
          list.forEach((label, i) => {
            const id = `fw-reason-${i}-${label.replace(/\s+/g, "-").toLowerCase()}`;
            const wrapper = document.createElement("label");
            wrapper.className = "fw-checkbox";
            wrapper.setAttribute("for", id);

            const input = document.createElement("input");
            input.type = "checkbox";
            input.id = id;
            input.value = label;
            input.checked = selected.includes(label);
            input.addEventListener("change", () => {
              const idx = selected.indexOf(label);
              if (idx > -1) {
                selected.splice(idx, 1);
              } else {
                selected.push(label);
              }
            });

            const span = document.createElement("span");
            span.textContent = label;

            wrapper.appendChild(input);
            wrapper.appendChild(span);
            container.appendChild(wrapper);
          });
        }

        // State
        const state = {
          isUseful: null, // "yes" | "no" | null
          reasons: [],
          feedback: "",
          gender: "",
          open: false,
          submitted: false,
        };

        function setOpen(open) {
          state.open = open;
          if (questions) questions.hidden = !open || state.submitted;
          if (btnClose) btnClose.hidden = !open || state.submitted;
          // Show stats only when closed or after submission
          if (stats) stats.hidden = open && !state.submitted ? true : false;
          // IMPORTANT: Don't clear data-fw-useful attribute when closing
          // It needs to persist for submission to work
        }

        function setSubmitted(submitted) {
          state.submitted = submitted;
          if (actions) actions.hidden = submitted;
          if (questions) questions.hidden = true;
          if (btnClose) btnClose.hidden = true;
          if (stats) stats.hidden = false;
          if (statusText) {
            if (submitted) {
              const successText = (typeof drupalSettings !== 'undefined' && drupalSettings.dgaFeedback && drupalSettings.dgaFeedback.submittedSuccessText) 
                ? drupalSettings.dgaFeedback.submittedSuccessText 
                : (typeof Drupal !== 'undefined' ? Drupal.t('Your feedback is submitted!') : 'Your feedback is submitted!');
              statusText.textContent = successText;
            } else {
              // Get the original question text from the element (already translated from server)
              const originalQuestion = statusText.getAttribute('data-original-question') || statusText.textContent || (typeof Drupal !== 'undefined' ? Drupal.t('Was this page useful?') : 'Was this page useful?');
              statusText.textContent = originalQuestion;
            }
          }
          if (statusIcon) {
            statusIcon.hidden = !submitted;
          }
        }

        function mountReasons(kind) {
          state.reasons = []; // reset when toggling yes/no
          renderReasons(reasonsWrap, OPTIONS[kind], state.reasons);
        }

        // Events - ALL AJAX, no form submission
        if (btnYes) {
          btnYes.addEventListener("click", (e) => {
            e.preventDefault();
            e.stopPropagation();
            state.isUseful = "yes";
            root.setAttribute('data-fw-useful', 'yes'); // Store in widget for global handler
            mountReasons("yes");
            setOpen(true);
            // Clear any error styling when form opens
            if (feedbackEl) {
              feedbackEl.classList.remove('error');
              feedbackEl.removeAttribute('required');
            }
            const reasonsWrap = root.querySelector('[data-fw-reasons]');
            if (reasonsWrap) {
              reasonsWrap.classList.remove('error');
            }
            const genderWrap = root.querySelector('.fw-gender');
            if (genderWrap) {
              genderWrap.classList.remove('error');
            }
            const actions = root.querySelector('[data-fw-actions]');
            if (actions) {
              actions.classList.remove('error');
            }
          });
        }

        if (btnNo) {
          btnNo.addEventListener("click", (e) => {
            e.preventDefault();
            e.stopPropagation();
            state.isUseful = "no";
            root.setAttribute('data-fw-useful', 'no'); // Store in widget for global handler
            mountReasons("no");
            setOpen(true);
            // Clear any error styling when form opens
            if (feedbackEl) {
              feedbackEl.classList.remove('error');
              feedbackEl.removeAttribute('required');
            }
            const reasonsWrap = root.querySelector('[data-fw-reasons]');
            if (reasonsWrap) {
              reasonsWrap.classList.remove('error');
            }
            const genderWrap = root.querySelector('.fw-gender');
            if (genderWrap) {
              genderWrap.classList.remove('error');
            }
            const actions = root.querySelector('[data-fw-actions]');
            if (actions) {
              actions.classList.remove('error');
            }
          });
        }

        if (btnClose) {
          btnClose.addEventListener("click", (e) => {
            e.preventDefault();
            e.stopPropagation();
            // Don't clear the is_useful attribute when closing - keep it for submission
            // The attribute should remain set so submission can work
            setOpen(false);
          });
        }

        if (feedbackEl) {
          feedbackEl.addEventListener("input", (e) => {
            state.feedback = e.target.value;
            // Clear error styling when user starts typing
            if (e.target.value.trim().length > 0) {
              feedbackEl.classList.remove('error');
              const feedbackLabel = root.querySelector('label[for="fw-feedback"]');
              if (feedbackLabel) {
                const errorMsg = feedbackLabel.querySelector('.fw-error-message');
                if (errorMsg) errorMsg.remove();
              }
            }
          });
        }

        genderEls.forEach((el) => {
          el.addEventListener("change", (e) => {
            if (e.target.checked) {
              state.gender = e.target.value;
              // Clear error styling when user selects gender
              const genderWrap = root.querySelector('.fw-gender');
              if (genderWrap) {
                genderWrap.classList.remove('error');
                const genderTitle = genderWrap.querySelector('.fw-title-inline');
                if (genderTitle) {
                  const errorMsg = genderTitle.querySelector('.fw-error-message');
                  if (errorMsg) errorMsg.remove();
                }
              }
            }
          });
        });

        // Clear error styling when user checks a reason
        if (reasonsWrap) {
          reasonsWrap.addEventListener("change", (e) => {
            if (e.target.type === 'checkbox' && e.target.checked) {
              const reasonsWrapEl = e.target.closest('[data-fw-reasons]');
              if (reasonsWrapEl) {
                reasonsWrapEl.classList.remove('error');
                const reasonsTitle = reasonsWrapEl.parentElement.querySelector('.fw-title');
                if (reasonsTitle) {
                  const errorMsg = reasonsTitle.querySelector('.fw-error-message');
                  if (errorMsg) errorMsg.remove();
                }
              }
            }
          });
        }

        // Initial paint
        setOpen(false);
        setSubmitted(false);
      });
    },
  };

  // GLOBAL HANDLER - Direct button handler that works independently (like dga_rating)
  function attachGlobalSubmitHandler() {
    const submitButtons = document.querySelectorAll('[data-fw-submit]');

    submitButtons.forEach(function(btn) {
      // Skip if already attached or if button doesn't exist
      if (!btn || btn.hasAttribute('data-global-handler-attached')) {
        return;
      }

      // Ensure button is type="button" to prevent form submission
      if (btn.type !== 'button') {
        btn.type = 'button';
      }

      btn.setAttribute('data-global-handler-attached', 'true');

      btn.onclick = function(e) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();

        // Find widget root
        const widget = btn.closest('[data-feedback-widget]');
        if (!widget) {
          console.error('DGA Feedback: Widget not found');
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          return false;
        }

        // Get validation messages and settings from widget data attributes
        const settingsObj = (typeof drupalSettings !== 'undefined' && drupalSettings.dgaFeedback) ? drupalSettings.dgaFeedback : {};
        const validationMessages = {
          yesNo: widget.getAttribute('data-validation-yes-no') || settingsObj.validationYesNo || 'Please select Yes or No first.',
          reasonRequired: widget.getAttribute('data-validation-reason-required') || 'Please select at least one reason',
          reasonInvalid: widget.getAttribute('data-validation-reason-invalid') || widget.getAttribute('data-validation-reason-required') || 'At least one valid reason must be selected.',
          feedbackRequired: widget.getAttribute('data-validation-feedback-required') || 'Please provide feedback text',
          genderRequired: widget.getAttribute('data-validation-gender-required') || 'Please select your gender',
          submissionFailed: widget.getAttribute('data-validation-submission-failed') || settingsObj.errorMessage || 'Submission failed. Please try again.',
          unknownError: widget.getAttribute('data-validation-unknown-error') || settingsObj.unknownMessage || 'Unknown error',
        };
        const buttonSubmittingText = widget.getAttribute('data-button-submitting') || settingsObj.buttonSubmittingText || 'Submitting...';
        const refreshDelay = typeof settingsObj.refreshDelay === 'number' ? settingsObj.refreshDelay : 3000;
        const refreshBlockUrl = settingsObj.refreshBlockUrl || '/dga-feedback/refresh-block';

        // Store original button text for potential restoration
        const originalText = btn.textContent;
        
        // CRITICAL: Set a flag to prevent any other handlers from running
        if (btn.hasAttribute('data-submitting')) {
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          return false;
        }

        // CRITICAL: Ensure button is type="button" to prevent form submission
        if (btn.type !== 'button') {
          btn.type = 'button';
        }

        // STEP 1: Validate is_useful (Yes/No selection)
        // First, try to get from data attribute (set when Yes/No button is clicked)
        // This attribute should persist even after closing the form
        let isUseful = widget.getAttribute('data-fw-useful');

        // If attribute not found, try to infer from the current state
        // This can happen if the global handler runs before behaviors, or if form was closed
        if (!isUseful || (isUseful !== 'yes' && isUseful !== 'no')) {
          // Method 1: Check if questions are visible (form is open) and infer from reasons
          const questions = widget.querySelector('[data-fw-questions]');
          if (questions && !questions.hidden) {
            const firstReason = widget.querySelector('[data-fw-reasons] label:first-child span');
            if (firstReason) {
              const firstReasonText = firstReason.textContent.trim();
              const yesReasons = widget.getAttribute('data-reasons-yes') || '';
              const noReasons = widget.getAttribute('data-reasons-no') || '';

              if (yesReasons.includes(firstReasonText)) {
                isUseful = 'yes';
                widget.setAttribute('data-fw-useful', 'yes');
              } else if (noReasons.includes(firstReasonText)) {
                isUseful = 'no';
                widget.setAttribute('data-fw-useful', 'no');
              }
            }
          }

          // Method 2: Check which checkboxes exist even if hidden (Yes reasons vs No reasons)
          if (!isUseful || (isUseful !== 'yes' && isUseful !== 'no')) {
            const allReasonLabels = widget.querySelectorAll('[data-fw-reasons] label span');
            if (allReasonLabels.length > 0) {
              const firstLabelText = allReasonLabels[0].textContent.trim();
              const yesReasons = widget.getAttribute('data-reasons-yes') || '';
              const noReasons = widget.getAttribute('data-reasons-no') || '';

              if (yesReasons.includes(firstLabelText)) {
                isUseful = 'yes';
                widget.setAttribute('data-fw-useful', 'yes');
              } else if (noReasons.includes(firstLabelText)) {
                isUseful = 'no';
                widget.setAttribute('data-fw-useful', 'no');
              }
            }
          }

          // Method 3: Check if actions are hidden (means Yes/No was clicked, form might be closed now)
          if (!isUseful || (isUseful !== 'yes' && isUseful !== 'no')) {
            const actions = widget.querySelector('[data-fw-actions]');
            if (actions && actions.hidden) {
              // Actions are hidden, which means Yes/No was clicked at some point
              // Check reasons to determine which (even if questions are hidden now)
              const firstReason = widget.querySelector('[data-fw-reasons] label:first-child span');
              if (firstReason) {
                const firstReasonText = firstReason.textContent.trim();
                const yesReasons = widget.getAttribute('data-reasons-yes') || '';
                const noReasons = widget.getAttribute('data-reasons-no') || '';

                if (yesReasons.includes(firstReasonText)) {
                  isUseful = 'yes';
                  widget.setAttribute('data-fw-useful', 'yes');
                } else if (noReasons.includes(firstReasonText)) {
                  isUseful = 'no';
                  widget.setAttribute('data-fw-useful', 'no');
                }
              }
            }
          }
        }

        // STEP 1: Final validation - if still not found, don't submit
        if (!isUseful || (isUseful !== 'yes' && isUseful !== 'no')) {
          // CRITICAL: Stop ALL event propagation immediately - MUST BE FIRST
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          
          // CRITICAL: Ensure button cannot submit
          btn.type = 'button';
          btn.disabled = false;
          
          // Show Yes/No buttons and focus on them
          const actions = widget.querySelector('[data-fw-actions]');
          if (actions) {
            actions.hidden = false;
            actions.classList.add('error');
            // Scroll to actions
            actions.scrollIntoView({ behavior: 'smooth', block: 'center' });
          }
          
          // Show error message
          const statusText = widget.querySelector('.fw-status-text');
          if (statusText) {
            statusText.textContent = validationMessages.yesNo;
            statusText.style.color = '#dc3545';
            statusText.style.fontWeight = 'bold';
          }
          
          // Ensure questions form is visible if it exists
          const questions = widget.querySelector('[data-fw-questions]');
          if (questions) {
            questions.hidden = false;
          }
          
          return false;
        }

        // STEP 2: Validate reasons (at least one checkbox must be selected)
        // IMPORTANT: Check ALL checkboxes, even if form is closed (hidden)
        const reasonCheckboxes = widget.querySelectorAll('[data-fw-reasons] input[type="checkbox"]:checked');
        const reasons = Array.from(reasonCheckboxes).map(cb => cb.value);

        if (reasons.length === 0) {

          // CRITICAL: Stop ALL event propagation immediately - MUST BE FIRST
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();

          // CRITICAL: Ensure button cannot submit
          btn.type = 'button';
          btn.disabled = false; // Re-enable in case it was disabled

          // CRITICAL: Show questions form if hidden (user might have closed it)
          const questions = widget.querySelector('[data-fw-questions]');
          if (questions && questions.hidden) {
            questions.hidden = false;
          }

          const reasonsWrap = widget.querySelector('[data-fw-reasons]');
          if (reasonsWrap) {
            reasonsWrap.classList.add('error');
            // Scroll to reasons
            reasonsWrap.scrollIntoView({ behavior: 'smooth', block: 'center' });
            // Focus on first checkbox
            const firstCheckbox = reasonsWrap.querySelector('input[type="checkbox"]');
            if (firstCheckbox) {
              firstCheckbox.focus();
            }
            // Show error message in reasons title
            const reasonsTitle = reasonsWrap.parentElement.querySelector('.fw-title');
            if (reasonsTitle) {
              const errorSpan = document.createElement('span');
              errorSpan.textContent = ' - ' + validationMessages.reasonRequired;
              errorSpan.className = 'fw-error-message';
              // Remove existing error if any
              const existingError = reasonsTitle.querySelector('.fw-error-message');
              if (existingError) existingError.remove();
              reasonsTitle.appendChild(errorSpan);
            }
          }

          // CRITICAL: Return false to prevent any further execution
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          return false;
        }

        // STEP 3: Validate feedback text (must not be empty)
        const feedbackEl = widget.querySelector('[data-fw-feedback]');
        const feedback = feedbackEl ? feedbackEl.value.trim() : '';

        if (!feedback || feedback.length === 0) {
          // CRITICAL: Stop ALL event propagation immediately - MUST BE FIRST
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();

          // CRITICAL: Ensure button cannot submit
          btn.type = 'button';
          btn.disabled = false;

          // CRITICAL: Show questions form if hidden
          const questions = widget.querySelector('[data-fw-questions]');
          if (questions && questions.hidden) {
            questions.hidden = false;
          }

          if (feedbackEl) {
            feedbackEl.classList.add('error');
            feedbackEl.focus();
            feedbackEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
            // Show error message in label
            const feedbackLabel = widget.querySelector('label[for="fw-feedback"]');
            if (feedbackLabel) {
              const errorSpan = document.createElement('span');
              errorSpan.textContent = ' - ' + validationMessages.feedbackRequired;
              errorSpan.className = 'fw-error-message';
              // Remove existing error if any
              const existingError = feedbackLabel.querySelector('.fw-error-message');
              if (existingError) existingError.remove();
              feedbackLabel.appendChild(errorSpan);
            }
          }

          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          return false;
        }

        // STEP 4: Validate gender (must be selected)
        const genderRadio = widget.querySelector('[data-fw-gender]:checked');
        const gender = genderRadio ? genderRadio.value : '';

        if (!gender || (gender !== 'male' && gender !== 'female')) {
          // CRITICAL: Stop ALL event propagation immediately - MUST BE FIRST
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();

          // CRITICAL: Ensure button cannot submit
          btn.type = 'button';
          btn.disabled = false;

          // CRITICAL: Show questions form if hidden
          const questions = widget.querySelector('[data-fw-questions]');
          if (questions && questions.hidden) {
            questions.hidden = false;
          }

          const genderWrap = widget.querySelector('.fw-gender');
          if (genderWrap) {
            genderWrap.classList.add('error');
            genderWrap.scrollIntoView({ behavior: 'smooth', block: 'center' });
            // Focus on first gender radio
            const genderEls = widget.querySelectorAll('[data-fw-gender]');
            if (genderEls.length > 0) {
              genderEls[0].focus();
            }
            // Show error message in gender title
            const genderTitle = genderWrap.querySelector('.fw-title-inline');
            if (genderTitle) {
              const errorSpan = document.createElement('span');
              errorSpan.textContent = ' - ' + validationMessages.genderRequired;
              errorSpan.className = 'fw-error-message';
              // Remove existing error if any
              const existingError = genderTitle.querySelector('.fw-error-message');
              if (existingError) existingError.remove();
              genderTitle.appendChild(errorSpan);
            }
          }

          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          return false;
        }

        // ALL VALIDATIONS PASSED - Clear error styles

        // Clear any error styles and messages before submission
        const reasonsWrap = widget.querySelector('[data-fw-reasons]');
        if (reasonsWrap) {
          reasonsWrap.classList.remove('error');
          const reasonsTitle = reasonsWrap.parentElement.querySelector('.fw-title');
          if (reasonsTitle) {
            const errorMsg = reasonsTitle.querySelector('.fw-error-message');
            if (errorMsg) errorMsg.remove();
          }
        }
        if (feedbackEl) {
          feedbackEl.classList.remove('error');
          const feedbackLabel = widget.querySelector('label[for="fw-feedback"]');
          if (feedbackLabel) {
            const errorMsg = feedbackLabel.querySelector('.fw-error-message');
            if (errorMsg) errorMsg.remove();
          }
        }
        const genderWrap = widget.querySelector('.fw-gender');
        if (genderWrap) {
          genderWrap.classList.remove('error');
          const genderTitle = genderWrap.querySelector('.fw-title-inline');
          if (genderTitle) {
            const errorMsg = genderTitle.querySelector('.fw-error-message');
            if (errorMsg) errorMsg.remove();
          }
        }
        const actions = widget.querySelector('[data-fw-actions]');
        if (actions) {
          actions.classList.remove('error');
        }

        // Get URL
        let url = widget.getAttribute('data-url') || window.location.pathname;
        url = url.trim().replace(/\/+$/, '') || '/';
        if (url.match(/^\/[a-z]{2}\//)) {
          url = url.replace(/^\/[a-z]{2}/, '');
          url = url || '/';
        }

        // Get entity info
        const entityType = widget.getAttribute('data-entity-type') || 'node';
        const entityId = widget.getAttribute('data-entity-id') || '0';

        // Validate is_useful one more time before proceeding
        if (!isUseful || (isUseful !== 'yes' && isUseful !== 'no')) {
          btn.removeAttribute('data-submitting');
          btn.disabled = false;
          btn.textContent = originalText;
          return false;
        }

        // Disable button - only after all validations pass
        btn.setAttribute('data-submitting', 'true');
        btn.disabled = true;
        btn.type = 'button'; // Ensure button type is set (prevents form submission)
        btn.textContent = buttonSubmittingText;

        // Prepare payload - ALL AJAX, NO FORM SUBMISSION
        const payload = {
          is_useful: isUseful, // This should be 'yes' or 'no' from data-fw-useful attribute
          reasons: reasons,
          feedback: feedback || '',
          gender: gender || '',
          url: url || '/',
          entity_type: entityType,
          entity_id: parseInt(entityId) || 0
        };

        // CRITICAL: Send request using synchronous XMLHttpRequest (AJAX only, like dga_rating)
        // This is AJAX - no page reload, no form submission
        const xhr = new XMLHttpRequest();
        xhr.open('POST', '/dga-feedback/submit', false); // false = synchronous, AJAX
        xhr.setRequestHeader('Content-Type', 'application/json');

        try {
          xhr.send(JSON.stringify(payload));

          let data;
          try {
            data = JSON.parse(xhr.responseText);
          } catch (e) {
            btn.removeAttribute('data-submitting');
            btn.disabled = false;
            btn.textContent = originalText;
            const statusText = widget.querySelector('.fw-status-text');
            if (statusText) {
            statusText.textContent = validationMessages.submissionFailed;
              statusText.style.color = '#dc3545';
            }
            return false;
          }

          // Validate response
          if (xhr.status === 200 && data && data.success === true && data.feedback_id) {
            // Clear any error styles and messages
            const reasonsWrap = widget.querySelector('[data-fw-reasons]');
            if (reasonsWrap) {
              reasonsWrap.classList.remove('error');
              const reasonsTitle = reasonsWrap.parentElement.querySelector('.fw-title');
              if (reasonsTitle) {
                const errorMsg = reasonsTitle.querySelector('.fw-error-message');
                if (errorMsg) errorMsg.remove();
              }
            }
            const feedbackEl = widget.querySelector('[data-fw-feedback]');
            if (feedbackEl) {
              feedbackEl.classList.remove('error');
              const feedbackLabel = widget.querySelector('label[for="fw-feedback"]');
              if (feedbackLabel) {
                const errorMsg = feedbackLabel.querySelector('.fw-error-message');
                if (errorMsg) errorMsg.remove();
              }
            }
            const genderWrap = widget.querySelector('.fw-gender');
            if (genderWrap) {
              genderWrap.classList.remove('error');
              const genderTitle = genderWrap.querySelector('.fw-title-inline');
              if (genderTitle) {
                const errorMsg = genderTitle.querySelector('.fw-error-message');
                if (errorMsg) errorMsg.remove();
              }
            }
            // Update stats if provided
            if (data.statistics) {
              const newPercentage = Math.round(data.statistics.yes_percentage || 0);
              const newCount = data.statistics.total_count || 0;
              // Update stats display directly
              const stats = widget.querySelector('[data-fw-stats]');
              if (stats) {
                const statsTemplate = stats.getAttribute('data-template') || "@percentage% of users said Yes from @count Feedbacks";
                stats.textContent = statsTemplate
                  .replace("@percentage", newPercentage)
                  .replace("@count", newCount);
              }
            }

            // Update widget UI
            const actions = widget.querySelector('[data-fw-actions]');
            const questions = widget.querySelector('[data-fw-questions]');
            const btnClose = widget.querySelector('[data-fw-close]');
            const statusIcon = widget.querySelector('.fw-status-icon');
            const statusText = widget.querySelector('.fw-status-text');

            if (actions) actions.hidden = true;
            if (questions) questions.hidden = true;
            if (btnClose) btnClose.hidden = true;
            if (statusIcon) statusIcon.hidden = false;
            if (statusText) {
              const successText = (typeof drupalSettings !== 'undefined' && drupalSettings.dgaFeedback && drupalSettings.dgaFeedback.submittedSuccessText) 
                ? drupalSettings.dgaFeedback.submittedSuccessText 
                : (typeof Drupal !== 'undefined' ? Drupal.t('Your feedback is submitted!') : 'Your feedback is submitted!');
              statusText.textContent = successText;
            }

            setTimeout(() => {
              let widgetUrl = widget.getAttribute('data-url') || window.location.pathname;
              widgetUrl = widgetUrl.trim().replace(/\/+$/, '') || '/';
              if (widgetUrl.match(/^\/[a-z]{2}\//)) {
                widgetUrl = widgetUrl.replace(/^\/[a-z]{2}/, '');
                widgetUrl = widgetUrl || '/';
              }

              let refreshUrl = refreshBlockUrl + '?url=' + encodeURIComponent(widgetUrl);
              if (entityType) {
                refreshUrl += '&entity_type=' + encodeURIComponent(entityType);
              }
              if (entityId) {
                refreshUrl += '&entity_id=' + encodeURIComponent(entityId);
              }

              fetch(refreshUrl, {
                method: 'GET',
                headers: {
                  'Accept': 'application/json',
                },
              })
                .then(response => response.json())
                .then(refreshData => {
                  if (refreshData.success && refreshData.statistics) {
                    const refreshedPercentage = Math.round(refreshData.statistics.yes_percentage || 0);
                    const refreshedCount = refreshData.statistics.total_count || 0;
                    // Update stats display directly
                    const stats = widget.querySelector('[data-fw-stats]');
                    if (stats) {
                      const statsTemplate = stats.getAttribute('data-template') || "@percentage% of users said Yes from @count Feedbacks";
                      stats.textContent = statsTemplate
                        .replace("@percentage", refreshedPercentage)
                        .replace("@count", refreshedCount);
                    }
                  }

                  widget.removeAttribute('data-fw-useful');
                  
                  const reasonsWrapEl = widget.querySelector('[data-fw-reasons]');
                  const feedbackEl = widget.querySelector('[data-fw-feedback]');
                  const genderEls = widget.querySelectorAll('[data-fw-gender]');
                  
                  if (reasonsWrapEl) {
                    reasonsWrapEl.innerHTML = '';
                  }
                  if (feedbackEl) {
                    feedbackEl.value = '';
                  }
                  genderEls.forEach((el) => {
                    el.checked = false;
                  });

                  // Get elements from widget
                  const actions = widget.querySelector('[data-fw-actions]');
                  const questions = widget.querySelector('[data-fw-questions]');
                  const btnClose = widget.querySelector('[data-fw-close]');
                  const statusIcon = widget.querySelector('.fw-status-icon');
                  const statusText = widget.querySelector('.fw-status-text');
                  
                  if (actions) actions.hidden = false;
                  if (questions) questions.hidden = true;
                  if (btnClose) btnClose.hidden = true;
                  if (statusIcon) statusIcon.hidden = true;
                  if (statusText) {
                    const originalQuestion = statusText.getAttribute('data-original-question') || statusText.textContent || '';
                    statusText.textContent = originalQuestion;
                    statusText.style.color = '';
                    statusText.style.fontWeight = '';
                  }
                  
                  // Reset widget to closed state
                  if (questions) questions.hidden = true;
                  if (actions) actions.hidden = false;
                  if (statusIcon) statusIcon.hidden = true;
                })
                .catch(() => {
                  widget.removeAttribute('data-fw-useful');
                  const actions = widget.querySelector('[data-fw-actions]');
                  const questions = widget.querySelector('[data-fw-questions]');
                  const btnClose = widget.querySelector('[data-fw-close]');
                  const statusIcon = widget.querySelector('.fw-status-icon');
                  const statusText = widget.querySelector('.fw-status-text');
                  
                  if (actions) actions.hidden = false;
                  if (questions) questions.hidden = true;
                  if (btnClose) btnClose.hidden = true;
                  if (statusIcon) statusIcon.hidden = true;
                  if (statusText) {
                    const originalQuestion = statusText.getAttribute('data-original-question') || statusText.textContent || '';
                    statusText.textContent = originalQuestion;
                    statusText.style.color = '';
                    statusText.style.fontWeight = '';
                  }
                  
                  const feedbackEl = widget.querySelector('[data-fw-feedback]');
                  const genderEls = widget.querySelectorAll('[data-fw-gender]');
                  const reasonsWrap = widget.querySelector('[data-fw-reasons]');
                  
                  if (feedbackEl) {
                    feedbackEl.value = '';
                  }
                  genderEls.forEach((el) => {
                    el.checked = false;
                  });
                  if (reasonsWrap) {
                    reasonsWrap.innerHTML = '';
                  }
                  
                  // Reset widget to closed state
                  if (questions) questions.hidden = true;
                  if (actions) actions.hidden = false;
                  if (statusIcon) statusIcon.hidden = true;
                })
                .finally(() => {
                  btn.removeAttribute('data-submitting');
                  btn.disabled = false;
                  btn.textContent = originalText;
                });
            }, refreshDelay);

            return false;
          } else {
            // Show error inline
            const errorMsg = (data && data.message) ? data.message : validationMessages.unknownError;
            const statusText = widget.querySelector('.fw-status-text');
            if (statusText) {
              const originalStatusText = statusText.textContent;
              statusText.textContent = errorMsg;
              statusText.style.color = '#dc3545';
              setTimeout(() => {
                statusText.textContent = originalStatusText;
                statusText.style.color = '';
              }, 5000);
            }
            btn.removeAttribute('data-submitting');
            btn.disabled = false;
            btn.textContent = originalText;
          }
        } catch (err) {
          // Show error inline
          const statusText = widget.querySelector('.fw-status-text');
          if (statusText) {
            statusText.textContent = validationMessages.submissionFailed;
            statusText.style.color = '#dc3545';
          }
          btn.removeAttribute('data-submitting');
          btn.disabled = false;
          btn.textContent = originalText;
        }

        // Always remove submitting flag and return false
        btn.removeAttribute('data-submitting');
        return false;
      };
    });
  }

  // Attach on DOM ready and once after delay
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', attachGlobalSubmitHandler);
  } else {
    attachGlobalSubmitHandler();
  }

  // Single delayed attach as fallback
  setTimeout(attachGlobalSubmitHandler, 1000);

})(Drupal, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc