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

File diff suppressed because one or more lines are too long

View File

@@ -19,33 +19,201 @@
}
});
function formatLabel(fieldId, str, tab) {
if(fieldId == str){
str = str
.split('-') // Split by hyphen
.map(part =>
part
.split('_') // Split by underscore
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) // Capitalize
.join(' ')
)
.join(' → ');
return str;
}
return tab + ' → ' + str;
function formatLabelPath(parts) {
return parts.filter(Boolean).map(function (s) {
return String(s).trim();
}).filter(Boolean).join(' → ');
}
function triggerSwalEmptyRequired(fieldId, firstLabel, tab) {
function triggerSwalEmptyRequired(arg1, firstLabel, tab) {
// Supports both old signature (fieldId, firstLabel, tab)
// and new signature (object { id, tab, group, repeater, label, parent })
var path = '';
var p;
if (typeof arg1 === 'object' && arg1 !== null) {
p = arg1;
// Include parent (repeater parent field label) when present
path = formatLabelPath([p.tab, p.group, p.parent, p.repeater, p.label]);
} else {
// Fallback to old behavior
if (firstLabel) {
path = formatLabelPath([tab, firstLabel]);
}
}
// Wider dialog for repeater breadcrumb to avoid wrapping
var isRepeaterContext = (typeof p !== 'undefined') && (!!p.parent || !!p.repeater);
Swal.fire({
icon: 'warning',
title: 'Empty Required Field',
html: firstLabel ? 'Check: <b>' + formatLabel(fieldId, firstLabel, tab) + '</b>' : '',
html: path ? 'Check: <b>' + path + '</b>' : '',
width: isRepeaterContext ? '50em' : undefined,
customClass: {
confirmButton: 'btn text-bg-primary'
},
didOpen: (popup) => {
if (isRepeaterContext) {
var htmlC = popup.querySelector('.swal2-html-container');
if (htmlC) {
htmlC.style.whiteSpace = 'nowrap';
}
}
}
});
}
// === DeweBox/Nuxy required/visibility helpers (global) ===
function isBoxChildVisible($box, includeHidden) {
if (includeHidden === true) return true; // scan *all* tabs/sections
return $box.is(':visible');
}
function readFieldValue($input) {
const id = $input.attr('id');
const tag = $input.prop('tagName');
const type= ($input.attr('type') || '').toLowerCase();
// TinyMCE
if (tag === 'TEXTAREA' && typeof window.tinymce !== 'undefined') {
const ed = tinymce.get(id);
if (ed) return ed.getContent();
}
// CodeMirror via WP code editor (textarea sibling with .CodeMirror)
if (tag === 'TEXTAREA' && window.wp && wp.codeEditor) {
const cmWrap = $input.next('.CodeMirror');
if (cmWrap.length && cmWrap[0].CodeMirror) {
return cmWrap[0].CodeMirror.getValue();
}
}
if (type === 'checkbox' || type === 'radio') {
const name = $input.attr('name');
return !!$(`[name="${name}"]:checked`).length;
}
return $.trim($input.val() || '');
}
function isRequiredEmpty($input) {
if (!$input.is('[required]')) return false;
const tag = $input.prop('tagName');
const type = ($input.attr('type') || '').toLowerCase();
if (type === 'checkbox' || type === 'radio') {
const name = $input.attr('name');
return !$(`[name="${name}"]:checked`).length;
}
const v = readFieldValue($input);
return v === '' || v === null || typeof v === 'undefined';
}
function collectInvalidFields(includeHidden) {
const invalid = [];
// Ensure TinyMCE mirrors content back to textarea before reading values
if (window.tinymce) tinymce.triggerSave();
$('.wpcfto-box-child').each(function () {
const $boxChild = $(this);
const fieldId = $boxChild.attr('data-field');
const $tab = $boxChild.closest('.wpcfto-tab');
const tabId = $tab.attr('id');
const tabTitle = $(`[data-section=${tabId}]`).text();
if (!isBoxChildVisible($boxChild, includeHidden)) return;
// Try to find nearest group title before this field
const groupLabel = ($boxChild.prevAll('.wpcfto_group_started:first')
.find('.wpcfto-field-aside__label span:first-child').text() || '').trim();
if ($boxChild.hasClass('repeater')) {
// Repeater parent label (the field label for the repeater itself)
const parentLabel = ($boxChild.find('.wpcfto-field-aside__label span:first-child').first().text() || '').trim();
// checker for the parent itself
if($boxChild.find('.wpcfto-repeater-single').length == 0){
invalid.push({
id: fieldId,
tab: tabTitle,
group: groupLabel,
parent: parentLabel
});
}
$boxChild.find('.wpcfto-repeater-single').each(function (idx) {
const $item = $(this);
// Repeater item label heuristics (prefer the Vue-rendered title)
const titleEl = $item.find('.wpcfto_group_title').first();
let repeaterLabel = (titleEl.text() || '').toString().trim();
if (!repeaterLabel) {
repeaterLabel = (
$item.find('[data-repeater-label]').val() ||
$item.find('[name*="[label]"]').val() ||
$item.find('[name*="[title]"]').val() ||
$item.find('[name*="[name]"]').val() ||
$item.find('.wpcfto-repeater-title, .wpcfto-repeater-item-label').first().text() ||
''
).toString().trim();
}
if (!repeaterLabel) repeaterLabel = `Item #${idx+1}`;
$item.find('input, textarea, select').filter('[required]').each(function () {
const $f = $(this);
if (isRequiredEmpty($f)) {
const emptyLabel = $f.closest('.wpcfto_generic_field')
.find('.wpcfto-field-aside__label span:first-child').text() || fieldId;
invalid.push({
id: fieldId,
tab: tabTitle,
group: groupLabel,
parent: parentLabel,
repeater: repeaterLabel,
label: emptyLabel
});
}
});
});
return;
}
$boxChild.find('input, textarea, select').filter('[required]').each(function () {
const $f = $(this);
if (isRequiredEmpty($f)) {
const fieldLabel = $(`[data-field=${fieldId}] label > span:first-child`).text() || fieldId;
invalid.push({
id: fieldId,
tab: tabTitle,
group: groupLabel,
repeater: null,
label: fieldLabel
});
}
});
});
return invalid;
}
function firstInvalidField(includeHidden) {
const inv = collectInvalidFields(includeHidden);
return inv.length ? inv[0] : null;
}
function focusFirstInvalid(invalid) {
if (!invalid) return;
const $targetTab = $(`[data-field=${invalid.id}]`).closest('.wpcfto-tab');
const tabId = $targetTab.attr('id');
if (tabId) {
// try Vue's changeTab if available
if (typeof vm !== 'undefined' && vm.changeTab) {
vm.changeTab(tabId);
} else {
$('#' + tabId).addClass('active').siblings('.wpcfto-tab').removeClass('active');
$(`[data-section="${tabId}"]`).closest('.wpcfto-nav').addClass('active').siblings('.wpcfto-nav').removeClass('active');
}
$('html, body').animate({ scrollTop: $(`[data-field=${invalid.id}]`).offset().top - 120 }, 'fast');
}
}
$('[data-vue]').each(function () {
var $this = $(this);
var data_var = $this.attr('data-vue');
@@ -77,96 +245,56 @@
mounted: function mounted() {
this.getSettings();
this.clearEmptyGroups();
// Intercept #publish (WP Update/Publish) button
// Intercept Classic Editor Publish/Save buttons
const vm = this;
$(document).on('click', '#publish', function(e) {
vm.validateAllFields();
const invalidField = vm.validateAllFields();
if (invalidField) {
e.preventDefault();
let firstInvalidFieldId = invalidField.id;
let firstLabel = invalidField.label;
let tabTitle = invalidField.tab;
if (typeof window.Swal === 'function') {
triggerSwalEmptyRequired(firstInvalidFieldId, firstLabel, tabTitle);
} else {
alert('Please fill all required fields before saving.' + (firstLabel ? '\nFirst missing: ' + firstLabel : ''));
vm.$nextTick(() => {
$(document).off('click.deweboxPublish').on('click.deweboxPublish', '#publish, #save-post', function(e) {
const invalidField = firstInvalidField(true);
if (invalidField) {
e.preventDefault();
if (typeof window.Swal === 'function') {
triggerSwalEmptyRequired(invalidField);
} else {
alert('Please fill all required fields before saving.' + (invalidField.label ? '\nFirst missing: ' + invalidField.label : ''));
}
focusFirstInvalid(invalidField);
return false;
}
return false;
}
});
});
// Gutenberg: lock publish when invalid and show SweetAlert on attempt
if (window.wp && wp.data && wp.data.select('core/editor')) {
const { subscribe, dispatch } = wp.data;
const LOCK_KEY = 'wpcfto-required-lock';
const applyLock = () => {
const invalid = firstInvalidField(true);
if (invalid) {
dispatch('core/editor').lockPostSaving(LOCK_KEY);
} else {
dispatch('core/editor').unlockPostSaving(LOCK_KEY);
}
};
applyLock();
subscribe(applyLock);
$(document).off('click.deweboxGutenberg').on('click.deweboxGutenberg', '.editor-post-publish-button, .editor-post-publish-panel__toggle', function(e){
const invalid = firstInvalidField(true);
if (invalid) {
e.preventDefault();
triggerSwalEmptyRequired(invalid);
focusFirstInvalid(invalid);
return false;
}
});
}
},
methods: {
validateAllFields: function validateAllFields() {
let domInvalidFields = [];
$('.wpcfto-box-child').each(function () {
const boxChild = $(this);
const fieldId = boxChild.attr('data-field');
const fieldTab = boxChild.closest('.wpcfto-tab').attr('id');
const fieldTabTitle = $(`[data-section=${fieldTab}]`).text();
const $inputs = boxChild.find('input, textarea, select');
if(boxChild.hasClass('repeater')){
const repeater_item = boxChild.find('.wpcfto-repeater-single');
repeater_item.each(function(key, field){
const _key = key+1;
const repeater_item_inputs = $(this).find('input, textarea, select');
repeater_item_inputs.each(function () {
const $field = $(this);
const name = $field.attr('name');
// if (!$field.is(':visible')) return;
if (!$field.is('[required]')) return;
let isEmpty = false;
if ($field.is(':checkbox') || $field.is(':radio')) {
if (!$(`[name="${name}"]`).val()) isEmpty = true;
} else {
if (!$.trim($field.val())) isEmpty = true;
}
if (isEmpty) {
const fieldSubTabTitle = fieldTabTitle +'→ Item #'+_key ;
const fieldSubLabel = $field.closest('.wpcfto_generic_field').find('.wpcfto-field-aside__label span:first-child').text()
domInvalidFields.push({
id: fieldId,
tab: fieldSubTabTitle,
label: fieldSubLabel
});
}
});
});
}else{
$inputs.each(function () {
const $field = $(this);
const name = $field.attr('name');
if (!$field.is('[required]')) return;
let isEmpty = false;
if ($field.is(':checkbox') || $field.is(':radio')) {
if (!$(`[name="${name}"]`).val()) isEmpty = true;
} else {
if (!$.trim($field.val())) isEmpty = true;
}
if (isEmpty) {
domInvalidFields.push({
id: fieldId,
tab: fieldTabTitle,
label: $(`[data-field=${fieldId}] label > span:first-child`).text()
});
}
});
}
});
if (domInvalidFields.length > 0) {
console.log(domInvalidFields);
return domInvalidFields[0]; // Return the first invalid field object
}
return null;
const invalid = firstInvalidField(true);
return invalid ? invalid : null;
},
getFieldLabelById(fieldId) {
let label = '';
@@ -256,17 +384,17 @@
});
},
saveSettings: function saveSettings(id) {
const invalidField = this.validateAllFields();
// Ensure editors sync back to DOM before validation/post
if (window.tinymce) tinymce.triggerSave();
const invalidField = firstInvalidField(true);
if (invalidField) {
let firstInvalidFieldId = invalidField.id;
let firstLabel = invalidField.label;
var firstTab = firstInvalidFieldId.split('-');
var _firstTab = firstTab[0];
if (typeof window.Swal === 'function') {
triggerSwalEmptyRequired(firstInvalidFieldId, firstLabel, _firstTab);
triggerSwalEmptyRequired(invalidField);
} else {
alert('Please fill all required fields before saving.' + (firstLabel ? '\nFirst missing: ' + firstLabel : ''));
alert('Please fill all required fields before saving.' + (invalidField.label ? '\nFirst missing: ' + invalidField.label : ''));
}
focusFirstInvalid(invalidField);
return;
}
@@ -346,7 +474,8 @@
});
},
addRepeaterRow() {
if (!this.validateAllFields()) {
const invalid = firstInvalidField(true);
if (invalid) {
if (typeof window.Swal === 'function') {
Toast.fire({
icon: "warning",
@@ -355,9 +484,10 @@
} else {
alert('Please fill all required fields before adding a new row.');
}
focusFirstInvalid(invalid);
return;
}
// Your existing logic to add repeater row
// TODO: your existing logic to actually add a repeater row goes here
}
},
watch: {

View File

@@ -1,54 +1,104 @@
window.validationMixin = {
methods: {
// --- Helpers
_depsArray() {
// Support both `dependencies: [...]` and legacy single `dependency: {...}`
if (this.fields && Array.isArray(this.fields.dependencies)) return this.fields.dependencies;
if (this.fields && this.fields.dependency && typeof this.fields.dependency === 'object') return [this.fields.dependency];
return null;
},
_isNotEmpty(val) {
if (val === undefined || val === null) return false;
if (typeof val === 'string') return val.trim() !== '';
if (Array.isArray(val)) return val.length > 0;
// numbers: 0 should be considered not-empty (valid) for required numeric fields
if (typeof val === 'number') return true;
if (typeof val === 'boolean') return val === true;
// objects: consider not empty if it has at least one key
if (typeof val === 'object') return Object.keys(val).length > 0;
return !!val;
},
_equals(a, b) {
// Loose compare for primitives, but normalize truthy/empty strings
if (Array.isArray(a) || Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b);
return String(a) == String(b);
},
// --- Visibility according to dependencies
isVisible() {
// If fields or dependencies do not exist, field is visible
if (!this.fields || !Array.isArray(this.fields.dependencies)) {
return true;
}
// dependencies is an array, safe to use every()
return this.fields.dependencies.every(dep => {
const deps = this._depsArray();
if (!deps) return true;
// Parent holds child refs by field key
return deps.every(dep => {
if (!dep || !dep.field) return true;
const depValue = this.$parent?.$refs[dep.field]?.value;
return depValue === dep.value;
// Prefer parent $refs value (set by Nuxy components)
const depValue = this.$parent?.$refs?.[dep.field]?.value;
// Operator
const op = dep.operator || dep.op || (dep.value === 'not_empty' ? 'not_empty' : 'equals');
if (op === 'not_empty') {
return this._isNotEmpty(depValue);
}
// Default equals
return this._equals(depValue, dep.value);
});
},
validateField() {
// Check if fields exists before accessing it
if (!this.fields) {
return true; // If fields is undefined, consider validation passed
}
const visible = this.isVisible();
const required = this.fields.required === true;
const value = this.field_value;
// Must be unique for each field instance!
// --- Required validation by type
validateField() {
if (!this.fields) return true;
const visible = this.isVisible();
const required = 'required' in this.fields && this.fields.required === true;
const type = this.fields.type || '';
const value = this.field_value;
let filled;
if (!required) {
filled = true;
} else if (!visible) {
// If not visible, treat as valid (global validator already skips hidden)
filled = true;
} else if (type === 'checkbox') {
filled = value === 1 || value === true || value === '1' || value === 'true';
} else if (type === 'repeater' && required) {
filled = this.fields.value && this.fields.value.length > 0;
} else if (Array.isArray(value)) {
filled = value.length > 0;
} else if (typeof value === 'number') {
// 0 is a valid number for required numeric fields
filled = true;
} else {
filled = this._isNotEmpty(value);
}
const uniqueFieldId = this.field_id || (this.fields && this.fields.field_id);
const isValid = !required || (visible && value !== undefined && value !== null && value !== '');
// Only emit if we have a field_id
// Emit once per change/mount with stable payload
if (uniqueFieldId) {
this.$root.$emit('field-validation', {
fieldId: uniqueFieldId,
isValid
isValid: !!filled
});
}
return isValid;
}
return !!filled;
},
},
watch: {
field_value() {
// Only call validateField if it exists
if (typeof this.validateField === 'function') {
this.validateField();
const isValid = this.validateField();
}
}
},
mounted() {
// Only call validateField if it exists
if (typeof this.validateField === 'function') {
this.validateField();
}