Skip to content

Instantly share code, notes, and snippets.

@iErik
Created March 25, 2019 14:31
Show Gist options
  • Select an option

  • Save iErik/97aae78dfb45f223321680e088fd1224 to your computer and use it in GitHub Desktop.

Select an option

Save iErik/97aae78dfb45f223321680e088fd1224 to your computer and use it in GitHub Desktop.
<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