fix wpcfto select and repeater related visibility and validation
This commit is contained in:
@@ -596,11 +596,6 @@ jQuery(function($){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).on('change', '#customer_data select', function() {
|
|
||||||
var value = $(this).val();
|
|
||||||
$(this).attr('data-current-value', value);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('click', '.delete-preview-field', function(e){
|
$(document).on('click', '.delete-preview-field', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(this).parents('.preview-field').remove();
|
$(this).parents('.preview-field').remove();
|
||||||
@@ -760,40 +755,40 @@ jQuery(function($){
|
|||||||
$(this).closest('.child-field-title').toggleClass('option-detail-opened');
|
$(this).closest('.child-field-title').toggleClass('option-detail-opened');
|
||||||
});
|
});
|
||||||
|
|
||||||
var all_checkbox = $('[type="checkbox"]');
|
// var all_checkbox = $('[type="checkbox"]');
|
||||||
if(all_checkbox.length > 0){
|
// if(all_checkbox.length > 0){
|
||||||
$.each(all_checkbox, function(a,b){
|
// $.each(all_checkbox, function(a,b){
|
||||||
if($(b).val() == 'yes'){
|
// if($(b).val() == 'yes'){
|
||||||
$(b).val('no').trigger('click');
|
// $(b).val('no').trigger('click');
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
function modify_payment_box_behavior() {
|
// function modify_payment_box_behavior() {
|
||||||
|
|
||||||
var allbox = $('#payments multi_checkbox');
|
// var allbox = $('#payments multi_checkbox');
|
||||||
var checkbox_input = $('.payments-payment input[type=checkbox]');
|
// var checkbox_input = $('.payments-payment input[type=checkbox]');
|
||||||
var checked_value = [];
|
// var checked_value = [];
|
||||||
if(checkbox_input.length > 0){
|
// if(checkbox_input.length > 0){
|
||||||
$.each(checkbox_input, function(x, y){
|
// $.each(checkbox_input, function(x, y){
|
||||||
if($(y).is(':checked')){
|
// if($(y).is(':checked')){
|
||||||
checked_value.push($(y).val());
|
// checked_value.push($(y).val());
|
||||||
$('[data-field=wpcfto_addon_option_payment_'+$(y).val()+']').show();
|
// $('[data-field=wpcfto_addon_option_payment_'+$(y).val()+']').show();
|
||||||
}else{
|
// }else{
|
||||||
$('[data-field=wpcfto_addon_option_payment_'+$(y).val()+']').hide();
|
// $('[data-field=wpcfto_addon_option_payment_'+$(y).val()+']').hide();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
modify_payment_box_behavior();
|
// modify_payment_box_behavior();
|
||||||
}, 500);
|
// }, 500);
|
||||||
|
|
||||||
$(document).on('click', '.payments-payment input[type=checkbox]', function(){
|
// $(document).on('click', '.payments-payment input[type=checkbox]', function(){
|
||||||
modify_payment_box_behavior();
|
// modify_payment_box_behavior();
|
||||||
});
|
// });
|
||||||
|
|
||||||
$(document).on('mouseover', 'span.grab', function(){
|
$(document).on('mouseover', 'span.grab', function(){
|
||||||
$(this).css('pointer', 'grab');
|
$(this).css('pointer', 'grab');
|
||||||
@@ -807,9 +802,14 @@ jQuery(function($){
|
|||||||
$(this).closest('.child-fields-wrapper').find('.the_title').text($(this).val());
|
$(this).closest('.child-fields-wrapper').find('.the_title').text($(this).val());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('change', '#customer_data select', function() {
|
||||||
|
var value = $(this).val();
|
||||||
|
$(this).attr('data-current-value', value);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
jQuery(function($){
|
// jQuery(function($){
|
||||||
|
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// var autocomplete_fields = $('.wpcfto-box .autocomplete');
|
// var autocomplete_fields = $('.wpcfto-box .autocomplete');
|
||||||
@@ -831,116 +831,116 @@ jQuery(function($){
|
|||||||
// }, 500);
|
// }, 500);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
$( document ).on( 'click', '.add-thumbnail', function( event ) {
|
// $( document ).on( 'click', '.add-thumbnail', function( event ) {
|
||||||
|
|
||||||
var gallery_items_frame;
|
// var gallery_items_frame;
|
||||||
const $el = $( this );
|
// const $el = $( this );
|
||||||
var target_field = $el.attr('data-field');
|
// var target_field = $el.attr('data-field');
|
||||||
var target_id = $el.siblings('.'+target_field+'-id');
|
// var target_id = $el.siblings('.'+target_field+'-id');
|
||||||
var target_url = $el.siblings('.'+target_field+'-url');
|
// var target_url = $el.siblings('.'+target_field+'-url');
|
||||||
var selected = target_id.val();
|
// var selected = target_id.val();
|
||||||
var able_multiple = $el.attr('data-able-multiple');
|
// var able_multiple = $el.attr('data-able-multiple');
|
||||||
|
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
|
|
||||||
if ( gallery_items_frame ) {
|
// if ( gallery_items_frame ) {
|
||||||
|
|
||||||
// Select the attachment when the frame opens
|
// // Select the attachment when the frame opens
|
||||||
gallery_items_frame.on( 'open', function() {
|
// gallery_items_frame.on( 'open', function() {
|
||||||
var selection = gallery_items_frame.state().get( 'selection' );
|
// var selection = gallery_items_frame.state().get( 'selection' );
|
||||||
selection.reset( selected ? [ wp.media.attachment( selected ) ] : [] );
|
// selection.reset( selected ? [ wp.media.attachment( selected ) ] : [] );
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Open the modal.
|
// // Open the modal.
|
||||||
gallery_items_frame.open();
|
// gallery_items_frame.open();
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Create the media frame.
|
// // Create the media frame.
|
||||||
gallery_items_frame = wp.media.frames.gallery_items = wp.media({
|
// gallery_items_frame = wp.media.frames.gallery_items = wp.media({
|
||||||
// Set the title of the modal.
|
// // Set the title of the modal.
|
||||||
title: 'Choose or upload media',
|
// title: 'Choose or upload media',
|
||||||
button: {
|
// button: {
|
||||||
text: 'Select'
|
// text: 'Select'
|
||||||
},
|
// },
|
||||||
states: [
|
// states: [
|
||||||
new wp.media.controller.Library({
|
// new wp.media.controller.Library({
|
||||||
title: 'Choose or upload media',
|
// title: 'Choose or upload media',
|
||||||
filterable: 'all',
|
// filterable: 'all',
|
||||||
multiple: able_multiple
|
// multiple: able_multiple
|
||||||
})
|
// })
|
||||||
]
|
// ]
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Select the attachment when the frame opens
|
// // Select the attachment when the frame opens
|
||||||
gallery_items_frame.on( 'open', function() {
|
// gallery_items_frame.on( 'open', function() {
|
||||||
var selection = gallery_items_frame.state().get( 'selection' );
|
// var selection = gallery_items_frame.state().get( 'selection' );
|
||||||
selection.reset( selected ? [ wp.media.attachment( selected ) ] : [] );
|
// selection.reset( selected ? [ wp.media.attachment( selected ) ] : [] );
|
||||||
});
|
// });
|
||||||
|
|
||||||
gallery_items_frame.on( 'select', function() {
|
// gallery_items_frame.on( 'select', function() {
|
||||||
attachment = gallery_items_frame.state().get('selection').first().toJSON();
|
// attachment = gallery_items_frame.state().get('selection').first().toJSON();
|
||||||
target_id.val( attachment.id );
|
// target_id.val( attachment.id );
|
||||||
target_url.val( attachment.url );
|
// target_url.val( attachment.url );
|
||||||
if(target_id.val() !== ''){
|
// if(target_id.val() !== ''){
|
||||||
// $el.removeClass('text-white').addClass('text-info d-none');
|
// // $el.removeClass('text-white').addClass('text-info d-none');
|
||||||
if($el.hasClass('btn')){
|
// if($el.hasClass('btn')){
|
||||||
$el.siblings('i').hide();
|
// $el.siblings('i').hide();
|
||||||
}else{
|
// }else{
|
||||||
$el.hide();
|
// $el.hide();
|
||||||
}
|
// }
|
||||||
$el.siblings('img').removeClass('d-none').attr('src', attachment.url).show();
|
// $el.siblings('img').removeClass('d-none').attr('src', attachment.url).show();
|
||||||
}else{
|
// }else{
|
||||||
// $el.removeClass('text-info d-none').addClass('text-white');
|
// // $el.removeClass('text-info d-none').addClass('text-white');
|
||||||
if($el.hasClass('btn')){
|
// if($el.hasClass('btn')){
|
||||||
$el.siblings('i').show();
|
// $el.siblings('i').show();
|
||||||
}else{
|
// }else{
|
||||||
$el.show();
|
// $el.show();
|
||||||
}
|
// }
|
||||||
$el.siblings('img').addClass('d-none').hide();
|
// $el.siblings('img').addClass('d-none').hide();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Open the modal.
|
// // Open the modal.
|
||||||
gallery_items_frame.open();
|
// gallery_items_frame.open();
|
||||||
|
|
||||||
});
|
// });
|
||||||
|
|
||||||
$( document ).on( 'click', '.trumbowyg-button-group:has(.trumbowyg-insertImage-button)', function( event ) {
|
// $( document ).on( 'click', '.trumbowyg-button-group:has(.trumbowyg-insertImage-button)', function( event ) {
|
||||||
|
|
||||||
var gallery_items_frame;
|
// var gallery_items_frame;
|
||||||
|
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
|
|
||||||
// Create the media frame.
|
// // Create the media frame.
|
||||||
gallery_items_frame = wp.media.frames.gallery_items = wp.media({
|
// gallery_items_frame = wp.media.frames.gallery_items = wp.media({
|
||||||
// Set the title of the modal.
|
// // Set the title of the modal.
|
||||||
title: 'Choose or upload media',
|
// title: 'Choose or upload media',
|
||||||
button: {
|
// button: {
|
||||||
text: 'Select'
|
// text: 'Select'
|
||||||
},
|
// },
|
||||||
states: [
|
// states: [
|
||||||
new wp.media.controller.Library({
|
// new wp.media.controller.Library({
|
||||||
title: 'Choose or upload media',
|
// title: 'Choose or upload media',
|
||||||
filterable: 'all',
|
// filterable: 'all',
|
||||||
multiple: false
|
// multiple: false
|
||||||
})
|
// })
|
||||||
]
|
// ]
|
||||||
});
|
// });
|
||||||
|
|
||||||
gallery_items_frame.on( 'select', function() {
|
// gallery_items_frame.on( 'select', function() {
|
||||||
attachment = gallery_items_frame.state().get('selection').first().toJSON();
|
// attachment = gallery_items_frame.state().get('selection').first().toJSON();
|
||||||
var target_input_url = $('.trumbowyg-modal.trumbowyg-fixed-top .trumbowyg-input-html input');
|
// var target_input_url = $('.trumbowyg-modal.trumbowyg-fixed-top .trumbowyg-input-html input');
|
||||||
var target_confirm = $('.trumbowyg-modal.trumbowyg-fixed-top .trumbowyg-modal-submit');
|
// var target_confirm = $('.trumbowyg-modal.trumbowyg-fixed-top .trumbowyg-modal-submit');
|
||||||
target_input_url.val( attachment.url );
|
// target_input_url.val( attachment.url );
|
||||||
target_confirm.trigger('click');
|
// target_confirm.trigger('click');
|
||||||
|
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Open the modal.
|
// // Open the modal.
|
||||||
gallery_items_frame.open();
|
// gallery_items_frame.open();
|
||||||
|
|
||||||
});
|
// });
|
||||||
|
|
||||||
});
|
// });
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
function numberFormat(nStr) {
|
function numberFormat(nStr) {
|
||||||
nStr = parseFloat(nStr).toFixed(2);
|
nStr = parseFloat(nStr).toFixed(2);
|
||||||
var x = nStr.split('.');
|
var x = nStr.split('.');
|
||||||
var x1 = x[0];
|
var x1 = x[0];
|
||||||
var x2 = x.length > 1 ? '.' + x[1] : '';
|
var x2 = x.length > 1 ? '.' + x[1] : '';
|
||||||
var rgx = /(\d+)(\d{3})/;
|
var rgx = /(\d+)(\d{3})/;
|
||||||
while (rgx.test(x1)) {
|
while (rgx.test(x1)) {
|
||||||
x1 = x1.replace(rgx, '$1' + ',' + '$2');
|
x1 = x1.replace(rgx, '$1' + ',' + '$2');
|
||||||
}
|
}
|
||||||
return x1 + x2;
|
return x1 + x2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processPostsReport(data) {
|
function processPostsReport(data) {
|
||||||
@@ -38,32 +38,32 @@ jQuery(function($){
|
|||||||
});
|
});
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var supportsPassive = false;
|
var supportsPassive = false;
|
||||||
try {
|
try {
|
||||||
var opts = Object.defineProperty({}, 'passive', {
|
var opts = Object.defineProperty({}, 'passive', {
|
||||||
get: function() {
|
get: function() {
|
||||||
supportsPassive = true;
|
supportsPassive = true;
|
||||||
}
|
|
||||||
});
|
|
||||||
window.addEventListener("testPassive", null, opts);
|
|
||||||
window.removeEventListener("testPassive", null, opts);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
if (!supportsPassive) return;
|
|
||||||
|
|
||||||
var origAddEventListener = EventTarget.prototype.addEventListener;
|
|
||||||
EventTarget.prototype.addEventListener = function(type, listener, options) {
|
|
||||||
// Only patch touchstart and touchmove if options is not explicitly passive
|
|
||||||
if (
|
|
||||||
(type === 'touchstart' || type === 'touchmove') &&
|
|
||||||
(options === undefined || options === false || (typeof options === 'object' && !options.passive))
|
|
||||||
) {
|
|
||||||
options = options || {};
|
|
||||||
if (typeof options === 'object') {
|
|
||||||
options.passive = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return origAddEventListener.call(this, type, listener, options);
|
});
|
||||||
};
|
window.addEventListener("testPassive", null, opts);
|
||||||
})();
|
window.removeEventListener("testPassive", null, opts);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (!supportsPassive) return;
|
||||||
|
|
||||||
|
var origAddEventListener = EventTarget.prototype.addEventListener;
|
||||||
|
EventTarget.prototype.addEventListener = function(type, listener, options) {
|
||||||
|
// Only patch touchstart and touchmove if options is not explicitly passive
|
||||||
|
if (
|
||||||
|
(type === 'touchstart' || type === 'touchmove') &&
|
||||||
|
(options === undefined || options === false || (typeof options === 'object' && !options.passive))
|
||||||
|
) {
|
||||||
|
options = options || {};
|
||||||
|
if (typeof options === 'object') {
|
||||||
|
options.passive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return origAddEventListener.call(this, type, listener, options);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
@@ -1216,7 +1216,11 @@ class Form {
|
|||||||
'placeholder' => esc_html__( '-- Choose related field', 'formipay' )
|
'placeholder' => esc_html__( '-- Choose related field', 'formipay' )
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'nonce' => wp_create_nonce('formipay-form-editor')
|
'nonce' => wp_create_nonce('formipay-form-editor'),
|
||||||
|
'multicurrency' => formipay_is_multi_currency_active(),
|
||||||
|
'all_currencies' => formipay_currency_as_options(),
|
||||||
|
'global_selected_currencies' => formipay_global_currency_options(),
|
||||||
|
'default_currency' => formipay_default_currency()
|
||||||
] );
|
] );
|
||||||
|
|
||||||
wp_enqueue_media();
|
wp_enqueue_media();
|
||||||
|
|||||||
10
includes/Integration/ExchangeRateAPI.php
Normal file
10
includes/Integration/ExchangeRateAPI.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace Formipay\Integration;
|
||||||
|
use Formipay\Traits\SingletonTrait;
|
||||||
|
use Formipay\Payment\Payment;
|
||||||
|
// Exit if accessed directly
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
class ExchangeRateAPI extends Payment {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -59,6 +59,24 @@ abstract class Payment {
|
|||||||
'submenu' => __( 'General', 'formipay' ),
|
'submenu' => __( 'General', 'formipay' ),
|
||||||
'group' => 'started'
|
'group' => 'started'
|
||||||
),
|
),
|
||||||
|
'allowed_currencies' => array(
|
||||||
|
'type' => 'multi_checkbox',
|
||||||
|
'searchable' => true,
|
||||||
|
'required' => true,
|
||||||
|
'label' => __( 'Allowed Currencies', 'formipay' ),
|
||||||
|
'options' => formipay_global_currency_options(),
|
||||||
|
'submenu' => __( 'General', 'formipay' ),
|
||||||
|
'description' => __( 'Activate multicurrency and set more than one currency to enable this option. Default, only default currency is allowed.', 'formipay' )
|
||||||
|
),
|
||||||
|
'default_currencies' => array(
|
||||||
|
'type' => 'select',
|
||||||
|
'searchable' => true,
|
||||||
|
'required' => true,
|
||||||
|
'label' => __( 'Default Currency', 'formipay' ),
|
||||||
|
'options' => formipay_global_currency_options(),
|
||||||
|
'submenu' => __( 'General', 'formipay' ),
|
||||||
|
'description' => __( 'First apply currency before buyer select.', 'formipay' )
|
||||||
|
),
|
||||||
'payment_section_title' => array(
|
'payment_section_title' => array(
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'label' => __( 'Payment Section Title', 'formipay' ),
|
'label' => __( 'Payment Section Title', 'formipay' ),
|
||||||
@@ -102,10 +120,10 @@ abstract class Payment {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$channels['payment_tablet_columns'] = array(
|
$channels['payment_tablet_columns'] = array(
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'label' => __( 'Tablet View', 'formipay' ),
|
'label' => __( 'Tablet View', 'formipay' ),
|
||||||
'submenu' => __( 'General', 'formipay' ),
|
'submenu' => __( 'General', 'formipay' ),
|
||||||
'value' => 3
|
'value' => 3
|
||||||
);
|
);
|
||||||
|
|
||||||
$channels['payment_mobile_columns'] = array(
|
$channels['payment_mobile_columns'] = array(
|
||||||
|
|||||||
@@ -57,6 +57,30 @@ class Settings {
|
|||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'label' => __( 'Enable Multi Currency', 'formipay' )
|
'label' => __( 'Enable Multi Currency', 'formipay' )
|
||||||
],
|
],
|
||||||
|
'enable_auto_exchangerate' => [
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __( 'Enable Auto Exchange Rate', 'formipay' ),
|
||||||
|
'dependency' => [
|
||||||
|
'key' => 'enable_multicurrency',
|
||||||
|
'value' => 'not_empty'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'enable_auto_exchangerate_apikey' => [
|
||||||
|
'type' => 'text',
|
||||||
|
'label' => __( 'Auto Exchange Rate API Key', 'formipay' ),
|
||||||
|
'required' => true,
|
||||||
|
'dependency' => [
|
||||||
|
[
|
||||||
|
'key' => 'enable_multicurrency',
|
||||||
|
'value' => 'not_empty'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'enable_auto_exchangerate',
|
||||||
|
'value' => 'not_empty'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'dependencies' => '&&'
|
||||||
|
],
|
||||||
'multicurrencies' => [
|
'multicurrencies' => [
|
||||||
'type' => 'repeater',
|
'type' => 'repeater',
|
||||||
'label' => __( 'Currencies', 'formipay' ),
|
'label' => __( 'Currencies', 'formipay' ),
|
||||||
@@ -94,13 +118,12 @@ class Settings {
|
|||||||
'options' => $payment_checkboxes,
|
'options' => $payment_checkboxes,
|
||||||
'submenu' => __( 'General', 'formipay' ),
|
'submenu' => __( 'General', 'formipay' ),
|
||||||
),
|
),
|
||||||
'payment_gateways_select' => array(
|
'exchange_rate' => array(
|
||||||
'type' => 'multiselect',
|
'type' => 'number',
|
||||||
'label' => __( 'Payment Gateways', 'formipay' ),
|
'label' => __( 'Manual Exchange Rate', 'formipay' ),
|
||||||
'options' => $payment_checkboxes,
|
'description' => __( 'This value is the exchange rate of default currency against this currency. If this currency selected, total order will be multiplied to this value. <b>This override the value from ExchangeRatePI if enabled</b>', 'formipay' ),
|
||||||
'submenu' => __( 'General', 'formipay' ),
|
'submenu' => __( 'General', 'formipay' ),
|
||||||
'placeholder' => 'Select related Payments'
|
)
|
||||||
),
|
|
||||||
],
|
],
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'dependency' => [
|
'dependency' => [
|
||||||
|
|||||||
39
readme.txt
39
readme.txt
@@ -76,4 +76,41 @@ Developed by Dwindi Ramadhana.
|
|||||||
|
|
||||||
== Privacy ==
|
== Privacy ==
|
||||||
|
|
||||||
Formipay collects and processes user data in accordance with GDPR. Please review and customize the included privacy policy template for your site.
|
Formipay collects and processes user data in accordance with GDPR. Please review and customize the included privacy policy template for your site.
|
||||||
|
|
||||||
|
== Mermaid Multi-Currency Implementation ==
|
||||||
|
|
||||||
|
flowchart TD
|
||||||
|
GS[Global Settings]
|
||||||
|
GS -->|Toggle: Multi‑Currency ON/OFF| MODE{Mode}
|
||||||
|
|
||||||
|
%% SINGLE-CURRENCY MODE
|
||||||
|
MODE -->|OFF| SC[Single‑Currency Mode]
|
||||||
|
SC -->|GS Default Currency only| FS1[Form Settings]
|
||||||
|
SC -->|GS Default Currency only| PS1[Product Settings]
|
||||||
|
FS1 --> CO1[Checkout]
|
||||||
|
PS1 --> CO1
|
||||||
|
CO1 --> ORD1[Order Stored :: currency = GS default]
|
||||||
|
ORD1 --> RPT1[Reports for single currency]
|
||||||
|
|
||||||
|
%% MULTI-CURRENCY MODE
|
||||||
|
MODE -->|ON| MC[Multi‑Currency Mode]
|
||||||
|
GS -->|Enabled Currencies + Rates| FS[Form Settings]
|
||||||
|
GS -->|Enabled Currencies + Rates| PS[Product Settings xN]
|
||||||
|
|
||||||
|
FS -->|FS.allowed ⊆ GS.enabled\nFS.default ∈ FS.allowed| CK[Checkout]
|
||||||
|
PS -->|Per Product:\nBase currency default GS\nManual overrides optional\nDerive from base via GS toggle| CK
|
||||||
|
|
||||||
|
CK -->|Compute CheckoutAllowed =\nFS.allowed ∩ as ProductSupported p| ALLOWED{CheckoutAllowed empty?}
|
||||||
|
ALLOWED -->|Yes| BLOCK[Block checkout + Admin diagnostic:\nEnable derive / add manual prices /\nadjust FS.allowed / remove product]
|
||||||
|
ALLOWED -->|No| CUR[Buyer selects currency ∈ CheckoutAllowed]
|
||||||
|
|
||||||
|
CUR --> PAY[Filter payment gateways by selected currency]
|
||||||
|
PAY --> TOT[Compute totals:\nManual price → else derive via GS]
|
||||||
|
TOT --> ORD[Persist Order:\norder_currency, total_in_order_currency,\nfx_rate_used, report_total_in_GS_base]
|
||||||
|
ORD --> RPT[Reports:\nSum in GS base with per‑currency breakdown option]
|
||||||
|
|
||||||
|
%% CATALOG (when MC=ON)
|
||||||
|
MC --> CAT[Catalog]
|
||||||
|
CAT -->|Query: currency=USD,IDR,AUTO| RESOLVE[Resolve display currency AUTO→pref/GS default]
|
||||||
|
RESOLVE --> FILT[Filter products strictly:\nshow only products supporting the display currency\n derive ok if product toggle is ON]
|
||||||
17
vendor/wpcfto/metaboxes/assets/js/metaboxes.js
vendored
17
vendor/wpcfto/metaboxes/assets/js/metaboxes.js
vendored
@@ -126,9 +126,18 @@
|
|||||||
if ($boxChild.hasClass('repeater')) {
|
if ($boxChild.hasClass('repeater')) {
|
||||||
// Repeater parent label (the field label for the repeater itself)
|
// Repeater parent label (the field label for the repeater itself)
|
||||||
const parentLabel = ($boxChild.find('.wpcfto-field-aside__label span:first-child').first().text() || '').trim();
|
const parentLabel = ($boxChild.find('.wpcfto-field-aside__label span:first-child').first().text() || '').trim();
|
||||||
|
|
||||||
// checker for the parent itself
|
// Determine if this repeater is required.
|
||||||
if($boxChild.find('.wpcfto-repeater-single').length == 0){
|
// We prefer presence of the hidden proxy input (rendered only when required).
|
||||||
|
// Fallback: a data attribute marker if used by templates.
|
||||||
|
const isRequiredRepeater = (
|
||||||
|
$boxChild.find('.wpcfto-required-proxy').length > 0 ||
|
||||||
|
$boxChild.is('[data-required="true"]')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parent-level empty check: only flag when the repeater itself is required
|
||||||
|
const hasRows = $boxChild.find('.wpcfto-repeater-single').length > 0;
|
||||||
|
if (isRequiredRepeater && !hasRows) {
|
||||||
invalid.push({
|
invalid.push({
|
||||||
id: fieldId,
|
id: fieldId,
|
||||||
tab: tabTitle,
|
tab: tabTitle,
|
||||||
@@ -137,6 +146,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Child-level checks: scan only inputs that explicitly declare [required]
|
||||||
$boxChild.find('.wpcfto-repeater-single').each(function (idx) {
|
$boxChild.find('.wpcfto-repeater-single').each(function (idx) {
|
||||||
const $item = $(this);
|
const $item = $(this);
|
||||||
|
|
||||||
@@ -156,6 +166,7 @@
|
|||||||
}
|
}
|
||||||
if (!repeaterLabel) repeaterLabel = `Item #${idx+1}`;
|
if (!repeaterLabel) repeaterLabel = `Item #${idx+1}`;
|
||||||
|
|
||||||
|
// Only required child fields should be considered invalid when empty
|
||||||
$item.find('input, textarea, select').filter('[required]').each(function () {
|
$item.find('input, textarea, select').filter('[required]').each(function () {
|
||||||
const $f = $(this);
|
const $f = $(this);
|
||||||
if (isRequiredEmpty($f)) {
|
if (isRequiredEmpty($f)) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
window.validationMixin = {
|
window.validationMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
// --- Helpers
|
// --- Helpers
|
||||||
@@ -24,6 +23,12 @@ window.validationMixin = {
|
|||||||
if (Array.isArray(a) || Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b);
|
if (Array.isArray(a) || Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b);
|
||||||
return String(a) == String(b);
|
return String(a) == String(b);
|
||||||
},
|
},
|
||||||
|
_coalesceValue() {
|
||||||
|
// Prefer prop field_value if present; otherwise fall back to instance `value`
|
||||||
|
if (typeof this.field_value !== 'undefined') return this.field_value;
|
||||||
|
if (typeof this.value !== 'undefined') return this.value;
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
|
||||||
// --- Visibility according to dependencies
|
// --- Visibility according to dependencies
|
||||||
isVisible() {
|
isVisible() {
|
||||||
@@ -56,7 +61,7 @@ window.validationMixin = {
|
|||||||
const visible = this.isVisible();
|
const visible = this.isVisible();
|
||||||
const required = 'required' in this.fields && this.fields.required === true;
|
const required = 'required' in this.fields && this.fields.required === true;
|
||||||
const type = this.fields.type || '';
|
const type = this.fields.type || '';
|
||||||
const value = this.field_value;
|
const value = this._coalesceValue();
|
||||||
|
|
||||||
let filled;
|
let filled;
|
||||||
if (!required) {
|
if (!required) {
|
||||||
@@ -66,8 +71,18 @@ window.validationMixin = {
|
|||||||
filled = true;
|
filled = true;
|
||||||
} else if (type === 'checkbox') {
|
} else if (type === 'checkbox') {
|
||||||
filled = value === 1 || value === true || value === '1' || value === 'true';
|
filled = value === 1 || value === true || value === '1' || value === 'true';
|
||||||
} else if (type === 'repeater' && required) {
|
} else if (type === 'repeater') {
|
||||||
filled = this.fields.value && this.fields.value.length > 0;
|
if (!required) {
|
||||||
|
filled = true; // optional repeater: always valid
|
||||||
|
} else {
|
||||||
|
// Prefer the component's live repeater array; fallback to value/field_value
|
||||||
|
const list = Array.isArray(this.repeater)
|
||||||
|
? this.repeater
|
||||||
|
: (Array.isArray(value)
|
||||||
|
? value
|
||||||
|
: (this.fields && Array.isArray(this.fields.value) ? this.fields.value : []));
|
||||||
|
filled = list.length > 0;
|
||||||
|
}
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
filled = value.length > 0;
|
filled = value.length > 0;
|
||||||
} else if (typeof value === 'number') {
|
} else if (typeof value === 'number') {
|
||||||
@@ -95,6 +110,11 @@ window.validationMixin = {
|
|||||||
if (typeof this.validateField === 'function') {
|
if (typeof this.validateField === 'function') {
|
||||||
this.validateField();
|
this.validateField();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
value() {
|
||||||
|
if (typeof this.validateField === 'function') {
|
||||||
|
this.validateField();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ $field = "data['{$section_name}']['fields']['{$field_name}']";
|
|||||||
:field_id="'<?php echo esc_attr( $field_id ); ?>'"
|
:field_id="'<?php echo esc_attr( $field_id ); ?>'"
|
||||||
:field_value="<?php echo esc_attr( $field_value ); ?>"
|
:field_value="<?php echo esc_attr( $field_value ); ?>"
|
||||||
:field_data='<?php echo esc_attr( htmlspecialchars( wp_json_encode( $field_data ) ) ); ?>'
|
:field_data='<?php echo esc_attr( htmlspecialchars( wp_json_encode( $field_data ) ) ); ?>'
|
||||||
@wpcfto-get-value="<?php echo esc_attr( $field_value ); ?> = $event">
|
@wpcfto-get-value="$set(<?php echo esc_attr( $field ); ?>, 'value', $event)">
|
||||||
</wpcfto_autocomplete>
|
</wpcfto_autocomplete>
|
||||||
|
|||||||
2
vendor/wpcfto/metaboxes/fields/repeater.php
vendored
2
vendor/wpcfto/metaboxes/fields/repeater.php
vendored
@@ -23,5 +23,5 @@ wp_enqueue_script('my-super-component', STM_WPCFTO_URL . '/metaboxes/general_com
|
|||||||
v-bind:field_id="'<?php echo esc_attr($field_id); ?>'"
|
v-bind:field_id="'<?php echo esc_attr($field_id); ?>'"
|
||||||
v-bind:field_value="<?php echo esc_attr($field_value); ?>"
|
v-bind:field_value="<?php echo esc_attr($field_value); ?>"
|
||||||
v-bind:field_data='<?php echo str_replace("'", "", json_encode($field_data)); ?>'
|
v-bind:field_data='<?php echo str_replace("'", "", json_encode($field_data)); ?>'
|
||||||
@wpcfto-get-value="$set(<?php echo esc_attr($field) ?>, 'value', $event)"
|
@wpcfto-get-value="$set(<?php echo esc_attr($field); ?>, 'value', $event)"
|
||||||
></wpcfto_repeater>
|
></wpcfto_repeater>
|
||||||
@@ -59,9 +59,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
:name="field_name"
|
:name="field_name"
|
||||||
v-model="value"
|
:value="serializedValue"
|
||||||
:required="fields.required === true"
|
:required="fields && fields.required === true"
|
||||||
|
:disabled="!(fields && fields.required === true) && (!value || (Array.isArray(value) && value.length === 0))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,6 +71,11 @@
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
computed: {
|
computed: {
|
||||||
|
serializedValue() {
|
||||||
|
const v = this.value;
|
||||||
|
if (Array.isArray(v)) return v.join(',');
|
||||||
|
return v || '';
|
||||||
|
},
|
||||||
computedPlaceholder() {
|
computedPlaceholder() {
|
||||||
// Default placeholder template or fallback
|
// Default placeholder template or fallback
|
||||||
const template = formipay_admin?.config?.autocomplete?.placeholder || 'Search {field_label}...';
|
const template = formipay_admin?.config?.autocomplete?.placeholder || 'Search {field_label}...';
|
||||||
@@ -92,6 +98,9 @@
|
|||||||
} else {
|
} else {
|
||||||
this.limit = 5; // default limit
|
this.limit = 5; // default limit
|
||||||
}
|
}
|
||||||
|
if (!this.field_value) {
|
||||||
|
this.value = [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|||||||
@@ -34,15 +34,22 @@ Vue.component('wpcfto_repeater', {
|
|||||||
<div class="wpcfto-field-content">
|
<div class="wpcfto-field-content">
|
||||||
<!-- Required proxy: forces validation when repeater is empty -->
|
<!-- Required proxy: forces validation when repeater is empty -->
|
||||||
<input
|
<input
|
||||||
|
v-if="fields && fields.required === true"
|
||||||
class="wpcfto-required-proxy"
|
class="wpcfto-required-proxy"
|
||||||
type="text"
|
type="text"
|
||||||
:name="field_name + '__required__'"
|
:name="field_id + '__required__'"
|
||||||
:value="(repeater && repeater.length ? 'ok' : '')"
|
:value="(repeater && repeater.length ? 'ok' : '')"
|
||||||
:required="fields && fields.required === true && (!repeater || repeater.length === 0)"
|
:required="!repeater || repeater.length === 0"
|
||||||
:disabled="!(fields && fields.required === true && (!repeater || repeater.length === 0))"
|
:disabled="repeater && repeater.length > 0"
|
||||||
tabindex="-1" readonly
|
tabindex="-1" readonly
|
||||||
style="position:absolute; left:-9999px; top:auto; width:1px; height:1px; opacity:0; pointer-events:none;"
|
style="position:absolute; left:-9999px; top:auto; width:1px; height:1px; opacity:0; pointer-events:none;"
|
||||||
/>
|
/>
|
||||||
|
<!-- Hidden data carrier to persist repeater rows via standard form submit -->
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
:name="field_name"
|
||||||
|
:value="jsonValue"
|
||||||
|
/>
|
||||||
<div v-for="(area, area_key) in repeater" class="wpcfto-repeater-single" :class="'wpcfto-repeater_' + field_name + '_' + area_key">
|
<div v-for="(area, area_key) in repeater" class="wpcfto-repeater-single" :class="'wpcfto-repeater_' + field_name + '_' + area_key">
|
||||||
<div class="wpcfto_group_title" @click="toggleArea(area)" v-html="getGroupTitle(area, area_key)"></div>
|
<div class="wpcfto_group_title" @click="toggleArea(area)" v-html="getGroupTitle(area, area_key)"></div>
|
||||||
<div class="repeater_inner" :class="{ closed: area.closed_tab }">
|
<div class="repeater_inner" :class="{ closed: area.closed_tab }">
|
||||||
@@ -94,44 +101,74 @@ Vue.component('wpcfto_repeater', {
|
|||||||
item.closed_tab = true;
|
item.closed_tab = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Normalize rows to include only declared fields (copy defaults if missing)
|
||||||
|
if (this.fields && this.fields.fields) {
|
||||||
|
this.repeater = this.repeater.map((row) => {
|
||||||
|
const normalized = { closed_tab: true };
|
||||||
|
Object.keys(this.fields.fields).forEach((fname) => {
|
||||||
|
if (typeof row[fname] !== 'undefined') {
|
||||||
|
normalized[fname] = row[fname];
|
||||||
|
} else {
|
||||||
|
const def = this.fields.fields[fname] && this.fields.fields[fname].value;
|
||||||
|
if (typeof def !== 'undefined') normalized[fname] = def;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return normalized;
|
||||||
|
});
|
||||||
|
}
|
||||||
// Initial validation state
|
// Initial validation state
|
||||||
if (typeof this.validateField === 'function') this.validateField();
|
if (typeof this.validateField === 'function') this.validateField();
|
||||||
// Block Save when repeater is required but empty (length check only)
|
if (this.fields && this.fields.required === true) {
|
||||||
const handler = (e) => {
|
// Block Save when repeater is required but empty (length check only)
|
||||||
// Only enforce when this component is in DOM
|
const handler = (e) => {
|
||||||
if (!this.$el || !document.body.contains(this.$el)) return;
|
// Only enforce when this component is in DOM
|
||||||
const emptyRequired = !!(this.fields && this.fields.required === true && (!this.repeater || this.repeater.length === 0));
|
if (!this.$el || !document.body.contains(this.$el)) return;
|
||||||
if (emptyRequired) {
|
const emptyRequired = !this.repeater || this.repeater.length === 0;
|
||||||
e.preventDefault();
|
if (emptyRequired) {
|
||||||
e.stopPropagation();
|
e.preventDefault();
|
||||||
this.focusSelf();
|
e.stopPropagation();
|
||||||
}
|
this.focusSelf();
|
||||||
};
|
}
|
||||||
try {
|
};
|
||||||
const btns = document.querySelectorAll('.wpcfto_save_settings, .wpcfto_save_metabox');
|
try {
|
||||||
btns.forEach(btn => btn.addEventListener('click', handler, true));
|
const btns = document.querySelectorAll('.wpcfto_save_settings, .wpcfto_save_metabox');
|
||||||
this.__wpcftoSaveHandler = handler;
|
btns.forEach(btn => btn.addEventListener('click', handler, true));
|
||||||
} catch(e) {}
|
this.__wpcftoSaveHandler = handler;
|
||||||
// Also prevent <form> submission if invalid (works for both settings + metabox)
|
} catch(e) {}
|
||||||
try {
|
// Also prevent <form> submission if invalid (works for both settings + metabox)
|
||||||
const form = this.$el.closest('form');
|
try {
|
||||||
if (form) {
|
const form = this.$el.closest('form');
|
||||||
const onSubmit = (e) => {
|
if (form) {
|
||||||
const emptyRequired = !!(this.fields && this.fields.required === true && (!this.repeater || this.repeater.length === 0));
|
const onSubmit = (e) => {
|
||||||
if (emptyRequired) {
|
const emptyRequired = !this.repeater || this.repeater.length === 0;
|
||||||
e.preventDefault();
|
if (emptyRequired) {
|
||||||
e.stopPropagation();
|
e.preventDefault();
|
||||||
this.focusSelf();
|
e.stopPropagation();
|
||||||
}
|
this.focusSelf();
|
||||||
};
|
}
|
||||||
form.addEventListener('submit', onSubmit, true);
|
};
|
||||||
this.__wpcftoFormSubmitHandler = onSubmit;
|
form.addEventListener('submit', onSubmit, true);
|
||||||
}
|
this.__wpcftoFormSubmitHandler = onSubmit;
|
||||||
} catch(e) {}
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
if (typeof this.validateField === 'function') this.validateField();
|
if (typeof this.validateField === 'function') this.validateField();
|
||||||
},
|
},
|
||||||
beforeDestroy: function () {
|
beforeDestroy: function () {
|
||||||
if (this.__teardown) this.__teardown();
|
if (this.__wpcftoSaveHandler) {
|
||||||
|
try {
|
||||||
|
const btns = document.querySelectorAll('.wpcfto_save_settings, .wpcfto_save_metabox');
|
||||||
|
btns.forEach(btn => btn.removeEventListener('click', this.__wpcftoSaveHandler, true));
|
||||||
|
} catch(e) {}
|
||||||
|
this.__wpcftoSaveHandler = null;
|
||||||
|
}
|
||||||
|
if (this.__wpcftoFormSubmitHandler) {
|
||||||
|
try {
|
||||||
|
const form = this.$el && this.$el.closest ? this.$el.closest('form') : null;
|
||||||
|
if (form) form.removeEventListener('submit', this.__wpcftoFormSubmitHandler, true);
|
||||||
|
} catch(e) {}
|
||||||
|
this.__wpcftoFormSubmitHandler = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
focusSelf() {
|
focusSelf() {
|
||||||
@@ -154,28 +191,41 @@ Vue.component('wpcfto_repeater', {
|
|||||||
}).filter(Boolean).join(' → ');
|
}).filter(Boolean).join(' → ');
|
||||||
},
|
},
|
||||||
validateField() {
|
validateField() {
|
||||||
let isValid = true;
|
// If this repeater is not required, always treat as valid and clean any invalid markers
|
||||||
if ('required' in this.fields && this.fields.required === true) {
|
if (!(this.fields && this.fields.required === true)) {
|
||||||
isValid = !!(this.fields.value.length > 0);
|
try { this.$el.classList.remove('wpcfto-invalid'); } catch(e){}
|
||||||
|
try { this.$el.removeAttribute('aria-invalid'); } catch(e){}
|
||||||
|
try {
|
||||||
|
const fid = this.field_id || (this.fields && this.fields.field_id);
|
||||||
|
if (fid) this.$root.$emit('field-validation', { fieldId: fid, isValid: true });
|
||||||
|
} catch(e) {}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
// Repeater-level required: valid iff it has at least one row
|
||||||
|
let isValid = true;
|
||||||
|
if (this.fields && this.fields.required === true) {
|
||||||
|
const len = (this.repeater && Array.isArray(this.repeater)) ? this.repeater.length : 0;
|
||||||
|
isValid = len > 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
try { this.$el.classList.add('wpcfto-invalid'); } catch(e){}
|
try { this.$el.classList.add('wpcfto-invalid'); } catch(e){}
|
||||||
try { this.$el.setAttribute('aria-invalid','true'); } catch(e){}
|
try { this.$el.setAttribute('aria-invalid','true'); } catch(e){}
|
||||||
// Tell the global collector (validationMixin) about our state
|
// Notify global validator (validationMixin listener)
|
||||||
try {
|
try {
|
||||||
const fid = this.field_id || (this.fields && this.fields.field_id);
|
const fid = this.field_id || (this.fields && this.fields.field_id);
|
||||||
if (fid) this.$root.$emit('field-validation', { fieldId: fid, isValid: false });
|
if (fid) this.$root.$emit('field-validation', { fieldId: fid, isValid: false });
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// valid state
|
||||||
try { this.$el.classList.remove('wpcfto-invalid'); } catch(e){}
|
try { this.$el.classList.remove('wpcfto-invalid'); } catch(e){}
|
||||||
try { this.$el.removeAttribute('aria-invalid'); } catch(e){}
|
try { this.$el.removeAttribute('aria-invalid'); } catch(e){}
|
||||||
try {
|
try {
|
||||||
const fid = this.field_id || (this.fields && this.fields.field_id);
|
const fid = this.field_id || (this.fields && this.fields.field_id);
|
||||||
if (fid) this.$root.$emit('field-validation', { fieldId: fid, isValid: true });
|
if (fid) this.$root.$emit('field-validation', { fieldId: fid, isValid: true });
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// This method validates all visible required fields in all rows before adding a new row
|
// This method validates all visible required fields in all rows before adding a new row
|
||||||
@@ -382,10 +432,40 @@ Vue.component('wpcfto_repeater', {
|
|||||||
repeater: {
|
repeater: {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler: function handler(repeater) {
|
handler: function handler(repeater) {
|
||||||
this.$emit('wpcfto-get-value', repeater);
|
// Build a clean payload with only declared inner fields
|
||||||
|
const payload = (Array.isArray(repeater) ? repeater : []).map((row) => {
|
||||||
|
const out = {};
|
||||||
|
if (this.fields && this.fields.fields) {
|
||||||
|
Object.keys(this.fields.fields).forEach((fname) => {
|
||||||
|
if (typeof row[fname] !== 'undefined') out[fname] = row[fname];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
this.$emit('wpcfto-get-value', payload);
|
||||||
this.validateField();
|
this.validateField();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
jsonValue() {
|
||||||
|
const list = Array.isArray(this.repeater) ? this.repeater : [];
|
||||||
|
const fieldsDef = (this.fields && this.fields.fields) ? this.fields.fields : null;
|
||||||
|
const payload = list.map((row) => {
|
||||||
|
const out = {};
|
||||||
|
if (fieldsDef) {
|
||||||
|
Object.keys(fieldsDef).forEach((fname) => {
|
||||||
|
if (typeof row[fname] !== 'undefined') out[fname] = row[fname];
|
||||||
|
else {
|
||||||
|
const def = fieldsDef[fname] && fieldsDef[fname].value;
|
||||||
|
if (typeof def !== 'undefined') out[fname] = def;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
try { return JSON.stringify(payload); } catch(e) { return '[]'; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ Vue.component('wpcfto_select', {
|
|||||||
dropdownOpen: false,
|
dropdownOpen: false,
|
||||||
// local reactive copy so we don't mutate props
|
// local reactive copy so we don't mutate props
|
||||||
localOptions: {}, // {value: label}
|
localOptions: {}, // {value: label}
|
||||||
localSearchable: false
|
localSearchable: false,
|
||||||
|
runtimeSubmenuClass: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -68,6 +69,48 @@ Vue.component('wpcfto_select', {
|
|||||||
this.localSearchable = !!(this.fields && this.fields.searchable);
|
this.localSearchable = !!(this.fields && this.fields.searchable);
|
||||||
this.value = this.field_value;
|
this.value = this.field_value;
|
||||||
|
|
||||||
|
// Attach submenu section class expected by initSubmenu()
|
||||||
|
let submenuClass, slug;
|
||||||
|
try {
|
||||||
|
const tabEl = this.$el.closest('.wpcfto-tab');
|
||||||
|
const tabId = tabEl && tabEl.getAttribute ? tabEl.getAttribute('id') : null;
|
||||||
|
const raw = (this.fields && this.fields.submenu) ? String(this.fields.submenu) : '';
|
||||||
|
slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
||||||
|
// initSubmenu() expects classes like `${tabId}_${slug}`
|
||||||
|
submenuClass = tabId ? (tabId + '_' + slug) : slug;
|
||||||
|
if (submenuClass) {
|
||||||
|
this.runtimeSubmenuClass = submenuClass;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Ensure submenu discovery works even if initSubmenu ran before this component mounted
|
||||||
|
try {
|
||||||
|
const active = document.querySelector('.wpcfto-submenus .active');
|
||||||
|
const activeKey = active ? active.getAttribute('data-submenu') : null;
|
||||||
|
if (submenuClass) {
|
||||||
|
// Also set data-submenu attr for selectors that rely on attributes
|
||||||
|
this.$el.setAttribute('data-submenu', submenuClass);
|
||||||
|
// if (activeKey && (activeKey === submenuClass || activeKey === slug)) {
|
||||||
|
// this.$el.style.removeProperty('display');
|
||||||
|
// this.$el.style.removeProperty('visibility');
|
||||||
|
// }
|
||||||
|
// no direct style changes; visibility is handled by initSubmenu()
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Listen for submenu changes to unhide this field when needed
|
||||||
|
try {
|
||||||
|
this.__onSubmenuClick = (ev) => {
|
||||||
|
const btn = ev.target.closest('[data-submenu]');
|
||||||
|
if (!btn) return;
|
||||||
|
const key = btn.getAttribute('data-submenu');
|
||||||
|
if (key && submenuClass && (key === submenuClass || key === slug)) {
|
||||||
|
// nothing needed; class binding ensures we have the right class and initSubmenu will reveal us
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', this.__onSubmenuClick, true);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
// Register for external control (optional but handy)
|
// Register for external control (optional but handy)
|
||||||
try {
|
try {
|
||||||
window.wpcftoSelectRegistry = window.wpcftoSelectRegistry || {};
|
window.wpcftoSelectRegistry = window.wpcftoSelectRegistry || {};
|
||||||
@@ -77,6 +120,10 @@ Vue.component('wpcfto_select', {
|
|||||||
document.addEventListener('click', this.handleClickOutside);
|
document.addEventListener('click', this.handleClickOutside);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
if (this.__onSubmenuClick) {
|
||||||
|
try { document.removeEventListener('click', this.__onSubmenuClick, true); } catch(e) {}
|
||||||
|
this.__onSubmenuClick = null;
|
||||||
|
}
|
||||||
document.removeEventListener('click', this.handleClickOutside);
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
if (window.wpcftoSelectRegistry) {
|
if (window.wpcftoSelectRegistry) {
|
||||||
delete window.wpcftoSelectRegistry[this.field_id];
|
delete window.wpcftoSelectRegistry[this.field_id];
|
||||||
@@ -162,7 +209,10 @@ Vue.component('wpcfto_select', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<div class="wpcfto_generic_field wpcfto_generic_field__select" :class="{ open: dropdownOpen }" :data-field="field_id">
|
<div
|
||||||
|
class="wpcfto_generic_field wpcfto_generic_field_select"
|
||||||
|
:class="['columns-' + (fields && fields.columns ? fields.columns : 1), runtimeSubmenuClass, { open: dropdownOpen }]"
|
||||||
|
:data-field="field_id">
|
||||||
|
|
||||||
<wpcfto_fields_aside_before :fields="fields" :field_label="field_label" :required="fields.required === true"></wpcfto_fields_aside_before>
|
<wpcfto_fields_aside_before :fields="fields" :field_label="field_label" :required="fields.required === true"></wpcfto_fields_aside_before>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user