update multicurrencies functionality on global level
This commit is contained in:
2
vendor/wpcfto/metaboxes/assets/css/main.css
vendored
2
vendor/wpcfto/metaboxes/assets/css/main.css
vendored
File diff suppressed because one or more lines are too long
348
vendor/wpcfto/metaboxes/assets/js/metaboxes.js
vendored
348
vendor/wpcfto/metaboxes/assets/js/metaboxes.js
vendored
@@ -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: {
|
||||
|
||||
106
vendor/wpcfto/metaboxes/assets/js/validationMixin.js
vendored
106
vendor/wpcfto/metaboxes/assets/js/validationMixin.js
vendored
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user