UNPKG

apostrophe

Version:
219 lines (213 loc) 5.86 kB
import { klona } from 'klona'; export default { // Implements v-model pattern emits: [ 'update:modelValue' ], props: { // The value passed in from the parent component through the v-model // directive. modelValue: { type: Object, required: true }, field: { type: Object, required: true }, meta: { type: Object, default() { return {}; } }, modifiers: { default: function () { return []; }, type: Array }, followingValues: { type: Object, required: false }, conditionMet: { type: Boolean, required: false }, triggerValidation: { type: Boolean, default: false }, displayOptions: { type: Object, default() { return {}; } }, // Because some field types, like AposInputSlug, must check for // uniqueness without regarding the document itself as a conflict docId: { type: String, required: false }, // An error for this field that was provided by the server on // an attempt to save the larger document serverError: { type: Object, required: false }, noBlurEmit: { type: Boolean, default: false } }, data () { return { next: (this.modelValue && this.modelValue.data !== undefined) ? this.modelValue.data : '', error: false, // This is just meant to be sufficient to prevent unintended collisions // in the UI between id attributes uid: Math.random(), // Automatically updated for you, can be watched focus: false, // Can be overriden at input component level to handle async field // preparation fieldReady: true }; }, mounted () { this.$el.addEventListener('focusin', this.focusInListener); this.$el.addEventListener('focusout', this.focusOutListener); }, unmounted () { this.$el.removeEventListener('focusin', this.focusInListener); this.$el.removeEventListener('focusout', this.focusOutListener); }, computed: { options () { return window.apos.schema; }, iconSize () { if (this.modifiers.includes('small')) { return 14; } else { return 20; } }, tooltip () { let msg = false; if (this.field.readOnly) { msg = 'apostrophe:inputFieldIsDisabled'; } return msg; }, effectiveError () { return this.error || this.serverError; }, fieldMeta() { return this.meta?.[this.field.name] || {}; } }, watch: { modelValue: { deep: true, handler (value) { this.watchValue(); } }, // `next` is the internal state of the input's value, which is eventually // emitted with the 'input' event. next: { deep: true, handler (value) { this.watchNext(); } }, triggerValidation(value) { if (value) { this.validateAndEmit(); } }, focus(value) { if (!this.noBlurEmit && !value) { this.validateAndEmit(); } } }, methods: { // You must supply the validate method. It receives the // internal representation used for editing (a string, for instance) validateAndEmit () { if (!this.fieldReady) { return; } // If the field is conditional and isn't shown, disregard any errors. // If field isn't ready we don't want to validate its value const shouldValidate = this.conditionMet !== false; const error = shouldValidate ? this.validate(this.next) : false; this.$emit('update:modelValue', { data: error ? this.next : this.convert(this.next), error, ranValidation: shouldValidate ? true : this.modelValue.ranValidation }); }, // Allows replacing the current component value externally, e.g. via // local component or global bus events. replaceFieldValue(value) { this.next = value; }, watchValue () { this.error = this.modelValue.error; this.next = this.modelValue.data; }, watchNext () { this.validateAndEmit(); }, focusInListener() { this.focus = true; }, focusOutListener() { this.focus = false; }, // Convert from the representation used for editing // (for instance, a string) to the final representation // required by the field type (for instance, a number). // Called just before the input event is emitted. Not // called if validation, which is done on the editing // representation, is unsuccessful. Instead the editing // representation is emitted to preserve the editing // experience. convert() { return this.next; }, // Accepts an array of object values and convertts the current meta to items // so that it contains only the item _id's as keys (without leading `@`) and // meta keys for the current field if any. // This util is meant to be used in array and widget wrappers or // custom fields that manage array of object values having unique `_id`. convertMetaToItems(valueItems = []) { const fieldMeta = klona(this.fieldMeta); const meta = this.meta || {}; const shared = {}; for (const fieldName of Object.keys(meta)) { if (fieldName.startsWith('@') && !valueItems.some(item => meta[`@${item._id}`])) { shared[fieldName] = meta[fieldName]; continue; } } for (const item of valueItems) { const itemMeta = meta[`@${item._id}`] || {}; const subMeta = itemMeta.aposMeta; fieldMeta[item._id] = { ...itemMeta, aposMeta: { ...(subMeta || {}), ...shared } }; } return fieldMeta; } } };