2
\$\begingroup\$

I am accepting dynamic input from the user if they have any i.e - Children/Siblings/Previous Employement etc through Vue Components. A form is created and they input their data which is validated and then submitted. Submitted data is displayed in another Vue table component from which they can delete data if needed.

The methods that I am calling for submitting, cancelling and resetting the different inputs are almost identical but I need to repeat it as they each have different validation standards. Another repetition in my code are the display tables. Each input component has its own display table component. I thought about making the display table as its own component but then I would have to make another component which holds the general display component and the input component.

Child Add

<template>
    <div>
        <div>
            <button class="btn btn-primary ml-auto" id="child_add" v-if="!showAdd" @click="addRow">Add Child</button>
        </div>
        <b-form @submit.stop.prevent="onSubmit" v-if="showAdd">
            <b-form-group :id="'children-name-div-'+count" label="Name" :label-for="'children-name-'+count">
                <b-form-input
                        :id="'children-name-'+count"
                        :name="'children-name-'+count"
                        v-model="$v.form.name.$model"
                        :state="validateState('name')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'children-name-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'children-school-div-'+count" label="School" :label-for="'children-school-'+count">
                <b-form-input
                        :id="'children-school-'+count"
                        :name="'children-school-'+count"
                        v-model="$v.form.school.$model"
                        :state="validateState('school')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'children-school-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'children-class-div-'+count" label="Class" :label-for="'children-class-'+count">
                <b-form-input
                        :id="'children-class-'+count"
                        :name="'children-class-'+count"
                        v-model="$v.form.class.$model"
                        :state="validateState('class')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'children-class-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'children-age-div-'+count" label="Age" :label-for="'children-age-'+count">
                <b-form-input
                        :id="'children-age-'+count"
                        :name="'children-age-'+count"
                        v-model="$v.form.age.$model"
                        :state="validateState('age')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'children-age-'+count+'live-feedback'">This is a required field and only accepts numbers</b-form-invalid-feedback>
            </b-form-group>

            <b-button type="submit" id="child-submit" variant="primary">Submit</b-button>
            <b-button class="ml-2" id="child-reset" @click="resetForm()">Reset</b-button>
            <b-button class="ml-2" id="child-cancel" variant="danger" @click="cancel()">Cancel</b-button>
        </b-form>
    </div>
</template>
<script>
    import { validationMixin } from "vuelidate";
    import { required, alphaNum, between, alpha } from "vuelidate/lib/validators";

    export default {
        name: "SiblingsInput",
        mixins: [validationMixin],
        data() {
            return {
                count: 0,
                showAdd: false,
                form: {
                    name: null,
                    school: null,
                    class: null,
                    age: null
                }
            };
        },
        validations: {
            form: {
                name: {
                    required,
                },
                school: {
                    required,
                },
                class:{
                    required,
                },
                age:{
                    required,
                    between: between(1,999)
                }
            }
        },
        methods: {
            addRow(){
                this.showAdd = true;
                this.count++;
            },
            validateState(name) {
                const { $dirty, $error } = this.$v.form[name];
                return $dirty ? !$error : null;
            },
            resetForm() {
                this.form = {
                    name: null,
                    school: null,
                    class: null,
                    age: null
                };

                this.$nextTick(() => {
                    this.$v.$reset();
                });
            },
            onSubmit() {
                this.$v.form.$touch();
                if (this.$v.form.$anyError) {
                    return;
                }

                const data = Object();
                Object.assign(data, this.form);
                this.$emit('add-row', data, this.count);
                this.resetForm();

                this.showAdd = false;
            },
            setCount(){
              this.count--;
            },
            cancel(){
                this.resetForm();
                this.count--;
                this.showAdd = false;
            }
        }
    };
</script>

Child Display

<template>
    <div id="table-form">
        <table v-if="this.rows.length" class="table" >
            <thead>
            <tr>
                <th v-for="item in items" >{{ item | capitalize}}</th>
                <th class="text-right">Del</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(row, index) in rows" :key="index">
                <td v-for="item in items">{{ row[item]}}</td>
                <td class="text-right">
                    <b-button variant="outline-danger" @click.prevent="delRow(index)">
                        Delete
                    </b-button>
                </td>
            </tr>
            </tbody>
        </table>
        <input type="text" name="children" hidden :value="JSON.stringify(rows)"/>
        <children-input @add-row="addRow" ref="childrenInput" class="mt-2" ></children-input>
    </div>
</template>

<script>
    import ChildrenInput from "./ChildrenInput";
    import { BIconXCircleFill } from 'bootstrap-vue'
    export default {
        name: "ChildrenTable",
        components:{
            ChildrenInput,
            BIconXCircleFill
        },
        data(){
            return{
                count:0,
                rows:[],
                items:['name','school','class','age'],
                itemType:{
                    name:'text',
                    school:'text',
                    class:'text',
                    age:'number'
                }
            }
        },
        methods:{
            addRow(row, rowCount){
                this.rows.push(row);
                this.count = rowCount;
            },
            delRow(index){
                this.$refs.childrenInput.setCount();
                this.rows.splice(index, 1);
                this.count--;
            }
        },
        filters: {
            capitalize: function (value) {
                if (!value) return ''
                value = value.toString()
                return value.charAt(0).toUpperCase() + value.slice(1)
            }
        }
    }
</script>

I'm repeating the same thing for Previous Employment with different inputs, data and validation

Employment Add

template>
    <div>
        <div>
            <button class="btn btn-primary ml-auto" id="emp_add" v-if="!showAdd" @click="addRow">Add Previous Employment</button>
        </div>
        <b-form @submit.stop.prevent="onSubmit" v-if="showAdd">
            <b-form-group :id="'emp-employer-div-'+count" label="Employer Name" :label-for="'emp-employer-'+count">
                <b-form-input
                        :id="'emp-employer-'+count"
                        :name="'emp-employer-'+count"
                        v-model="$v.form.employer.$model"
                        :state="validateState('employer')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'emp-employer-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'emp-address-div-'+count" label="Employer Address" :label-for="'emp-address-'+count">
                <b-form-input
                        :id="'emp-address-'+count"
                        :name="'emp-address-'+count"
                        v-model="$v.form.address.$model"
                        :state="validateState('address')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'emp-address-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'emp-job-div-'+count" label="Job Description" :label-for="'emp-job-'+count">
                <b-form-input
                        :id="'emp-job-'+count"
                        :name="'emp-job-'+count"
                        v-model="$v.form.job.$model"
                        :state="validateState('job')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'emp-job-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'emp-salary-div-'+count" label="Salary Drawn" :label-for="'emp-salary-'+count">
                <b-form-input
                        :id="'emp-salary-'+count"
                        :name="'emp-salary-'+count"
                        v-model="$v.form.salary.$model"
                        :state="validateState('salary')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'emp-salary-'+count+'live-feedback'">This is a required field and accepts only numbers</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'emp-start-div-'+count" label="Period of Work - Starting Year" :label-for="'emp-start-'+count">
                <b-form-select
                        :id="'emp-start-'+count"
                        :name="'emp-start-'+count"
                        v-model="$v.form.start.$model"
                        :state="validateState('start')"
                        :options="options">
                </b-form-select>
                <b-form-invalid-feedback :id="'emp-start-'+count+'live-feedback'">Start Year can't be greater than End Year</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'emp-end-div-'+count" label="Period of Work - Ending Year" :label-for="'emp-end-'+count">
                <b-form-select
                        :id="'emp-end-'+count"
                        :name="'emp-end-'+count"
                        v-model="$v.form.end.$model"
                        :state="validateState('end')"
                        :options="options">
                </b-form-select>
                <b-form-invalid-feedback :id="'emp-end-'+count+'live-feedback'">End Year can't be lesser than Start Year</b-form-invalid-feedback>
            </b-form-group>
            <b-form-group :id="'emp-reason-div-'+count" label="Reason for leaving" :label-for="'emp-reason-'+count">
                <b-form-input
                        :id="'emp-reason-'+count"
                        :name="'emp-reason-'+count"
                        v-model="$v.form.reason.$model"
                        :state="validateState('reason')"
                        autocomplete="off"
                ></b-form-input>
                <b-form-invalid-feedback :id="'emp-reason-'+count+'live-feedback'">This is a required field</b-form-invalid-feedback>
            </b-form-group>
            <b-button type="submit" id="emp-submit" variant="primary">Submit</b-button>
            <b-button class="ml-2" id="emp-reset" @click="resetForm()">Reset</b-button>
            <b-button class="ml-2" id="emp-cancel" variant="danger" @click="cancel()">Cancel</b-button>
        </b-form>
    </div>
</template>

<script>
    import {validationMixin} from "vuelidate";
    import {minValue, maxValue, required, numeric} from "vuelidate/lib/validators";

    export default {
        name: "EmploymentInput",
        mixins: [validationMixin],
        data() {
            return {
                count: 0,
                showAdd: false,
                form: {
                    employer: null,
                    address: null,
                    job: null,
                    salary: null,
                    start: null,
                    end: null,
                    reason: null
                },
                options:[]
            };
        },
        validations(){
            return{
                form: {
                    employer: {
                        required
                    },
                    address: {
                        required,
                    },
                    job:{
                        required
                    },
                    salary: {
                        required,
                        numeric
                    },
                    start:{
                        required,
                        maxVal : maxValue(this.form.end)
                    },
                    end:{
                        required,
                        minVal : minValue(this.form.start)
                    },
                    reason:{
                        required,
                    }
                }
            }
        },
        methods: {
            addRow(){
                this.showAdd = true;
                this.count++;
            },
            validateState(name) {
                const { $dirty, $error } = this.$v.form[name];
                return $dirty ? !$error : null;
            },
            resetForm() {
                const year = new Date().getFullYear();
                this.form = {
                    employer: null,
                    address: null,
                    salary: null,
                    start: year,
                    end: year,
                    reason: null
                };

                this.$nextTick(() => {
                    this.$v.$reset();
                });
            },
            onSubmit() {
                this.$v.form.$touch();
                if (this.$v.form.$anyError) {
                    return;
                }

                const data = Object();
                Object.assign(data, this.form);
                this.$emit('add-row', data, this.count);
                this.resetForm();

                this.showAdd = false;
            },
            setCount(){
                this.count--;
            },
            cancel(){
                this.resetForm();
                this.count--;
                this.showAdd = false;
            }
        },
        created() {
            const year = new Date().getFullYear();
            for(let i = year; i >=1950; i--){
                this.form.start = year;
                this.form.end = year;
                this.options.push({value:i, text:i})
            }
        }
    }
</script>

It has an Employment Table component which is exactly similar to the Child Table component except that it has the employment-input component. I am doing the same thing for two other types of input which seems very futile and repetitive.

Another concern I have is the sending of data between the components when a row is added and deleted. From my testing the count variable has not faltered but it doesn't seem to be a very elegant solution

\$\endgroup\$

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.