Created
March 25, 2019 14:31
-
-
Save iErik/97aae78dfb45f223321680e088fd1224 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <template> | |
| <form class="c-form" :name="name" @submit.prevent="submitForm"> | |
| <div class="fields"> | |
| <slot> | |
| <template v-for="(field, fieldName) in fields"> | |
| <component | |
| v-show="!isFieldHidden(fieldName)" | |
| :is="getFieldComponent(field.type)" | |
| :key="fieldName" | |
| :class="['field', field.type]" | |
| :name="fieldName" | |
| :label-left="labelLeft" | |
| :disabled="isFieldDisabled(fieldName)" | |
| :validation="getValidationMsg(fieldName)" | |
| :options="getFieldOptions(fieldName)" | |
| :value="getFieldValue(fieldName)" | |
| v-bind="field" | |
| @add="addMultiSelectItem(fieldName, $event)" | |
| @remove="removeMultiSelectItem(fieldName, $event)" | |
| @input="updateField(fieldName, $event)" | |
| /> | |
| </template> | |
| </slot> | |
| </div> | |
| <div class="actions"> | |
| <slot name="actions"> | |
| <c-button primary class="action"> | |
| Salvar | |
| </c-button> | |
| </slot> | |
| </div> | |
| </form> | |
| </template> | |
| <script> | |
| import CMultiSelect from '../CMultiSelect' | |
| import CRadioButton from '../CRadioButton' | |
| import CCheckbox from '../CCheckbox' | |
| import CSelect from '../CSelect' | |
| import CInput from '../CInput' | |
| import CButton from '../CButton' | |
| import { is } from '../../helpers' | |
| import FormValidator from 'vue-convenia-validator' | |
| /** | |
| * **OBS: using c-select in text area and jumbo mode breaks the component** | |
| **/ | |
| export default { | |
| name: 'CForm', | |
| mixins: [ FormValidator ], | |
| components: { | |
| CMultiSelect, | |
| CRadioButton, | |
| CCheckbox, | |
| CSelect, | |
| CInput, | |
| CButton | |
| }, | |
| props: { | |
| /** | |
| * The form fields. | |
| */ | |
| fields: { | |
| type: [Object], | |
| required: true | |
| }, | |
| /** | |
| * The form name. | |
| */ | |
| name: { | |
| type: String, | |
| default: 'formData' | |
| }, | |
| /** | |
| * Whether the form is loading. | |
| */ | |
| loading: Boolean, | |
| /** | |
| * Disables the form. | |
| */ | |
| disabled: Boolean, | |
| /** | |
| * Moves all of the input's labels to the left side. | |
| */ | |
| labelLeft: Boolean | |
| }, | |
| data () { | |
| return { | |
| [this.name]: {}, | |
| dynamicFields: [] | |
| } | |
| }, | |
| methods: { | |
| isFieldHidden (fieldName) { | |
| const field = this.fields[fieldName] | |
| if (is(field.hide, 'Function')) return field.hide(this[formData]) | |
| return field.hide | |
| }, | |
| getFieldComponent (type) { | |
| const typeToComponent = { | |
| 'text': 'c-input', | |
| 'password': 'c-input', | |
| 'select': 'c-select', | |
| 'check': 'c-checkbox', | |
| 'radio': 'c-radio-button', | |
| 'multi-select': 'c-multi-select' | |
| } | |
| return typeToComponent[type] || 'c-input' | |
| }, | |
| addMultiSelectItem (fieldName, item) { | |
| const field = this.fields[fieldName] | |
| const fieldValues = [ ...(this[this.name][fieldName] || []) ] | |
| fieldValues.push(item) | |
| this[this.name][fieldName] = fieldValues | |
| }, | |
| removeMultiSelectItem (fieldName, item) { | |
| const field = this.fields[fieldName] | |
| const fieldValues = [ ...(this[this.name][fieldName] || []) ] | |
| const itemIndex = fieldValues.findIndex((fieldItem) => { | |
| return item[field.trackBy] === fieldItem[field.trackBy] | |
| }) | |
| fieldValues.splice(itemIndex, 1) | |
| this[this.name][fieldName] = fieldValues | |
| }, | |
| getValidationMsg (fieldName) { | |
| const fieldDef = this.fields[fieldName] | |
| const fieldFlags = this.$validations[fieldName] || {} | |
| const fieldErrors = (fieldFlags.errors || []) | |
| return fieldErrors.length || fieldDef.forceError | |
| ? fieldDef.validationMsg || fieldErrors[0] || '' | |
| : '' | |
| }, | |
| getFieldValue (fieldName) { | |
| const field = this.fields[fieldName] | |
| if (is(field.value, 'Function')) { | |
| const value = field.value(this[this.name]) | |
| this.formData[fieldName] = value | |
| return value | |
| } | |
| return this.formData[fieldName] | |
| }, | |
| getFieldOptions (fieldName) { | |
| const field = this.fields[fieldName] || {} | |
| return field.optionsFilter | |
| ? field.optionsFilter(this[this.name], field.options) | |
| : field.options | |
| }, | |
| isFieldDisabled (fieldName) { | |
| const field = this.fields[fieldName] || {} | |
| const fieldOptions = field.options && this.getFieldOptions(fieldName) | |
| if (is(field.isDisabled, 'Function')) | |
| return field.isDisabled(this[this.name]) | |
| return field.disabled || !!field.options && !(fieldOptions || []).length | |
| }, | |
| updateField (fieldName, value) { | |
| const field = this.fields[fieldName] || {} | |
| if (field.onInput) field.onInput(this[this.name], value, this) | |
| this[this.name][fieldName] = value | |
| }, | |
| submitForm () { | |
| const isValid = this.$validator.validateAll() | |
| if (isValid) this.$emit('submit', this[this.name]) | |
| }, | |
| bindDynamicFieldListeners () { | |
| const reEvaluator = (data) => { | |
| this.dynamicFields.forEach(({ name, ...field }) => { | |
| const newRule = field.validation(data, this.$validations) | |
| this.$validator.setFieldRule({ name, scope: 'formData' }, newRule) | |
| }) | |
| } | |
| this.dynamicFields = Object.keys(this.fields) | |
| .filter(fieldName => is(this.fields[fieldName].validation, 'Function')) | |
| .map(fieldName => ({ ...this.fields[fieldName], name: fieldName })) | |
| if (this.dynamicFields.length) | |
| this.$watch(this.name, reEvaluator, { deep: true }) | |
| }, | |
| }, | |
| created () { | |
| const reduceToValue = (entity, key, ignoreEmpty) => Object.keys(entity) | |
| .reduce((acc, propName) => ({ | |
| ...acc, | |
| ...(!(entity[propName] || {})[key] && ignoreEmpty | |
| ? {} | |
| : { [propName]: (entity[propName] || {})[key] }) | |
| }), {}) | |
| const validations = reduceToValue(this.fields, 'validation', true) | |
| this[this.name] = reduceToValue(this.fields, 'value') | |
| this.$validator.init({ [this.name]: validations }) | |
| this.bindDynamicFieldListeners() | |
| } | |
| } | |
| </script> | |
| <style lang="scss"> | |
| .c-form { | |
| & > .fields { | |
| & > .field:not(:last-child):not(.-validation):not(.check):not(.-jumbo) { padding-bottom: 20px; } | |
| // Nobody knows what it does, but it may be important :v | |
| // & > .field.-label-left:not(:last-child):not(.-validation) { padding-botom: 20px; } | |
| & > .field.check { margin-bottom: 20px; } | |
| & > .field.radio.-validation:not(:last-child) { margin-bottom: 62px; } | |
| & > .field.-jumbo { | |
| &:not(:last-child) { margin-bottom: 20px; } | |
| &.-validation.select:not(:last-child) { margin-bottom: 42px; } | |
| &:not(.radio) { margin-top: 0; } | |
| } | |
| & > .field[type="hidden"] { display: none; } | |
| & > .multi-select.-validation { padding-bottom: 30px; } | |
| } | |
| & > .actions { | |
| display: flex; | |
| justify-content: flex-end; | |
| margin-top: 40px; | |
| & > .action { | |
| flex: 1 1; | |
| max-width: 180px; | |
| &:not(:last-child) { margin-right: 10px; } | |
| } | |
| } | |
| /* This chunk of code is too specific to the convenia (and not the saas) | |
| * styleguide and as such it should be moved to a more specific place. | |
| @include responsive (mobile, desktop) { | |
| & > .fields > .field.radio > .label { font-size: 14px; } | |
| } | |
| */ | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment