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);
