PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.1
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.1
5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 All 38 releases
double-opt-in / assets / js / form-settings-panel.js

form-settings-panel.js in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.6.1, at assets/js/form-settings-panel.js

777 lines 30.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Form Settings Panel JavaScript
3 *
4 * Handles the slide-out panel for form settings management.
5 *
6 * @package Forge12\DoubleOptIn
7 * @since 4.1.0
8 */
9
10 (function($) {
11 'use strict';
12
13 var DoiFormSettings = {
14 /**
15 * Current form ID being edited.
16 */
17 currentFormId: null,
18
19 /**
20 * Current form data.
21 */
22 currentFormData: null,
23
24 /**
25 * Current integration type.
26 */
27 currentIntegration: null,
28
29 /**
30 * Initialize the module.
31 */
32 init: function() {
33 this.bindEvents();
34 },
35
36 /**
37 * Bind event handlers.
38 */
39 bindEvents: function() {
40 var self = this;
41
42 // Tooltip positioning (fixed)
43 $(document).on('mouseenter', '.doi-tooltip', function() {
44 var $tip = $(this).find('.doi-tooltip-text');
45 var rect = this.getBoundingClientRect();
46 var tipW = 260;
47 var left = rect.left + rect.width / 2 - tipW / 2;
48 // Keep tooltip within viewport
49 if (left < 8) left = 8;
50 if (left + tipW > window.innerWidth - 8) left = window.innerWidth - tipW - 8;
51 $tip.css({ left: left + 'px', bottom: (window.innerHeight - rect.top + 8) + 'px' });
52 });
53
54 // Toggle switch
55 $(document).on('change', '.doi-toggle-input', function() {
56 self.toggleForm($(this));
57 });
58
59 // Configure button
60 $(document).on('click', '.doi-configure-btn', function() {
61 var formId = $(this).data('form-id');
62 var formTitle = $(this).data('form-title');
63 var integration = $(this).data('integration') || '';
64 self.openPanel(formId, formTitle, integration);
65 });
66
67 // Close panel
68 $(document).on('click', '.doi-panel-close, .doi-panel-overlay, #doi-panel-cancel', function() {
69 self.closePanel();
70 });
71
72 // Save settings
73 $(document).on('click', '#doi-panel-save', function() {
74 self.saveSettings();
75 });
76
77 // Escape key to close panel
78 $(document).on('keydown', function(e) {
79 if (e.key === 'Escape' && $('#doi-settings-panel').hasClass('open')) {
80 self.closePanel();
81 }
82 });
83
84 // Placeholder click to insert
85 $(document).on('click', '.doi-placeholders code', function() {
86 var placeholder = $(this).text();
87 var $body = $('#doi-body');
88 var cursorPos = $body[0].selectionStart;
89 var text = $body.val();
90 $body.val(text.substring(0, cursorPos) + placeholder + text.substring(cursorPos));
91 $body.focus();
92 $body[0].selectionStart = $body[0].selectionEnd = cursorPos + placeholder.length;
93 });
94
95 // Toggle between select and manual input for recipient
96 $(document).on('click', '#doi-toggle-recipient-input', function() {
97 var $select = $('#doi-recipient-select');
98 var $input = $('#doi-recipient');
99
100 if ($select.is(':visible')) {
101 // Switch to manual input
102 $input.val($select.val()).show();
103 $select.hide();
104 $(this).find('.dashicons').removeClass('dashicons-edit').addClass('dashicons-list-view');
105 } else {
106 // Switch to select
107 var currentVal = $input.val();
108 $select.show();
109 $input.hide();
110 // Try to select the current value
111 if ($select.find('option[value="' + currentVal + '"]').length) {
112 $select.val(currentVal);
113 }
114 $(this).find('.dashicons').removeClass('dashicons-list-view').addClass('dashicons-edit');
115 }
116 });
117
118 // Sync select value to hidden input
119 $(document).on('change', '#doi-recipient-select', function() {
120 $('#doi-recipient').val($(this).val());
121 });
122
123 // Template selection change - toggle body editor vs custom template notice
124 $(document).on('change', '#doi-template', function() {
125 self.handleTemplateChange($(this).val());
126 });
127
128 // Unique Email: toggle options visibility
129 $(document).on('change', '#doi-unique-email-enabled', function() {
130 $('#doi-unique-email-options').toggle($(this).is(':checked'));
131 });
132
133 // Unique Email: toggle redirect page field based on behavior
134 $(document).on('change', '#doi-unique-email-behavior', function() {
135 $('#doi-unique-email-redirect-field').toggle($(this).val() === 'redirect');
136 });
137
138 },
139
140 /**
141 * Handle template selection change.
142 * Shows/hides body editor based on whether a custom template is selected.
143 *
144 * @param {string} templateValue The selected template value.
145 */
146 handleTemplateChange: function(templateValue) {
147 var isCustomTemplate = templateValue && templateValue.indexOf('custom_') === 0;
148 var $bodyEditor = $('#doi-body-editor');
149 var $customNotice = $('#doi-custom-template-notice');
150 var $editBtn = $('#doi-edit-template-btn');
151
152 if (isCustomTemplate) {
153 // Custom template selected - hide body editor, show notice
154 $bodyEditor.hide();
155 $customNotice.show();
156
157 // Update edit button link to include template ID
158 var templateId = templateValue.replace('custom_', '');
159 var editorUrl = doiFormSettings.editorUrl + '&template_id=' + templateId;
160 $editBtn.attr('href', editorUrl);
161 } else {
162 // Standard template - show body editor, hide notice
163 $bodyEditor.show();
164 $customNotice.hide();
165 }
166 },
167
168 /**
169 * Toggle form enabled state.
170 *
171 * @param {jQuery} $toggle The toggle input element.
172 */
173 toggleForm: function($toggle) {
174 var self = this;
175 var formId = $toggle.data('form-id');
176 var $label = $toggle.closest('.doi-toggle');
177
178 $label.addClass('loading');
179
180 $.ajax({
181 url: doiFormSettings.ajaxUrl,
182 type: 'POST',
183 data: {
184 action: 'doi_toggle_form',
185 nonce: doiFormSettings.nonce,
186 form_id: formId
187 },
188 success: function(response) {
189 $label.removeClass('loading');
190
191 if (response.success) {
192 self.showToast(response.data.message, 'success');
193 } else {
194 // Revert toggle
195 $toggle.prop('checked', !$toggle.prop('checked'));
196 self.showToast(response.data.message || doiFormSettings.i18n.error, 'error');
197 }
198 },
199 error: function(xhr) {
200 $label.removeClass('loading');
201 $toggle.prop('checked', !$toggle.prop('checked'));
202 self.showErrorFromResponse(xhr);
203 }
204 });
205 },
206
207 /**
208 * Open the settings panel.
209 *
210 * @param {number} formId The form ID.
211 * @param {string} formTitle The form title.
212 * @param {string} integration The integration type (optional).
213 */
214 openPanel: function(formId, formTitle, integration) {
215 var self = this;
216 var $panel = $('#doi-settings-panel');
217
218 this.currentFormId = formId;
219 this.currentIntegration = integration || '';
220
221 // Update title
222 $panel.find('.doi-panel-title').text(
223 doiFormSettings.i18n.configure + ': ' + formTitle
224 );
225
226 // Show panel
227 $panel.addClass('open');
228 $('body').css('overflow', 'hidden');
229
230 // Show loading, hide form
231 $panel.find('.doi-panel-loading').show();
232 $panel.find('.doi-settings-form').hide();
233
234 // Load settings
235 $.ajax({
236 url: doiFormSettings.ajaxUrl,
237 type: 'POST',
238 data: {
239 action: 'doi_get_form_settings',
240 nonce: doiFormSettings.nonce,
241 form_id: formId,
242 integration: integration || ''
243 },
244 success: function(response) {
245 $panel.find('.doi-panel-loading').hide();
246
247 if (response.success) {
248 self.currentFormData = response.data;
249 self.populateForm(response.data);
250 $panel.find('.doi-settings-form').show();
251 } else {
252 self.showToast(response.data.message || doiFormSettings.i18n.error, 'error');
253 self.closePanel();
254 }
255 },
256 error: function(xhr) {
257 self.showErrorFromResponse(xhr);
258 self.closePanel();
259 }
260 });
261 },
262
263 /**
264 * Close the settings panel.
265 */
266 closePanel: function() {
267 var $panel = $('#doi-settings-panel');
268 $panel.removeClass('open');
269 $('body').css('overflow', '');
270 this.currentFormId = null;
271 this.currentFormData = null;
272 this.currentIntegration = null;
273 },
274
275 /**
276 * Populate the form with data.
277 *
278 * @param {Object} data The form data.
279 */
280 populateForm: function(data) {
281 var settings = data.settings || {};
282 var fields = data.fields || {};
283 var templates = data.templates || {};
284 var categories = data.categories || {};
285 var pages = data.pages || {};
286
287 // Form ID
288 $('#doi-form-id').val(data.id);
289
290 // Handle Enable/Disable field based on integration type
291 var isElementor = this.currentIntegration === 'elementor';
292
293 if (isElementor) {
294 // For Elementor: show status badge instead of checkbox
295 $('#doi-enable-field').hide();
296 $('#doi-status-field').show();
297
298 // Show appropriate badge based on enabled state
299 if (settings.enabled) {
300 $('#doi-status-active').show();
301 $('#doi-status-inactive').hide();
302 } else {
303 $('#doi-status-active').hide();
304 $('#doi-status-inactive').show();
305 }
306 } else {
307 // For other integrations: show checkbox
308 $('#doi-enable-field').show();
309 $('#doi-status-field').hide();
310 $('#doi-enabled').prop('checked', settings.enabled);
311 }
312
313 // Populate categories select
314 var $category = $('#doi-category');
315 $category.empty();
316 $.each(categories, function(id, name) {
317 $category.append($('<option>', { value: id, text: name }));
318 });
319 $category.val(settings.category || 0);
320
321 // Populate conditions select (form fields)
322 var $conditions = $('#doi-conditions');
323 $conditions.empty();
324 $conditions.append($('<option>', { value: 'disabled', text: doiFormSettings.i18n.disabled }));
325 $.each(fields, function(name, label) {
326 $conditions.append($('<option>', { value: name, text: '[' + name + ']' }));
327 });
328 $conditions.val(settings.conditions || 'disabled');
329
330 // Populate confirmation page select
331 var $page = $('#doi-confirmation-page');
332 $page.empty();
333 $.each(pages, function(id, title) {
334 $page.append($('<option>', { value: id, text: title }));
335 });
336 $page.val(settings.confirmationPage || -1);
337
338 // Populate error redirect page select
339 var $errorPage = $('#doi-error-redirect-page');
340 $errorPage.empty();
341 $.each(pages, function(id, title) {
342 $errorPage.append($('<option>', { value: id, text: title }));
343 });
344 $errorPage.val(settings.errorRedirectPage || -1);
345
346 // Populate recipient field (select + manual input)
347 var $recipientSelect = $('#doi-recipient-select');
348 var $recipientInput = $('#doi-recipient');
349 var $recipientToggle = $('#doi-toggle-recipient-input');
350 var hasFields = Object.keys(fields).length > 0;
351
352 $recipientSelect.empty();
353 $recipientSelect.append($('<option>', { value: '', text: '-- ' + doiFormSettings.i18n.disabled + ' --' }));
354 $.each(fields, function(name, label) {
355 var displayText = label !== name ? label + ' [' + name + ']' : '[' + name + ']';
356 $recipientSelect.append($('<option>', { value: '[' + name + ']', text: displayText }));
357 });
358
359 var currentRecipient = settings.recipient || '';
360 $recipientInput.val(currentRecipient);
361
362 // Determine if we should show select or input
363 var recipientInFields = false;
364 if (currentRecipient && hasFields) {
365 recipientInFields = $recipientSelect.find('option[value="' + currentRecipient + '"]').length > 0;
366 }
367
368 if (!hasFields || (currentRecipient && !recipientInFields)) {
369 // No fields found or current value not in fields - show manual input
370 $recipientSelect.hide();
371 $recipientInput.show();
372 $recipientToggle.find('.dashicons').removeClass('dashicons-edit').addClass('dashicons-list-view');
373 } else {
374 // Show select
375 $recipientSelect.show().val(currentRecipient);
376 $recipientInput.hide();
377 $recipientToggle.find('.dashicons').removeClass('dashicons-list-view').addClass('dashicons-edit');
378 }
379
380 // Hide toggle button if no fields (manual input only)
381 if (!hasFields) {
382 $recipientToggle.hide();
383 } else {
384 $recipientToggle.show();
385 }
386
387 // Sender
388 $('#doi-sender').val(settings.sender || '');
389 $('#doi-sender-name').val(settings.senderName || '');
390
391 // Subject
392 $('#doi-subject').val(settings.subject || '');
393
394 // Populate template select
395 var $template = $('#doi-template');
396 $template.empty();
397 $.each(templates, function(key, label) {
398 $template.append($('<option>', { value: key, text: label }));
399 });
400 var selectedTemplate = settings.template || 'blank';
401 $template.val(selectedTemplate);
402
403 // Body
404 $('#doi-body').val(settings.body || '');
405
406 // Consent Text (GDPR)
407 $('#doi-consent-text').val(settings.consentText || '');
408
409 // Unique Email settings
410 $('#doi-unique-email-enabled').prop('checked', settings.unique_email_enabled == 1);
411 $('#doi-unique-email-behavior').val(settings.unique_email_behavior || 'block');
412 $('#doi-unique-email-scope').val(settings.unique_email_scope || 'confirmed');
413 $('#doi-unique-email-message').val(settings.unique_email_message || '');
414 $('#doi-unique-email-options').toggle(settings.unique_email_enabled == 1);
415
416 // Populate unique email redirect page
417 var $ueRedirectPage = $('#doi-unique-email-redirect-page');
418 $ueRedirectPage.empty();
419 $.each(pages, function(id, title) {
420 $ueRedirectPage.append($('<option>', { value: id, text: title }));
421 });
422 $ueRedirectPage.val(settings.unique_email_redirect_page || -1);
423
424 // Show/hide redirect page based on behavior
425 $('#doi-unique-email-redirect-field').toggle(
426 $('#doi-unique-email-behavior').val() === 'redirect'
427 );
428
429 // Handle template change to show/hide body editor
430 this.handleTemplateChange(selectedTemplate);
431
432 // Update form field placeholders
433 var fieldsHtml = '';
434 $.each(fields, function(name) {
435 fieldsHtml += '<code>[' + name + ']</code> ';
436 });
437 $('.doi-form-fields-placeholders').html(fieldsHtml);
438
439 // Populate field mapping grid
440 this.populateFieldMapping(fields, settings.fieldMapping || {});
441
442 /**
443 * Trigger event for extensions (e.g., Pro version) to populate additional fields.
444 *
445 * @param {Object} data The full form data object.
446 * @param {Object} settings The settings object.
447 */
448 $(document).trigger('doi:panel:populated', [data, settings]);
449 },
450
451 /**
452 * Save the current settings.
453 */
454 saveSettings: function() {
455 var self = this;
456 var $panel = $('#doi-settings-panel');
457 var $form = $('#doi-settings-form');
458
459 if (!this.currentFormId) {
460 return;
461 }
462
463 // Collect form data
464 // For Elementor, preserve the enabled state from server (don't read checkbox)
465 var isElementor = this.currentIntegration === 'elementor';
466 var settings = {
467 enabled: isElementor ? (this.currentFormData && this.currentFormData.settings ? this.currentFormData.settings.enabled : false) : $('#doi-enabled').is(':checked'),
468 category: $('#doi-category').val(),
469 conditions: $('#doi-conditions').val(),
470 confirmationPage: $('#doi-confirmation-page').val(),
471 errorRedirectPage: $('#doi-error-redirect-page').val(),
472 recipient: $('#doi-recipient').val(),
473 sender: $('#doi-sender').val(),
474 senderName: $('#doi-sender-name').val(),
475 subject: $('#doi-subject').val(),
476 template: $('#doi-template').val(),
477 body: $('#doi-body').val(),
478 consentText: $('#doi-consent-text').val(),
479 fieldMapping: this.collectFieldMapping(),
480 unique_email_enabled: $('#doi-unique-email-enabled').is(':checked') ? 1 : 0,
481 unique_email_behavior: $('#doi-unique-email-behavior').val() || 'block',
482 unique_email_scope: $('#doi-unique-email-scope').val() || 'confirmed',
483 unique_email_message: $('#doi-unique-email-message').val() || '',
484 unique_email_redirect_page: $('#doi-unique-email-redirect-page').val() || -1
485 };
486
487 /**
488 * Trigger event for extensions (e.g., Pro version) to add additional fields to settings.
489 * Extensions should modify the settings object directly.
490 *
491 * @param {Object} settings The settings object to be saved.
492 * @param {number} formId The current form ID.
493 */
494 $(document).trigger('doi:panel:beforesave', [settings, this.currentFormId]);
495
496 // Clear previous errors before saving
497 self.clearFieldErrors();
498 $panel.addClass('saving');
499
500 $.ajax({
501 url: doiFormSettings.ajaxUrl,
502 type: 'POST',
503 data: {
504 action: 'doi_save_form_settings',
505 nonce: doiFormSettings.nonce,
506 form_id: this.currentFormId,
507 settings: JSON.stringify(settings)
508 },
509 success: function(response) {
510 $panel.removeClass('saving');
511
512 if (response.success) {
513 self.showToast(response.data.message || doiFormSettings.i18n.saved, 'success');
514
515 // Update toggle state in table
516 var $toggle = $('.doi-toggle-input[data-form-id="' + self.currentFormId + '"]');
517 $toggle.prop('checked', response.data.enabled);
518
519 self.closePanel();
520 } else {
521 // Clear previous field errors
522 self.clearFieldErrors();
523
524 var message = response.data.message || doiFormSettings.i18n.error;
525 var errors = response.data.errors || {};
526 var errorKeys = Object.keys(errors);
527
528 if (errorKeys.length > 0) {
529 // Build detailed error list
530 var errorHtml = '<strong>' + message + '</strong><ul class="doi-toast-errors">';
531 for (var i = 0; i < errorKeys.length; i++) {
532 errorHtml += '<li>' + errors[errorKeys[i]] + '</li>';
533 }
534 errorHtml += '</ul>';
535
536 self.showToast(errorHtml, 'error', true);
537
538 // Highlight fields with errors
539 self.highlightFieldErrors(errors);
540 } else {
541 self.showToast(message, 'error');
542 }
543 }
544 },
545 error: function(xhr) {
546 $panel.removeClass('saving');
547 self.showErrorFromResponse(xhr);
548 }
549 });
550 },
551
552 /**
553 * Show a toast notification.
554 *
555 * @param {string} message The message to show (can contain HTML for errors).
556 * @param {string} type The toast type (success/error).
557 * @param {boolean} hasDetail Whether the toast contains detailed error info.
558 */
559 showToast: function(message, type, hasDetail) {
560 // Remove any existing toasts
561 $('.doi-toast').remove();
562
563 var cssClass = 'doi-toast ' + type + (hasDetail ? ' doi-toast-detailed' : '');
564 var closeBtn = hasDetail ? '<button class="doi-toast-close" type="button">&times;</button>' : '';
565 var $toast = $('<div class="' + cssClass + '">' + closeBtn + message + '</div>');
566 $('body').append($toast);
567
568 // Close button handler
569 $toast.on('click', '.doi-toast-close', function() {
570 $toast.removeClass('show');
571 setTimeout(function() { $toast.remove(); }, 300);
572 });
573
574 setTimeout(function() {
575 $toast.addClass('show');
576 }, 10);
577
578 // Auto-hide: longer for detailed errors
579 var duration = hasDetail ? 8000 : 3000;
580 setTimeout(function() {
581 $toast.removeClass('show');
582 setTimeout(function() {
583 $toast.remove();
584 }, 300);
585 }, duration);
586 },
587
588 /**
589 * Parse XHR error response and show appropriate error message.
590 *
591 * @param {Object} xhr The XMLHttpRequest object from jQuery AJAX error.
592 */
593 showErrorFromResponse: function(xhr) {
594 var self = this;
595
596 try {
597 var response = JSON.parse(xhr.responseText);
598 if (response && response.data) {
599 var message = response.data.message || doiFormSettings.i18n.error;
600 var errors = response.data.errors || {};
601 var errorKeys = Object.keys(errors);
602
603 if (errorKeys.length > 0) {
604 // Build detailed error list
605 var errorHtml = '<strong>' + message + '</strong><ul class="doi-toast-errors">';
606 for (var i = 0; i < errorKeys.length; i++) {
607 errorHtml += '<li>' + errors[errorKeys[i]] + '</li>';
608 }
609 errorHtml += '</ul>';
610
611 self.showToast(errorHtml, 'error', true);
612 self.highlightFieldErrors(errors);
613 return;
614 } else if (message) {
615 self.showToast(message, 'error');
616 return;
617 }
618 }
619 } catch (e) {
620 // JSON parse failed, fall through to generic error
621 }
622
623 self.showToast(doiFormSettings.i18n.error, 'error');
624 },
625
626 /**
627 * Highlight form fields that have validation errors.
628 *
629 * @param {Object} errors Field name => error message mapping.
630 */
631 highlightFieldErrors: function(errors) {
632 // Map server field names to DOM element IDs
633 var fieldMap = {
634 'recipient': 'doi-recipient',
635 'sender': 'doi-sender',
636 'senderName': 'doi-sender-name',
637 'subject': 'doi-subject',
638 'body': 'doi-body',
639 'template': 'doi-template',
640 'confirmationPage': 'doi-confirmation-page',
641 'errorRedirectPage': 'doi-error-redirect-page',
642 'category': 'doi-category',
643 'conditions': 'doi-conditions',
644 'consentText': 'doi-consent-text'
645 };
646
647 for (var fieldName in errors) {
648 if (!errors.hasOwnProperty(fieldName)) continue;
649
650 var elementId = fieldMap[fieldName];
651 if (!elementId) continue;
652
653 var $field = $('#' + elementId);
654 if ($field.length) {
655 $field.addClass('doi-field-error');
656
657 // Add error message below the field
658 var $errorMsg = $('<span class="doi-field-error-msg">' + errors[fieldName] + '</span>');
659 $field.closest('.option').find('.input').append($errorMsg);
660 }
661 }
662 },
663
664 /**
665 * Clear all field error highlights.
666 */
667 clearFieldErrors: function() {
668 $('.doi-field-error').removeClass('doi-field-error');
669 $('.doi-field-error-msg').remove();
670 },
671
672 /**
673 * Populate the field mapping grid.
674 *
675 * @param {Object} fields Available form fields.
676 * @param {Object} fieldMapping Current field mapping settings.
677 */
678 populateFieldMapping: function(fields, fieldMapping) {
679 var $grid = $('#doi-field-mapping-grid');
680 $grid.empty();
681
682 var standardPlaceholders = doiFormSettings.standardPlaceholders || {};
683 var fieldKeys = Object.keys(fields || {});
684 var hasFields = fieldKeys.length > 0;
685
686 // Check if standardPlaceholders is empty
687 if (Object.keys(standardPlaceholders).length === 0) {
688 $grid.append('<p class="doi-mapping-empty">Standard placeholders not loaded.</p>');
689 return;
690 }
691
692 // Create mapping rows for each standard placeholder
693 $.each(standardPlaceholders, function(placeholderKey, placeholderLabel) {
694 var $row = $('<div class="doi-mapping-row"></div>');
695
696 // Placeholder label
697 var $label = $('<div class="doi-mapping-label"></div>');
698 $label.append('<code>[' + placeholderKey + ']</code>');
699 $label.append('<span class="doi-mapping-label-text">' + placeholderLabel + '</span>');
700 $row.append($label);
701
702 // Arrow
703 $row.append('<span class="doi-mapping-arrow">→</span>');
704
705 // Field select
706 var $selectWrapper = $('<div class="doi-mapping-field"></div>');
707 var $select = $('<select name="fieldMapping[' + placeholderKey + ']" class="doi-mapping-select"></select>');
708
709 // Add empty option
710 $select.append($('<option>', {
711 value: '',
712 text: doiFormSettings.i18n.notMapped || '-- Not mapped --'
713 }));
714
715 // Add form fields as options
716 if (hasFields) {
717 $.each(fields, function(fieldName, fieldLabel) {
718 var displayText = fieldLabel !== fieldName ? fieldLabel + ' [' + fieldName + ']' : '[' + fieldName + ']';
719 $select.append($('<option>', {
720 value: fieldName,
721 text: displayText
722 }));
723 });
724 }
725
726 // Set current value if exists
727 var currentValue = (fieldMapping && fieldMapping[placeholderKey]) ? fieldMapping[placeholderKey] : '';
728 // Remove brackets if present
729 if (currentValue) {
730 currentValue = currentValue.replace(/^\[|\]$/g, '');
731 }
732 $select.val(currentValue);
733
734 $selectWrapper.append($select);
735 $row.append($selectWrapper);
736
737 $grid.append($row);
738 });
739
740 // Show info message if no fields available
741 if (!hasFields) {
742 $grid.append('<p class="doi-mapping-info" style="margin-top: 10px; color: #666; font-size: 12px;"><em>' + (doiFormSettings.i18n.noFieldsFound || 'Note: No form fields detected. You can still configure the mapping manually by entering field names.') + '</em></p>');
743 }
744 },
745
746 /**
747 * Collect field mapping values from the form.
748 *
749 * @return {Object} Field mapping object.
750 */
751 collectFieldMapping: function() {
752 var mapping = {};
753
754 $('#doi-field-mapping-grid select.doi-mapping-select').each(function() {
755 var name = $(this).attr('name');
756 var value = $(this).val();
757
758 if (name && value) {
759 // Extract placeholder key from name (e.g., "fieldMapping[doi_email]" -> "doi_email")
760 var match = name.match(/fieldMapping\[([^\]]+)\]/);
761 if (match && match[1]) {
762 mapping[match[1]] = value;
763 }
764 }
765 });
766
767 return mapping;
768 }
769 };
770
771 // Initialize on document ready
772 $(document).ready(function() {
773 DoiFormSettings.init();
774 });
775
776 })(jQuery);
777