update multicurrencies functionality on global level

This commit is contained in:
dwindown
2025-08-25 19:55:38 +07:00
parent 38b6b5cddb
commit ccb2b1aea1
21 changed files with 1240 additions and 476 deletions

View File

@@ -1,14 +1,22 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
const Repeater = Swal.mixin({
customClass: {
const Repeater = (window.Swal && typeof window.Swal.mixin === 'function')
? window.Swal.mixin({
customClass: {
container: 'wpcfto-settings',
confirmButton: 'button'
}
});
}
})
: {
// Fallback that mimics Swal.fire returning a Promise
fire: (opts) => Promise.resolve({
isConfirmed: window.confirm((opts && (opts.text || opts.html || opts.title)) || 'Are you sure?')
})
};
Vue.component('wpcfto_repeater', {
mixins: (window.validationMixin ? [window.validationMixin] : []),
props: ['fields', 'field_label', 'field_name', 'field_id', 'field_value'],
data: function data() {
return {
@@ -18,8 +26,23 @@ Vue.component('wpcfto_repeater', {
},
template: `
<div class="wpcfto_generic_field wpcfto_generic_field_repeater wpcfto-repeater unflex_fields">
<wpcfto_fields_aside_before :fields="fields" :field_label="field_label"></wpcfto_fields_aside_before>
<wpcfto_fields_aside_before
:fields="fields"
:field_label="field_label"
:required="fields && fields.required === true"
/>
<div class="wpcfto-field-content">
<!-- Required proxy: forces validation when repeater is empty -->
<input
class="wpcfto-required-proxy"
type="text"
:name="field_name + '__required__'"
:value="(repeater && repeater.length ? 'ok' : '')"
:required="fields && fields.required === true && (!repeater || repeater.length === 0)"
:disabled="!(fields && fields.required === true && (!repeater || repeater.length === 0))"
tabindex="-1" readonly
style="position:absolute; left:-9999px; top:auto; width:1px; height:1px; opacity:0; pointer-events:none;"
/>
<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="repeater_inner" :class="{ closed: area.closed_tab }">
@@ -71,10 +94,101 @@ Vue.component('wpcfto_repeater', {
item.closed_tab = true;
});
}
// Initial validation state
if (typeof this.validateField === 'function') this.validateField();
// Block Save when repeater is required but empty (length check only)
const handler = (e) => {
// Only enforce when this component is in DOM
if (!this.$el || !document.body.contains(this.$el)) return;
const emptyRequired = !!(this.fields && this.fields.required === true && (!this.repeater || this.repeater.length === 0));
if (emptyRequired) {
e.preventDefault();
e.stopPropagation();
this.focusSelf();
}
};
try {
const btns = document.querySelectorAll('.wpcfto_save_settings, .wpcfto_save_metabox');
btns.forEach(btn => btn.addEventListener('click', handler, true));
this.__wpcftoSaveHandler = handler;
} catch(e) {}
// Also prevent <form> submission if invalid (works for both settings + metabox)
try {
const form = this.$el.closest('form');
if (form) {
const onSubmit = (e) => {
const emptyRequired = !!(this.fields && this.fields.required === true && (!this.repeater || this.repeater.length === 0));
if (emptyRequired) {
e.preventDefault();
e.stopPropagation();
this.focusSelf();
}
};
form.addEventListener('submit', onSubmit, true);
this.__wpcftoFormSubmitHandler = onSubmit;
}
} catch(e) {}
if (typeof this.validateField === 'function') this.validateField();
},
beforeDestroy: function () {
if (this.__teardown) this.__teardown();
},
methods: {
focusSelf() {
try {
const rect = this.$el.getBoundingClientRect();
const top = window.pageYOffset + rect.top - 60;
window.scrollTo({ top, behavior: 'smooth' });
} catch(e) {}
try { this.$el.classList.add('wpcfto-invalid'); } catch(e) {}
},
// scrollIntoViewIfNeeded() {
// try {
// const top = window.pageYOffset + this.$el.getBoundingClientRect().top - 60;
// window.scrollTo({ top, behavior: 'smooth' });
// } catch(e) {}
// },
formatLabelPath(parts) {
return parts.filter(Boolean).map(function (s) {
return String(s).trim();
}).filter(Boolean).join(' → ');
},
validateField() {
let isValid = true;
if ('required' in this.fields && this.fields.required === true) {
isValid = !!(this.fields.value.length > 0);
}
if (!isValid) {
try { this.$el.classList.add('wpcfto-invalid'); } catch(e){}
try { this.$el.setAttribute('aria-invalid','true'); } catch(e){}
// Tell the global collector (validationMixin) about our state
try {
const fid = this.field_id || (this.fields && this.fields.field_id);
if (fid) this.$root.$emit('field-validation', { fieldId: fid, isValid: false });
} catch(e) {}
return false;
}
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;
},
// This method validates all visible required fields in all rows before adding a new row
validateAndAddArea: function() {
const fieldsInRepeater = this._props.fields.fields;
let group_title = null;
Object.values(fieldsInRepeater).forEach(field => {
if ('is_group_title' in field) {
if (field.is_group_title !== false) {
group_title = field.label;
}
}
});
var invalidFields = [];
// Loop through all repeater rows and all fields
this.repeater.forEach((area, area_key) => {
@@ -91,6 +205,8 @@ Vue.component('wpcfto_repeater', {
if (childComponent.isVisible && childComponent.fields && childComponent.fields.required) {
if (!childComponent.validateField()) {
invalidFields.push({
repeater_label: this._props.field_label,
group_title: group_title,
label: childComponent.fields.label || field_name_inner,
area: area_key + 1
});
@@ -102,20 +218,25 @@ Vue.component('wpcfto_repeater', {
}
});
if (invalidFields.length > 0) {
// Compose error message
var msg = 'Please fill all required fields before adding a new item:\n\n';
msg += invalidFields.map(f => 'Item #' + f.area + ': ' + f.label).join('\n');
// Use SweetAlert2 if available, otherwise alert()
if (typeof window.Swal === 'function') {
Repeater.fire({
icon: 'warning',
title: 'Required fields missing',
html: msg.replace(/\n/g, '<br>')
});
} else {
alert(msg);
}
return;
// Compose error message
var msg = 'Please fill all required fields before adding a new item:\n\n';
const firstInvalidItem = invalidFields[0];
msg += 'Check item #'+firstInvalidItem.area+': <b>'+this.formatLabelPath([firstInvalidItem.group_title, firstInvalidItem.label])+'</b>';
// Use SweetAlert2 if available, otherwise alert()
if (typeof window.Swal === 'function') {
Repeater.fire({
icon: 'warning',
title: 'Required fields missing',
html: msg.replace(/\n/g, '<br>'),
width: 'fit-content',
customClass: {
confirmButton: 'btn text-bg-primary'
},
});
} else {
alert(msg);
}
return;
}
// If all required fields are valid, add a new row
this.addArea();
@@ -134,6 +255,9 @@ Vue.component('wpcfto_repeater', {
}
});
this.validateField();
if (typeof jQuery !== 'undefined') {
jQuery(document).trigger('repeater-item-added', [this._props]);
}
},
toggleArea: function toggleArea(area) {
// Close all other rows
@@ -159,6 +283,10 @@ Vue.component('wpcfto_repeater', {
}).then((result) => {
if (result.isConfirmed) {
this.repeater.splice(areaIndex, 1);
// jQuery action hook trigger example:
if (typeof jQuery !== 'undefined') {
jQuery(document).trigger('repeater-item-removed', [this._props, areaIndex]);
}
}
});
},
@@ -243,9 +371,10 @@ Vue.component('wpcfto_repeater', {
return true;
},
saveChanges() {
if (!this.validateAllRows()) {
return; // Prevent save if validation fails
}
// Block if repeater required but empty OR if any child invalid
const selfValid = this.validateField();
// const rowsValid = this.validateAllRows();
if (!selfValid) { return; }
// Proceed with save logic here...
}
},
@@ -253,7 +382,8 @@ Vue.component('wpcfto_repeater', {
repeater: {
deep: true,
handler: function handler(repeater) {
this.$emit('wpcfto-get-value', repeater);
this.$emit('wpcfto-get-value', repeater);
this.validateField();
}
}
}