UNPKG

@gitlab/ui

Version:
241 lines (228 loc) • 8.71 kB
import isFunction from 'lodash/isFunction'; import mapValues from 'lodash/mapValues'; import uniqueId from 'lodash/uniqueId'; import GlFormGroup from '../form_group/form_group'; import GlFormInput from '../form_input/form_input'; import { setObjectProperty } from '../../../../utils/set_utils'; import GlFormFieldValidator from './form_field_validator'; import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js'; var script = { name: 'GlFormFields', components: { GlFormGroup, GlFormInput, GlFormFieldValidator }, model: { prop: 'values', event: 'input' }, props: { /** * Object of keys to FieldDefinitions. * The shape of the keys will be the same for `values` and what's emitted by the `input` event. * * @typedef {object} FieldDefinition * @template TValue=string * @property {string} label - Label text to show for this field. * @property {undefined | Object} groupAttrs - Properties that are passed to the group wrapping this field. * @property {undefined | Object} inputAttrs - Properties that are passed to the actual input for this field. * @property {undefined | function(string): TValue} mapValue - Function that maps the inputted string value to the field's actual value (e.g. a Number). * @property {undefined | Array<function(TValue): string | undefined>=} validators - Collection of validator functions. * * @type {{ [key: string]: FieldDefinition }} */ fields: { type: Object, required: true }, /** * The current value for each field, by key. * Keys should match between `values` and `fields`. */ values: { type: Object, required: true }, /** * The id of the form element to handle "submit" listening. */ formId: { type: String, required: true }, /** * Validation errors from the server. Generally passed to the component after making an API call. */ serverValidations: { type: Object, required: false, default() { return {}; } } }, data() { return { fieldDirtyStatuses: {}, fieldValidations: {} }; }, computed: { formElement() { return document.getElementById(this.formId); }, fieldValidationProps() { return mapValues(this.fields, (_, fieldName) => { const invalidFeedback = this.serverValidations[fieldName] || this.fieldValidations[fieldName] || ''; return { invalidFeedback, state: invalidFeedback ? false : null }; }); }, fieldValues() { return mapValues(this.fields, (_, fieldName) => { if (fieldName in this.values) { return this.values[fieldName]; } return this.getMappedValue(fieldName, undefined); }); }, fieldNames() { return Object.keys(this.fields); }, fieldsToRender() { return mapValues(this.fields, (field, fieldName) => { const id = uniqueId('gl-form-field-'); const inputSlotName = `input(${fieldName})`; const groupPassthroughSlotName = `group(${fieldName})-`; const afterSlotName = `after(${fieldName})`; const inputSlot = { slotName: inputSlotName, attrs: { value: this.fieldValues[fieldName], input: val => this.onFieldInput(fieldName, val), blur: () => this.onFieldBlur(fieldName), validation: this.fieldValidationProps[fieldName], id } }; const groupPassthroughSlots = Object.keys(this.$scopedSlots).filter(slotName => slotName.startsWith(groupPassthroughSlotName)).map(slotName => { const childSlotName = slotName.replace(groupPassthroughSlotName, ''); return { slotName, childSlotName }; }); return { ...field, id, label: field.label || fieldName, inputSlot, groupPassthroughSlots, afterSlotName }; }); } }, mounted() { var _this$formElement; // why: We emit initial values as a convenience so that `v-model="values"` can be easily initialized. this.$emit('input', this.fieldValues); (_this$formElement = this.formElement) === null || _this$formElement === void 0 ? void 0 : _this$formElement.addEventListener('submit', this.onFormSubmission); }, destroyed() { var _this$formElement2; (_this$formElement2 = this.formElement) === null || _this$formElement2 === void 0 ? void 0 : _this$formElement2.removeEventListener('submit', this.onFormSubmission); }, methods: { setFieldDirty(fieldName) { this.fieldDirtyStatuses = setObjectProperty(this.fieldDirtyStatuses, fieldName, true); }, setAllFieldsDirty() { this.fieldNames.forEach(fieldName => this.setFieldDirty(fieldName)); }, hasAllFieldsValid() { // note: Only check "fieldNames" since "fields" could have changed since the life of "fieldValidations" return this.fieldNames.every(fieldName => !this.fieldValidations[fieldName]); }, async checkBeforeSubmission() { this.setAllFieldsDirty(); await this.$nextTick(); return this.hasAllFieldsValid(); }, getMappedValue(fieldName, val) { const field = this.fields[fieldName]; if (isFunction(field === null || field === void 0 ? void 0 : field.mapValue)) { return field.mapValue(val); } return val; }, onFieldValidationUpdate(fieldName, invalidFeedback) { this.fieldValidations = setObjectProperty(this.fieldValidations, fieldName, invalidFeedback); }, onFieldBlur(fieldName) { this.setFieldDirty(fieldName); }, onFieldInput(fieldName, inputValue) { const val = this.getMappedValue(fieldName, inputValue); /** * Emitted when any of the form values change. Used by `v-model`. */ this.$emit('input', { ...this.values, [fieldName]: val }); /** * Emitted when a form input emits the `input` event. */ this.$emit('input-field', { name: fieldName, value: val }); }, async onFormSubmission(e) { e.preventDefault(); const isValid = await this.checkBeforeSubmission(); if (isValid) { /** * Emitted when the form is submitted and all of the form fields are valid. */ this.$emit('submit', e); } } } }; /* script */ const __vue_script__ = script; /* template */ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_vm._l((_vm.fieldsToRender),function(field,fieldName){return [_c('gl-form-group',_vm._b({attrs:{"label":field.label,"label-for":field.id,"invalid-feedback":_vm.fieldValidationProps[fieldName].invalidFeedback,"state":_vm.fieldValidationProps[fieldName].state},scopedSlots:_vm._u([_vm._l((field.groupPassthroughSlots),function(ref){ var slotName = ref.slotName; var childSlotName = ref.childSlotName; return {key:childSlotName,fn:function(){return [_vm._t(slotName)]},proxy:true}})],null,true)},'gl-form-group',field.groupAttrs,false),[_c('gl-form-field-validator',{attrs:{"value":_vm.fieldValues[fieldName],"validators":field.validators,"should-validate":_vm.fieldDirtyStatuses[fieldName]},on:{"update":function($event){return _vm.onFieldValidationUpdate(fieldName, $event)}}}),_vm._v(" "),_vm._v(" "),_vm._t(field.inputSlot.slotName,function(){return [_c('gl-form-input',_vm._b({attrs:{"id":field.id,"value":_vm.fieldValues[fieldName],"state":_vm.fieldValidationProps[fieldName].state},on:{"input":function($event){return _vm.onFieldInput(fieldName, $event)},"blur":function($event){return _vm.onFieldBlur(fieldName)}}},'gl-form-input',field.inputAttrs,false))]},null,field.inputSlot.attrs)],2),_vm._v(" "),_vm._t(field.afterSlotName)]})],2)}; var __vue_staticRenderFns__ = []; /* style */ const __vue_inject_styles__ = undefined; /* scoped */ const __vue_scope_id__ = undefined; /* module identifier */ const __vue_module_identifier__ = undefined; /* functional template */ const __vue_is_functional_template__ = false; /* style inject */ /* style inject SSR */ /* style inject shadow dom */ const __vue_component__ = /*#__PURE__*/__vue_normalize__( { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, false, undefined, undefined, undefined ); export { __vue_component__ as default };