UNPKG

apostrophe

Version:
213 lines (207 loc) • 6.12 kB
import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin.js'; import AposFieldDirectionMixin from 'Modules/@apostrophecms/schema/mixins/AposFieldDirection.js'; export default { name: 'AposInputString', mixins: [ AposInputMixin, AposFieldDirectionMixin ], emits: [ 'return' ], props: { rows: { type: Number, default: 5 } }, data() { return { step: undefined, wasPopulated: false }; }, computed: { tabindex() { return this.field.disableFocus ? '-1' : '0'; }, type() { if (this.field.type) { if (this.field.type === 'float' || this.field.type === 'integer') { return 'number'; } if (this.field.type === 'string' && this.field.textarea) { return 'textarea'; } if (this.field.type === 'string' || this.field.type === 'slug') { return 'text'; } return this.field.type; } else { return 'text'; } }, classes() { return [ 'apos-input', `apos-input--${this.type}`, this.directionClass, this.modelValue?.duplicate && 'apos-input--error' ].filter(Boolean); }, icon() { if (this.error) { return 'circle-medium-icon'; } else if (this.field.icon) { return this.field.icon; } else { return null; } } }, watch: { followingValues: { // We may be following multiple fields, like firstName and lastName, // or none at all, depending deep: true, handler(newValue, oldValue) { if (this.field.followingIgnore === true) { return; } let ov = oldValue; let nv = newValue; if (Array.isArray(this.field.followingIgnore)) { ov = Object.fromEntries( Object.entries(oldValue).filter(([ key ]) => { return !this.field.followingIgnore.includes(key); }) ); nv = Object.fromEntries( Object.entries(newValue).filter(([ key ]) => { return !this.field.followingIgnore.includes(key); }) ); } // Follow the value of the other field(s), but only if our // previous value matched the previous value of the other field(s) oldValue = Object.values(ov).join(' ').trim(); newValue = Object.values(nv).join(' ').trim(); if ( (!this.wasPopulated && ((this.next == null) || (!this.next.length))) || (this.next === oldValue) ) { this.next = newValue; } } }, next() { if (this.next && this.next.length) { this.wasPopulated = true; } } }, mounted() { this.defineStep(); this.wasPopulated = this.next && this.next.length; }, methods: { enterEmit() { if (this.field.enterSubmittable) { // Include the validated results in cases where an Enter keydown should // act as submitting a form. this.$emit('return', { data: this.next, error: this.validate(this.next) }); } else { this.$emit('return'); } }, validate(value = '') { const isString = typeof value === 'string'; if (isString && !value.length) { // Also correct for float and integer because Vue coerces // number fields to either a number or the empty string return this.field.required ? 'required' : false; } const minMaxFields = [ 'integer', 'float', 'string', 'date', 'password' ]; if (typeof value === 'string' && this.field.pattern) { const regex = new RegExp(this.field.pattern); if (!regex.test(value)) { return 'invalid'; } } if (this.field.min && minMaxFields.includes(this.field.type)) { if ( (!isString || value.length) && (this.minMaxComparable(value) < this.field.min) ) { return 'min'; } } if (this.field.max && minMaxFields.includes(this.field.type)) { if ( (!isString || value.length) && (this.minMaxComparable(value) > this.field.max) ) { return 'max'; } } if (this.field.type === 'email' && value) { // regex source: https://emailregex.com/ const matches = value.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); if (!matches) { return 'invalid'; } } return false; }, defineStep() { if (this.type === 'number') { this.step = this.field.type === 'float' ? 'any' : 1; } }, convert(s) { if (this.field.type === 'integer') { if ((s == null) || (s === '')) { return s; } else { return parseInt(s); } } else if (this.field.type === 'float') { if ((s == null) || (s === '')) { return s; } else { // The native parse float converts 3.0 to 3 and makes // next to become integer. In theory we don't need parseFloat // as the value is natively guarded by the browser 'number' type. // However we need a float value sent to the backend // and we force that when focus is lost. if (this.focus && `${s}`.match(/\.[0]*$/)) { return s; } return parseFloat(s); } } else { if (s == null) { return ''; } else { return s.toString(); } } }, minMaxComparable(s) { const converted = this.convert(s); if ( [ 'integer', 'float', 'date', 'range', 'time' ].includes(this.field.type) ) { // Compare the actual values for these types return converted; } else { // Compare the length for other types, like string or password or url return converted.length; } } } };