@gitlab/ui
Version:
GitLab UI Components
241 lines (228 loc) • 8.71 kB
JavaScript
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 };