update multicurrencies functionality on global level
This commit is contained in:
@@ -2,148 +2,207 @@
|
||||
"use strict";
|
||||
|
||||
Vue.component('wpcfto_select', {
|
||||
mixins: [window.validationMixin],
|
||||
props: ['fields', 'field_label', 'field_name', 'field_id', 'field_value'],
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
searchTerm: '',
|
||||
dropdownOpen: false,
|
||||
$refs: {
|
||||
nativeSelect: null,
|
||||
searchBox: null
|
||||
}
|
||||
};
|
||||
mixins: [window.validationMixin],
|
||||
props: ['fields', 'field_label', 'field_name', 'field_id', 'field_value'],
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
searchTerm: '',
|
||||
dropdownOpen: false,
|
||||
// local reactive copy so we don't mutate props
|
||||
localOptions: {}, // {value: label}
|
||||
localSearchable: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
optionCount() {
|
||||
return Object.keys(this.localOptions || {}).length;
|
||||
},
|
||||
computed: {
|
||||
filteredOptions() {
|
||||
const options = this.fields.options || {};
|
||||
const selectedValue = this.value;
|
||||
|
||||
// If not searchable, show all options
|
||||
if (!this.fields.searchable) {
|
||||
return options;
|
||||
}
|
||||
|
||||
// If searchTerm is empty or less than 3 chars
|
||||
if (!this.searchTerm || this.searchTerm.length < 3) {
|
||||
// Show only selected option if exists
|
||||
if (selectedValue && options[selectedValue]) {
|
||||
return { [selectedValue]: options[selectedValue] };
|
||||
}
|
||||
// Otherwise show nothing
|
||||
return {};
|
||||
}
|
||||
|
||||
// If searchTerm has 3 or more chars, filter options
|
||||
const term = this.searchTerm.toLowerCase();
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).filter(([key, label]) =>
|
||||
label.toLowerCase().includes(term) || key.toLowerCase().includes(term)
|
||||
)
|
||||
);
|
||||
},
|
||||
selectedLabel() {
|
||||
return this.fields.options[this.value] || this.field_label || 'Select';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.value = this.field_value;
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
methods: {
|
||||
toggleDropdown() {
|
||||
this.dropdownOpen = !this.dropdownOpen;
|
||||
if (this.dropdownOpen) {
|
||||
this.searchTerm = '';
|
||||
}
|
||||
setTimeout(() => {
|
||||
const searchBox = this.$refs.searchBox;
|
||||
if (searchBox) {
|
||||
searchBox.focus()
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
selectOption(key) {
|
||||
this.value = key;
|
||||
this.$emit('wpcfto-get-value', key);
|
||||
this.dropdownOpen = false;
|
||||
// Manually trigger change event on hidden input
|
||||
const nativeSelect = this.$refs.nativeSelect;
|
||||
if (nativeSelect) {
|
||||
nativeSelect.value = key;
|
||||
const event = new Event('change', { bubbles: true });
|
||||
nativeSelect.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
handleClickOutside(event) {
|
||||
if (!this.$el.contains(event.target)) {
|
||||
this.dropdownOpen = false;
|
||||
}
|
||||
filteredOptions() {
|
||||
const options = this.localOptions || {};
|
||||
const selectedValue = this.value;
|
||||
|
||||
// Not searchable → show all
|
||||
if (!this.localSearchable) return options;
|
||||
|
||||
const count = Object.keys(options).length;
|
||||
const hasSearch = this.searchTerm && this.searchTerm.length >= 3;
|
||||
|
||||
// If small list (≤10): show all when no search term, else filter
|
||||
if (count <= 10) {
|
||||
if (!this.searchTerm) return options;
|
||||
const term = this.searchTerm.toLowerCase();
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).filter(([key, label]) =>
|
||||
String(label).toLowerCase().includes(term) ||
|
||||
String(key).toLowerCase().includes(term)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Large list (>10): keep the 3+ chars rule
|
||||
if (!hasSearch) {
|
||||
// Show only the selected option (if any)
|
||||
if (selectedValue && options[selectedValue]) {
|
||||
return { [selectedValue]: options[selectedValue] };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Filter when 3+ chars
|
||||
const term = this.searchTerm.toLowerCase();
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).filter(([key, label]) =>
|
||||
String(label).toLowerCase().includes(term) ||
|
||||
String(key).toLowerCase().includes(term)
|
||||
)
|
||||
);
|
||||
},
|
||||
template: `
|
||||
<div class="wpcfto_generic_field wpcfto_generic_field__select" :class="{ open: dropdownOpen }">
|
||||
selectedLabel() {
|
||||
return this.localOptions[this.value] || this.field_label || 'Select';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// make a local copy of props for reactivity and to avoid mutating props
|
||||
this.localOptions = Object.assign({}, (this.fields && this.fields.options) || {});
|
||||
this.localSearchable = !!(this.fields && this.fields.searchable);
|
||||
this.value = this.field_value;
|
||||
|
||||
<wpcfto_fields_aside_before :fields="fields" :field_label="field_label" :required="fields.required === true"></wpcfto_fields_aside_before>
|
||||
// Register for external control (optional but handy)
|
||||
try {
|
||||
window.wpcftoSelectRegistry = window.wpcftoSelectRegistry || {};
|
||||
window.wpcftoSelectRegistry[this.field_id] = this;
|
||||
} catch (e) {}
|
||||
|
||||
<div class="wpcfto-field-content">
|
||||
<!-- Render custom searchable select only if searchable -->
|
||||
<div v-if="fields.searchable" class="wpcfto-custom-select" :id="field_id" @click.stop="toggleDropdown">
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
if (window.wpcftoSelectRegistry) {
|
||||
delete window.wpcftoSelectRegistry[this.field_id];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// External API to replace options & pick the right value
|
||||
// currencies: {value: label}
|
||||
// multicurrencies: [{value, label}]
|
||||
updateCurrencyScope(currencies, multicurrencies = [], savedDefault = null) {
|
||||
let optionsToSet = {};
|
||||
if (Array.isArray(multicurrencies) && multicurrencies.length > 0) {
|
||||
// standardize array -> object
|
||||
multicurrencies.forEach(obj => {
|
||||
if (obj && obj.value != null) optionsToSet[obj.value] = obj.label || obj.value;
|
||||
});
|
||||
} else {
|
||||
optionsToSet = currencies || {};
|
||||
}
|
||||
|
||||
<div class="wpcfto-selected-value">
|
||||
{{ selectedLabel }}
|
||||
<span class="wpcfto-arrow" :class="{ open: dropdownOpen }">▾</span>
|
||||
</div>
|
||||
// replace options reactively
|
||||
this.localOptions = Object.assign({}, optionsToSet);
|
||||
|
||||
<div v-if="dropdownOpen" class="wpcfto-options-dropdown">
|
||||
<input ref="searchBox" type="text" class="wpcfto-select-search" v-model="searchTerm" placeholder="Search..." @click.stop />
|
||||
// decide the selected value
|
||||
const want = savedDefault != null ? savedDefault : this.value;
|
||||
if (want && Object.prototype.hasOwnProperty.call(this.localOptions, want)) {
|
||||
this.value = want;
|
||||
} else {
|
||||
// pick first option or empty
|
||||
const firstKey = Object.keys(this.localOptions)[0] || '';
|
||||
this.value = firstKey || '';
|
||||
}
|
||||
|
||||
<ul class="wpcfto-options-list" style="padding-left: 0;">
|
||||
<li v-for="(label, key) in filteredOptions" :key="key"
|
||||
:class="{ selected: key === value }"
|
||||
@click.stop="selectOption(key)">
|
||||
{{ label }}
|
||||
</li>
|
||||
<li v-if="Object.keys(filteredOptions).length === 0" class="no-options">No options found</li>
|
||||
</ul>
|
||||
</div>
|
||||
// notify & sync hidden input
|
||||
this.$nextTick(() => {
|
||||
this.emitAndSync();
|
||||
});
|
||||
},
|
||||
|
||||
</div>
|
||||
toggleDropdown() {
|
||||
this.dropdownOpen = !this.dropdownOpen;
|
||||
if (this.dropdownOpen) this.searchTerm = '';
|
||||
this.$nextTick(() => {
|
||||
const el = this.$refs.searchBox;
|
||||
if (el) el.focus();
|
||||
});
|
||||
},
|
||||
|
||||
<!-- Render native select if not searchable -->
|
||||
<div v-else class="wpcfto-admin-select">
|
||||
<select
|
||||
v-bind:name="field_name"
|
||||
v-model="value"
|
||||
v-bind:id="field_id"
|
||||
:required="fields.required === true">
|
||||
<option v-for="(option, key) in fields.options" :value="key" :key="key">
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
selectOption(key) {
|
||||
this.value = key;
|
||||
this.emitAndSync();
|
||||
this.dropdownOpen = false;
|
||||
},
|
||||
|
||||
emitAndSync() {
|
||||
this.$emit('wpcfto-get-value', this.value);
|
||||
const native = this.$refs.nativeSelect;
|
||||
if (native) {
|
||||
native.value = this.value;
|
||||
// ensure form listeners like WPCFTO dependency see it
|
||||
native.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (typeof window.wpcftoDependency !== 'undefined') {
|
||||
window.wpcftoDependency.check(this.field_id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleClickOutside(event) {
|
||||
if (!this.$el.contains(event.target)) {
|
||||
this.dropdownOpen = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// if parent updates field_value prop (rare), keep in sync
|
||||
field_value(nv) {
|
||||
if (nv !== this.value) {
|
||||
this.value = nv;
|
||||
this.$nextTick(this.emitAndSync);
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="wpcfto_generic_field wpcfto_generic_field__select" :class="{ open: dropdownOpen }" :data-field="field_id">
|
||||
|
||||
<wpcfto_fields_aside_before :fields="fields" :field_label="field_label" :required="fields.required === true"></wpcfto_fields_aside_before>
|
||||
|
||||
<div class="wpcfto-field-content">
|
||||
|
||||
<!-- Custom searchable -->
|
||||
<div v-if="localSearchable" class="wpcfto-custom-select" :id="field_id" @click.stop="toggleDropdown">
|
||||
<div class="wpcfto-selected-value">
|
||||
{{ selectedLabel }}
|
||||
<span class="wpcfto-arrow" :class="{ open: dropdownOpen }">▾</span>
|
||||
</div>
|
||||
|
||||
<wpcfto_fields_aside_after :fields="fields"></wpcfto_fields_aside_after>
|
||||
<div v-if="dropdownOpen" class="wpcfto-options-dropdown">
|
||||
<input ref="searchBox" type="text" class="wpcfto-select-search" v-model="searchTerm" placeholder="Search..." @click.stop />
|
||||
<ul class="wpcfto-options-list" style="padding-left:0;">
|
||||
<li v-for="(label, key) in filteredOptions" :key="key"
|
||||
:class="{ selected: key === value }"
|
||||
@click.stop="selectOption(key)">
|
||||
{{ label }}
|
||||
</li>
|
||||
<li v-if="Object.keys(filteredOptions).length === 0" class="no-options">No options found</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden input to submit form value for custom select -->
|
||||
<input ref="nativeSelect" v-if="fields.searchable" type="hidden" :name="field_name" v-model="value" :required="fields.required === true" />
|
||||
<!-- Native select -->
|
||||
<div v-else class="wpcfto-admin-select">
|
||||
<select :name="field_name" v-model="value" :id="field_id" :required="fields.required === true">
|
||||
<option v-for="(option, key) in localOptions" :value="key" :key="key">{{ option }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.$emit('wpcfto-get-value', newVal);
|
||||
this.$nextTick(() => {
|
||||
if (typeof window.wpcftoDependency !== 'undefined') {
|
||||
window.wpcftoDependency.check(this.field_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<wpcfto_fields_aside_after :fields="fields"></wpcfto_fields_aside_after>
|
||||
|
||||
<!-- Hidden input to submit form value for custom select -->
|
||||
<input ref="nativeSelect" v-if="localSearchable" type="hidden" :name="field_name" v-model="value" :required="fields.required === true" />
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZha2VfYzRiZDk3ZTMuanMiXSwibmFtZXMiOlsiVnVlIiwiY29tcG9uZW50IiwicHJvcHMiLCJkYXRhIiwidmFsdWUiLCJ0ZW1wbGF0ZSIsIm1vdW50ZWQiLCJmaWVsZF92YWx1ZSIsIm1ldGhvZHMiLCJ3YXRjaCIsIl92YWx1ZSIsIiRlbWl0Il0sIm1hcHBpbmdzIjoiQUFBQTs7QUFFQUEsR0FBRyxDQUFDQyxTQUFKLENBQWMsZUFBZCxFQUErQjtBQUM3QkMsRUFBQUEsS0FBSyxFQUFFLENBQUMsUUFBRCxFQUFXLGFBQVgsRUFBMEIsWUFBMUIsRUFBd0MsVUFBeEMsRUFBb0QsYUFBcEQsQ0FEc0I7QUFFN0JDLEVBQUFBLElBQUksRUFBRSxTQUFTQSxJQUFULEdBQWdCO0FBQ3BCLFdBQU87QUFDTEMsTUFBQUEsS0FBSyxFQUFFO0FBREYsS0FBUDtBQUdELEdBTjRCO0FBTzdCQyxFQUFBQSxRQUFRLEVBQUUsNndCQVBtQjtBQVE3QkMsRUFBQUEsT0FBTyxFQUFFLFNBQVNBLE9BQVQsR0FBbUI7QUFDMUIsU0FBS0YsS0FBTCxHQUFhLEtBQUtHLFdBQWxCO0FBQ0QsR0FWNEI7QUFXN0JDLEVBQUFBLE9BQU8sRUFBRSxFQVhvQjtBQVk3QkMsRUFBQUEsS0FBSyxFQUFFO0FBQ0xMLElBQUFBLEtBQUssRUFBRSxTQUFTQSxLQUFULENBQWVNLE1BQWYsRUFBdUI7QUFDNUIsV0FBS0MsS0FBTCxDQUFXLGtCQUFYLEVBQStCRCxNQUEvQjtBQUNEO0FBSEk7QUFac0IsQ0FBL0IiLCJzb3VyY2VzQ29udGVudCI6WyJcInVzZSBzdHJpY3RcIjtcblxuVnVlLmNvbXBvbmVudCgnd3BjZnRvX3NlbGVjdCcsIHtcbiAgcHJvcHM6IFsnZmllbGRzJywgJ2ZpZWxkX2xhYmVsJywgJ2ZpZWxkX25hbWUnLCAnZmllbGRfaWQnLCAnZmllbGRfdmFsdWUnXSxcbiAgZGF0YTogZnVuY3Rpb24gZGF0YSgpIHtcbiAgICByZXR1cm4ge1xuICAgICAgdmFsdWU6ICcnXG4gICAgfTtcbiAgfSxcbiAgdGVtcGxhdGU6IFwiXFxuICAgICAgICA8ZGl2IGNsYXNzPVxcXCJ3cGNmdG9fZ2VuZXJpY19maWVsZCB3cGNmdG9fZ2VuZXJpY19maWVsZF9fc2VsZWN0XFxcIj5cXG5cXG4gICAgICAgICAgICA8d3BjZnRvX2ZpZWxkc19hc2lkZV9iZWZvcmUgOmZpZWxkcz1cXFwiZmllbGRzXFxcIiA6ZmllbGRfbGFiZWw9XFxcImZpZWxkX2xhYmVsXFxcIj48L3dwY2Z0b19maWVsZHNfYXNpZGVfYmVmb3JlPlxcbiAgICAgICAgICAgIFxcbiAgICAgICAgICAgIDxkaXYgY2xhc3M9XFxcIndwY2Z0by1maWVsZC1jb250ZW50XFxcIj5cXG4gICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cXFwid3BjZnRvLWFkbWluLXNlbGVjdFxcXCI+XFxuICAgICAgICAgICAgICAgICAgICA8c2VsZWN0IHYtYmluZDpuYW1lPVxcXCJmaWVsZF9uYW1lXFxcIlxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2LW1vZGVsPVxcXCJ2YWx1ZVxcXCJcXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdi1iaW5kOmlkPVxcXCJmaWVsZF9pZFxcXCI+XFxuICAgICAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiB2LWZvcj1cXFwiKG9wdGlvbiwga2V5KSBpbiBmaWVsZHNbJ29wdGlvbnMnXVxcXCIgdi1iaW5kOnZhbHVlPVxcXCJrZXlcXFwiPnt7IG9wdGlvbiB9fTwvb3B0aW9uPlxcbiAgICAgICAgICAgICAgICAgICAgPC9zZWxlY3Q+XFxuICAgICAgICAgICAgICAgIDwvZGl2PlxcbiAgICAgICAgICAgIDwvZGl2PlxcblxcbiAgICAgICAgICAgIDx3cGNmdG9fZmllbGRzX2FzaWRlX2FmdGVyIDpmaWVsZHM9XFxcImZpZWxkc1xcXCI+PC93cGNmdG9fZmllbGRzX2FzaWRlX2FmdGVyPlxcblxcbiAgICAgICAgPC9kaXY+XFxuICAgIFwiLFxuICBtb3VudGVkOiBmdW5jdGlvbiBtb3VudGVkKCkge1xuICAgIHRoaXMudmFsdWUgPSB0aGlzLmZpZWxkX3ZhbHVlO1xuICB9LFxuICBtZXRob2RzOiB7fSxcbiAgd2F0Y2g6IHtcbiAgICB2YWx1ZTogZnVuY3Rpb24gdmFsdWUoX3ZhbHVlKSB7XG4gICAgICB0aGlzLiRlbWl0KCd3cGNmdG8tZ2V0LXZhbHVlJywgX3ZhbHVlKTtcbiAgICB9XG4gIH1cbn0pOyJdfQ==
|
||||
},{}]},{},[1])
|
||||
Reference in New Issue
Block a user