first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
@@ -0,0 +1,595 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
const interactions = registry.category("public.interactions");
function applyPatchTo(SurveyForm) {
// Helper to initialize address and name fields using jQuery scoped to this.el
function _initializeAddressFields() {
const $root = $(this.el);
$root.find('[data-question-type="address"]').each(function () {
const $hiddenInput = $(this);
const existingData = $hiddenInput.val();
if (existingData) {
try {
const addressData = JSON.parse(existingData);
const $container = $hiddenInput.closest('.o_survey_answer_wrapper').find('.address-fields');
$container.find('.address-street').val(addressData.street || '');
$container.find('.address-street2').val(addressData.street2 || '');
$container.find('.address-zip').val(addressData.zip || '');
$container.find('.address-city').val(addressData.city || '');
$container.find('.address-state').val(addressData.state || '');
$container.find('.address-country').val(addressData.country || '');
} catch (e) {
console.error('Error parsing address data:', e);
}
}
});
$root.find('[data-question-type="name"]').each(function () {
const $hiddenInput = $(this);
const existingData = $hiddenInput.val();
if (existingData) {
try {
const nameData = JSON.parse(existingData);
const $container = $hiddenInput.closest('.o_survey_answer_wrapper').find('.name-fields');
$container.find('.name-first').val(nameData.first_name || '');
$container.find('.name-middle').val(nameData.middle_name || '');
$container.find('.name-last').val(nameData.last_name || '');
} catch (e) {
console.error('Error parsing name data:', e);
}
}
});
$root.find('.o_survey_question_many2many').off('change.custom_many2many').on('change.custom_many2many', function() {
const selectedIds = Array.from(this.selectedOptions).map(option => option.value).filter(id => id);
$(this).siblings('.many2many-data').val(selectedIds.join(','));
});
}
function displayErrors(ctx, errors) {
// Prefer built-in methods if present (showErrors or _showErrors)
if (typeof ctx.showErrors === 'function') {
ctx.showErrors(errors);
} else if (typeof ctx._showErrors === 'function') {
ctx._showErrors(errors);
} else {
// fallback: simple inline display or alert
// Try to mark elements with error class if possible
try {
Object.keys(errors).forEach(function (qid) {
const msg = errors[qid];
const $el = $('#' + qid);
if ($el.length) {
$el.addClass('o_survey_error');
// append small error block if not present
if ($el.find('.o_survey_inline_error').length === 0) {
$el.append($('<div class="o_survey_inline_error"/>').text(msg));
} else {
$el.find('.o_survey_inline_error').text(msg);
}
}
});
} catch (e) {
// last resort
alert(Object.values(errors).join("\n"));
}
}
}
// Wrap start
const _origStart = SurveyForm.prototype.start;
SurveyForm.prototype.start = function () {
const res = _origStart && _origStart.apply(this, arguments);
try {
_initializeAddressFields.call(this);
} catch (e) {
console.error('Error in custom survey start initialiser:', e);
}
return res;
};
// Wrap prepareSubmitValues
const _origPrepare = SurveyForm.prototype.prepareSubmitValues;
SurveyForm.prototype.prepareSubmitValues = function (formData, params) {
_origPrepare && _origPrepare.call(this, formData, params);
const $root = $(this.el);
$root.find('[data-question-type="color"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="email"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="url"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="time"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="range"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="week"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="password"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="signature"]').each(function () {
const $hiddenInput = $(this);
const signatureData = $hiddenInput.val();
if (signatureData && signatureData.startsWith('data:image/')) {
params[this.name] = signatureData;
}
});
$root.find('[data-question-type="month"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="address"]').each(function () {
const $hiddenInput = $(this);
const $addressContainer = $hiddenInput.closest('.o_survey_answer_wrapper').find('.address-fields');
const addressData = {
street: $addressContainer.find('.address-street').val() || '',
street2: $addressContainer.find('.address-street2').val() || '',
zip: $addressContainer.find('.address-zip').val() || '',
city: $addressContainer.find('.address-city').val() || '',
state: $addressContainer.find('.address-state').val() || '',
country: $addressContainer.find('.address-country').val() || ''
};
params[this.name] = JSON.stringify(addressData);
});
$root.find('[data-question-type="name"]').each(function () {
const $hiddenInput = $(this);
const $nameContainer = $hiddenInput.closest('.o_survey_answer_wrapper').find('.name-fields');
const nameData = {
first_name: $nameContainer.find('.name-first').val() || '',
middle_name: $nameContainer.find('.name-middle').val() || '',
last_name: $nameContainer.find('.name-last').val() || ''
};
params[this.name] = JSON.stringify(nameData);
});
$root.find('[data-question-type="many2one"]').each(function () { params[this.name] = this.value; });
$root.find('[data-question-type="many2many"]').each(function () {
const selectedIds = Array.from(this.selectedOptions).map(option => option.value).filter(id => id);
params[this.name] = selectedIds.join(',');
});
$root.find('[data-question-type="file"]').each(function () {
const $input = $(this);
const files = $input[0].files;
if (files && files.length > 0) {
const fd = new FormData();
fd.append('file', files[0]);
// Keep synchronous ajax for parity with original behaviour
$.ajax({
url: '/survey/upload_file',
type: 'POST',
data: fd,
processData: false,
contentType: false,
async: false
}).done(function(response) {
let result;
try {
result = JSON.parse(response);
} catch (e) {
result = response;
}
if (result && result.attachment_id) {
// original code used data-question-id to set
params[$input.data('question-id')] = result.attachment_id;
}
}).fail(function (jqXHR, status, err) {
console.error('File upload failed:', status, err);
});
}
});
return params;
};
// Wrap validateForm
const _origValidate = SurveyForm.prototype.validateForm;
SurveyForm.prototype.validateForm = function (formEl, formData) {
const origResult = _origValidate && _origValidate.call(this, formEl, formData);
// If original validation failed, propagate false (keep original behavior)
if (origResult === false) {
return false;
}
const $form = $(formEl);
const errors = {};
// Color fields
$form.find('[data-question-type="color"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
if (questionRequired && !$input.val()) {
errors[questionId] = 'Please select a color.';
var customErrorMsg = $questionWrapper.data('required-error') || "Please select a color.";
errors[questionId] = customErrorMsg;
}
});
// Email fields
$form.find('[data-question-type="email"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const value = ($input.val() || '').trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (questionRequired && !value) {
errors[questionId] = 'Please enter an email address.';
var customErrorMsg = $questionWrapper.data('required-error') || "Please enter an email address.";
errors[questionId] = customErrorMsg;
} else if (value && !emailRegex.test(value)) {
errors[questionId] = 'Please enter a valid email address (e.g., user@example.com).';
}
});
// URL fields
$form.find('[data-question-type="url"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const value = ($input.val() || '').trim();
const urlRegex = /^https?:\/\/[^\s]+$/;
if (questionRequired && !value) {
errors[questionId] = 'Please enter a URL.';
var customErrorMsg = $questionWrapper.data('required-error') || "Please enter a URL.";
errors[questionId] = customErrorMsg;
} else if (value && !urlRegex.test(value)) {
errors[questionId] = 'Please enter a valid URL starting with http:// or https:// (e.g., https://example.com).';
}
});
// Time fields with min/max/step
$form.find('[data-question-type="time"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const value = $input.val();
const validateTime = $input.data('validate-time');
const step = parseInt($input.data('time-step'));
const min = $input.data('time-min');
const max = $input.data('time-max');
if (questionRequired && !value) {
errors[questionId] = "Please select a time.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select a time.";
errors[questionId] = customErrorMsg;
return;
}
if (value && validateTime) {
const timeParts = value.split(':');
if (timeParts.length !== 2) {
errors[questionId] = "Please enter a valid time format (HH:MM).";
return;
}
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
let minParts, maxParts;
if (min) {
minParts = min.split(':');
if (hours < parseInt(minParts[0], 10) || (hours === parseInt(minParts[0], 10) && minutes < parseInt(minParts[1], 10))) {
errors[questionId] = "Time must be after " + min + ".";
return;
}
}
if (max) {
maxParts = max.split(':');
if (hours > parseInt(maxParts[0], 10) || (hours === parseInt(maxParts[0], 10) && minutes > parseInt(maxParts[1], 10))) {
errors[questionId] = "Time must be before " + max + ".";
return;
}
}
if (step && min) {
const minTime = parseInt(minParts[0], 10) * 60 + parseInt(minParts[1], 10);
const valueTime = hours * 60 + minutes;
if ((valueTime - minTime) % step !== 0) {
errors[questionId] = "Please select time in " + step + " minute intervals from " + min + ".";
return;
}
}
}
});
// Range fields
$form.find('[data-question-type="range"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const validateRange = $questionWrapper.data('validateRange');
const val = parseFloat($input.val());
const min = parseFloat($input.attr('min'));
const max = parseFloat($input.attr('max'));
const step = parseFloat($input.attr('step') || 1);
if (questionRequired && !$input.val()) {
errors[questionId] = "Please select a value.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select a value.";
errors[questionId] = customErrorMsg;
} else if (validateRange && $input.val()) {
if (!isNaN(min) && val < min || !isNaN(max) && val > max) {
errors[questionId] = "Value must be between " + min + " and " + max + ".";
} else {
// step check (allow float rounding)
if (step && !isNaN(min)) {
const diff = (val - min) / step;
const near = Math.round(diff);
const eps = 1e-9;
if (Math.abs(diff - near) > eps) {
errors[questionId] = "Value must be in steps of " + step + " from " + min + ".";
}
}
}
}
});
// Week field validation
$form.find('[data-question-type="week"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const minWeek = $input.data('weekMin');
const maxWeek = $input.data('weekMax');
const step = parseInt($input.data('weekStep') || 1, 10);
const value = ($input.val() || '').trim();
if (questionRequired && !value) {
errors[questionId] = "Please select a week.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select a week.";
errors[questionId] = customErrorMsg;
return;
}
if (value && minWeek && maxWeek) {
const valParts = value.split('-W');
const minParts = minWeek.split('-W');
const maxParts = maxWeek.split('-W');
const valYear = parseInt(valParts[0], 10);
const valWeek = parseInt(valParts[1], 10);
const minYear = parseInt(minParts[0], 10);
const minWeekNum = parseInt(minParts[1], 10);
const maxYear = parseInt(maxParts[0], 10);
const maxWeekNum = parseInt(maxParts[1], 10);
if (valYear < minYear || (valYear === minYear && valWeek < minWeekNum) ||
valYear > maxYear || (valYear === maxYear && valWeek > maxWeekNum)) {
errors[questionId] = "Please select a week between " + minWeek + " and " + maxWeek + ".";
return;
}
if (step > 1) {
const diffWeeks = (valYear - minYear) * 52 + (valWeek - minWeekNum);
if (diffWeeks % step !== 0) {
errors[questionId] = "Please select a week in steps of " + step + " from " + minWeek + ".";
return;
}
}
}
});
// Password fields
$form.find('[data-question-type="password"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const required = $questionWrapper.data('required');
const validate = $input.data('validate-password');
const minLength = parseInt($input.data('password-min') || 0, 10);
const maxLength = parseInt($input.data('password-max') || 4096, 10);
const val = $input.val() || '';
if (required && !val) {
errors[questionId] = "Please enter a password.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please enter a password.";
errors[questionId] = customErrorMsg;
} else if (validate && val) {
if (val.length < minLength) {
errors[questionId] = "Password must be at least " + minLength + " characters long.";
} else if (val.length > maxLength) {
errors[questionId] = "Password cannot exceed " + maxLength + " characters.";
}
}
});
// File fields validation (size & allowed types)
$form.find('[data-question-type="file"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const maxSize = parseFloat($input.data('max-size')) || 10; // MB
const allowedTypes = $input.data('allowed-types'); // comma separated exts
const files = $input[0].files;
if (questionRequired && (!files || files.length === 0)) {
errors[questionId] = "Please select a file.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select a file.";
errors[questionId] = customErrorMsg;
return;
}
if (files && files.length > 0) {
const file = files[0];
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB > maxSize) {
errors[questionId] = "File size must not exceed " + maxSize + " MB.";
return;
}
if (allowedTypes) {
const fileExt = (file.name.split('.').pop() || '').toLowerCase();
const allowed = allowedTypes.toLowerCase().split(',').map(s => s.trim());
if (!allowed.includes(fileExt)) {
errors[questionId] = "Only " + allowedTypes + " files are allowed.";
return;
}
}
}
});
// Month fields
$form.find('[data-question-type="month"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const validateEntry = $input.data('validate-month-entry');
const minMonth = $input.data('month-min');
const maxMonth = $input.data('month-max');
const step = parseInt($input.data('month-step') || 1, 10);
const value = ($input.val() || '').trim();
if (questionRequired && !value) {
errors[questionId] = "Please select a month.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select a month.";
errors[questionId] = customErrorMsg;
return;
}
if (value && validateEntry) {
const monthRegex = /^\d{4}-\d{2}$/;
if (!monthRegex.test(value)) {
errors[questionId] = "Please enter a valid month in YYYY-MM format.";
return;
}
if (minMonth && value < minMonth) {
errors[questionId] = "Please select a month after " + minMonth + ".";
return;
}
if (maxMonth && value > maxMonth) {
errors[questionId] = "Please select a month before " + maxMonth + ".";
return;
}
if (step > 1 && minMonth) {
const minParts = minMonth.split('-');
const valParts = value.split('-');
const minYear = parseInt(minParts[0], 10);
const minMonthNum = parseInt(minParts[1], 10);
const valYear = parseInt(valParts[0], 10);
const valMonthNum = parseInt(valParts[1], 10);
const monthsFromMin = (valYear - minYear) * 12 + (valMonthNum - minMonthNum);
if (monthsFromMin % step !== 0) {
errors[questionId] = "Please select a month in steps of " + step + " from " + minMonth + ".";
return;
}
}
}
});
// Address fields required check (at least one non-empty)
$form.find('[data-question-type="address"]').each(function () {
const $hiddenInput = $(this);
const $questionWrapper = $hiddenInput.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const $addressContainer = $hiddenInput.closest('.o_survey_answer_wrapper').find('.address-fields');
if (questionRequired) {
let hasValue = false;
$addressContainer.find('input[type="text"]').each(function() {
if ($(this).val().trim()) {
hasValue = true;
return false;
}
});
if (!hasValue) {
errors[questionId] = "At least one address field must be filled.";
var customErrorMsg = $questionWrapper.data('required-error') || "At least one address field must be filled.";
errors[questionId] = customErrorMsg;
}
}
});
// Name fields required check (first & last, middle optional)
$form.find('[data-question-type="name"]').each(function () {
const $hiddenInput = $(this);
const $questionWrapper = $hiddenInput.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const middleOptional = $hiddenInput.data('middle-optional');
const $nameContainer = $hiddenInput.closest('.o_survey_answer_wrapper').find('.name-fields');
if (questionRequired) {
const firstName = ($nameContainer.find('.name-first').val() || '').trim();
const lastName = ($nameContainer.find('.name-last').val() || '').trim();
const middleName = ($nameContainer.find('.name-middle').val() || '').trim();
if (!firstName) {
errors[questionId] = "First name is required.";
var customErrorMsg = $questionWrapper.data('required-error') || "First name is required.";
errors[questionId] = customErrorMsg;
} else if (!lastName) {
errors[questionId] = "Last name is required.";
var customErrorMsg = $questionWrapper.data('required-error') || "Last name is required.";
errors[questionId] = customErrorMsg;
} else if (!middleOptional && !middleName) {
errors[questionId] = "Middle name is required.";
var customErrorMsg = $questionWrapper.data('required-error') || "Middle name is required.";
errors[questionId] = customErrorMsg;
}
}
});
// many2one required
$form.find('[data-question-type="many2one"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
if (questionRequired && !$input.val()) {
errors[questionId] = "Please select an option.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select an option.";
errors[questionId] = customErrorMsg;
}
});
// many2many required
$form.find('[data-question-type="many2many"]').each(function () {
const $input = $(this);
const $questionWrapper = $input.closest(".js_question-wrapper");
const questionId = $questionWrapper.attr('id');
const questionRequired = $questionWrapper.data('required');
const val = $input.val() || [];
if (questionRequired && val.length === 0) {
errors[questionId] = "Please select at least one option.";
var customErrorMsg = $questionWrapper.data('required-error') || "Please select at least one option.";
errors[questionId] = customErrorMsg;
}
});
if (Object.keys(errors).length > 0) {
displayErrors(this, errors);
return false;
}
return origResult;
};
}
// If the interaction is already registered, patch it now. Otherwise wait
// for the public.interactions registry to be updated.
if (interactions.contains("survey.SurveyForm")) {
applyPatchTo(interactions.get("survey.SurveyForm"));
} else {
const handler = (ev) => {
if (interactions.contains("survey.SurveyForm")) {
interactions.removeEventListener("UPDATE", handler);
applyPatchTo(interactions.get("survey.SurveyForm"));
}
};
interactions.addEventListener("UPDATE", handler);
}
@@ -0,0 +1,56 @@
function loadSelect2() {
if (!document.querySelector('link[href*="select2"]')) {
const css = document.createElement('link');
css.rel = 'stylesheet';
css.href = 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css';
document.head.appendChild(css);
}
if (typeof jQuery !== 'undefined' && !jQuery.fn.select2) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js';
script.onload = () => setTimeout(initSelect2, 100);
document.head.appendChild(script);
} else if (typeof jQuery !== 'undefined' && jQuery.fn.select2) {
initSelect2();
} else {
setTimeout(loadSelect2, 100);
}
}
function initSelect2() {
if (typeof jQuery === 'undefined' || !jQuery.fn.select2) {
setTimeout(initSelect2, 100);
return;
}
jQuery('.many2many-select2:not(.select2-hidden-accessible)').select2({
placeholder: "Select one or more options",
allowClear: true,
closeOnSelect: false,
width: '100%'
}).on('change', function() {
const values = jQuery(this).val() || [];
jQuery(this).closest('.o_survey_answer_wrapper').find('.many2many-data').val(values.join(','));
}).trigger('change');
jQuery('.many2one-select2:not(.select2-hidden-accessible)').select2({
placeholder: "-- Select an option --",
allowClear: true,
width: '100%'
});
}
if (typeof jQuery !== 'undefined') {
jQuery(document).ready(loadSelect2);
new MutationObserver(mutations => {
if (mutations.some(m => m.addedNodes.length &&
Array.from(m.addedNodes).some(n => n.nodeType === 1 &&
(n.querySelector('.many2many-select2') || n.querySelector('.many2one-select2'))))) {
setTimeout(initSelect2, 50);
}
}).observe(document.body, { childList: true, subtree: true });
} else {
setTimeout(loadSelect2, 100);
}
@@ -0,0 +1,18 @@
odoo.define('zehntech_survey_extra_fields.survey_range_field', [], function (require) {
'use strict';
$(document).ready(function() {
$(document).on('input', '.o_survey_question_range', function() {
var $range = $(this);
var $valueDisplay = $range.closest('.o_survey_answer_wrapper').find('.range-value');
$valueDisplay.text($range.val());
});
// Initialize value on page load
$('.o_survey_question_range').each(function() {
var $range = $(this);
var $valueDisplay = $range.closest('.o_survey_answer_wrapper').find('.range-value');
$valueDisplay.text($range.val());
});
});
});
@@ -0,0 +1,107 @@
(function() {
'use strict';
function initSignaturePads() {
document.querySelectorAll('.signature-pad').forEach(function(canvas) {
if (canvas.dataset.initialized) return;
canvas.dataset.initialized = 'true';
var ctx = canvas.getContext('2d');
var drawing = false;
var hiddenInput = canvas.closest('.o_survey_answer_wrapper').querySelector('.signature-data');
// Set drawing style
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Clear canvas
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Load existing signature if available
var existingData = hiddenInput.value;
if (existingData && existingData.startsWith('data:image/')) {
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = existingData;
}
function getMousePos(e) {
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
canvas.addEventListener('mousedown', function(e) {
drawing = true;
var pos = getMousePos(e);
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
});
canvas.addEventListener('mousemove', function(e) {
if (!drawing) return;
var pos = getMousePos(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
});
function saveSignature() {
if (drawing) {
drawing = false;
hiddenInput.value = canvas.toDataURL('image/png');
}
}
canvas.addEventListener('mouseup', saveSignature);
canvas.addEventListener('mouseout', saveSignature);
});
// Handle clear buttons
document.querySelectorAll('.clear-signature').forEach(function(btn) {
if (btn.dataset.initialized) return;
btn.dataset.initialized = 'true';
btn.addEventListener('click', function(e) {
e.preventDefault();
var container = btn.closest('.signature-container');
var canvas = container.querySelector('.signature-pad');
var hiddenInput = container.closest('.o_survey_answer_wrapper').querySelector('.signature-data');
if (canvas) {
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
hiddenInput.value = '';
}
});
});
}
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSignaturePads);
} else {
initSignaturePads();
}
// Re-initialize when new content is added (for dynamic content)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
initSignaturePads();
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();