v-generic-form
TypeScript icon, indicating that this package has built-in type declarations

1.1.13 • Public • Published

v-Generic-Form

TODO:

  • Allow user to choose schema layout via slot
  • Optional passing of form data when not using slots
  • Create Typescript validation function to provide better intellisense when defining schema
  • Individual passing of specific individual fields, e.g.
  • Add rules directive to children of generic form ( nice to have honestly )
  • Typescript rewrite overall
  • Allow global custom errors

Quickstart

Vue 2

yarn add v-generic-form-vue2

OR

npm i v-generic-form-vue2

import Vue from 'vue'
import vGenericForm from 'v-generic-form-vue2'
Vue.use(VGenericForm, {
    ...
})

Vue 3

yarn add v-generic-form

OR

npm i v-generic-form

import { createApp } from 'vue'
import vGenericForm from 'v-generic-form'

const el = document.getElementById('app');
const app = createApp();
app.use(vGenericForm, {
  ...
}).mount(el)

Basic usage

<template>
  <div>
    <v-generic-form :fields="fields" @submit="onSubmit" :onDataChange="onDataChange"  />
  </div>
</template>

<script>
export default {
  name: 'Form',
  computed: {
    fields() {
      return [
        {
          name: 'input',
          placeholder: 'input',
          label: 'input label',
          type: 'text',
        }
      ]
    }
  },
  methods: {
    onSubmit(data) {
      console.log(data)
    },
    onDataChange(data) {
      console.log(data)
    }
  }
};
</script>

Adding validation

vGenericForm uses validate.js behind the scenes for validation so refer to it for adding custom rules or finding all available validators.

Validation is as simple as passing a rules object to the field in your schema.

[
  {
    name: "input",
    defaultValue: 'some supported value',
    placeholder: "input",
    label: "input label",
    type: "text",
    rules: {
      required: true,
      exclusion: {
        within: ['Not Supported Value 1', 'Not Supported Value 2'],
        message: "^We don't support %{value} right now, sorry",
      },
    },
  },
];

Adding a custom validator

<script>
import validate from 'validate.js'
validate.validators.isTest = function (value, options, key, attributes) {
  console.log(value);
  console.log(`input`, attributes.atest) // same results as previous line
  console.log(options);
  console.log(key);
  console.log(attributes);
  const { message } = options
  if (value !== "test") {
    return message || 'must equal test'
  }
  // return null if success
  return null
};
export default {
  computed: {
    fields() {
      return [
        {
          name: "atest",
          placeholder: "input",
          rules: {
            isTest: {
              message: "This value does not equal test!",
            },
          },
          // disable showing input name in validation message
          fullMessages: false
        },
      ];
    },
  },
};

</script>

Using different variants

By default, vGenericForm comes with 2 inputs. Input, Select, RadioButton(coming soon), and Textarea. It will default to Input but to use a different variant, simply pass a variant as a string to the desired input you'd like to modify.

[{
  name: 'input',
  placeholder: 'input',
  label: 'input label',
  type: 'text',
  variant: 'Select',
  values: ['option1', 'option2']
}]

Using a external component with the form

<script>
import Custom from '@/components/custom'
export default {
  computed: {
    fields() {
      return [
        {
          name: "input",
          placeholder: "input",
          label: "input label",
          type: "text",
          component: Custom,
          rules: {
            required: true, // validation still works perfectly on these
          },
          customData: {
            customPropForCustomComponent: "123",
          },
        },
      ];
    },
  },
};

</script>


Custom.vue

<template>
  <div>
    <button @click="sendUpdatedValueToForm()">{{ value }}</button>
    {{ customData.customPropForCustomComponent }}
  </div>
</template>

<script>
export default {
  // default props available from generic form
  props: {
    value: {
      type: String,
      default: '',
    },
    inputType: {
      type: String,
      default: 'text',
    },
    name: {
      type: String,
      default: '',
    },
    values: {
      type Array,
      default: () => []
    },
    label: {
      type: String,
      default: '',
    },
    placeholder: {
      type: String,
      default: '',
    },
    errors: {
      type: [Array, String, Object],
      default: null,
    },
    customData: {
      type: Object,
      default: null,
    },
    getAllFields: {
      type: Array,
      default: () => {}
    },
  },
  data() {
    return {
      componentValue: "first value",
    };
  },
  methods: {
    sendUpdatedValueToForm() {
      this.componentValue = Math.random();
      $emit("setValue", {
        name,
        value: componentValue,
      });
    },
  },
};

</script>

Defining default components

import { createApp } from 'vue'
import MyInput from '@/components/myInput'
import vGenericForm from 'v-generic-form'

const el = document.getElementById('app');
const app = createApp();
app
  .use(vGenericForm, {
    components: {
      // define default inputs here
      MyInput, // you will be able to pass this as a variant now by passing variant: 'MyInput' to object scheme,
      submitText: 'Okay'
    },
  })
  .mount(el);

Styling

The generic form is structured like so:

div>
  <form
    :class="(options && options.formClass) || ''"
   >
     <div
        v-for="(field, i) in fields.filter(
          (f) => f.show === undefined || f.show !== false
        )"
        :key="i"
        :class="field.divClass"
    >

We can change the structure very easily by passing a options object

<v-generic-form :options="{
    formClass: 'flex justify-between'
}">

As you may imagine, you can control the widths of the divs each one of your fields are in by passing a divClass to your field schema:

[
  {
    name: "input",
    placeholder: "input",
    label: "input label",
    type: "text",
    divClass: "w-2/3",
    inputClasses: "bg-green-200", // you can also style the default inputs by passing this inputClasses key
  },
];

Changing submit

<v-generic-form @submit="onSubmit">
  <template v-slot:submit>
    <button type="submit">my custom submit button!</button>
  </template>
</v-generic-frorm>

Asynchronous validation

<template>
  <div>
    <v-generic-form ref="form" :fields="fields" @submit="onSubmit" :beforeSubmit="beforeSubmit" />
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: "Form",
  computed: {
    fields() {
      return [
        {
          name: "name",
          placeholder: "Your Name",
          label: "Name",
        },
        {
          name: "email",
          placeholder: "Your Email",
          label: "Email",
        },
      ];
    },
  },
  methods: {
    async beforeSubmit({email}) {
      const { data: { exists } } = await axios.post("https://myendpoint.com/checkEmails", { email });
      if (exists) {
        // prevent form from submitting by setting a custom error
        this.$refs.form.setCustomError({
          key: "email",
          value: "Email already exists.",
        });
      } else {
        // we can clear it if it doesn't exist
        this.$refs.form.setCustomError({
          key: "email",
          clear: true,
        });
      }
    },
  },
};

</script>

Defining your own layout

<template>
  <v-generic-form :schema="schema"  @submit="submit" ref="form">
    <template v-slot="{ errors, firstError, formData }">
        <generic-input v-model="formData.test1" />
        <span class="text-red-500">{{ firstError('test1') }}</span>
        <generic-input v-model="formData.test2" />
        <span class="text-red-500">{{ errors('test1') }}</span>
        <button class="custom-submit" type="submit">Submit</button>
    </template>
  </v-generic-form>
</template>

<script>
import GenericInput from "./GenericInput.vue";

export default {
  name: "CustomForm!",
  components: {
    GenericInput,
  },
  methods: {
    submit(data) {
      console.log(`data`, data);
    },
  },
  computed: {
    schema() {
      return {
        test1: {
          defaultValue: "test",
          rules: {
            required: true,
          },
        },
        test2: {
          rules: {
            required: true,
          },
        },
      };
    },
  },
};

</script>

<style scoped>
.custom-submit {
  background: blue;
  color: white;
  padding: 0.5rem 2rem;
  display: block;
  margin-top: 1rem;
  cursor: pointer;
}
</style>

Using your own formData object

<template>
  <v-generic-form :schema="schema"  @submit="submit" :customFormData="customFormData" ref="form">
    <template v-slot="{ errors, firstError, formData }">
        <generic-input v-model="formData.test1" />
        <span class="text-red-500">{{ firstError('test1') }}</span>
        <generic-input v-model="formData.test2" />
        <span class="text-red-500">{{ errors('test1') }}</span>
        <button class="custom-submit" type="submit">Submit</button>
    </template>
  </v-generic-form>
</template>

<script>
import GenericInput from "./GenericInput.vue";

export default {
  name: "CustomForm!",
  components: {
    GenericInput,
  },
  data() {
    return {
      customFormData: {
        test1: "",
        test2: "434",
      },
    };
  },
  methods: {
    submit(data) {
      console.log(`data`, data);
    },
  },
  computed: {
    schema() {
      return {
        test1: {
          defaultValue: "test",
          rules: {
            required: true,
          },
        },
        test2: {
          rules: {
            required: true,
          },
        },
      };
    },
  },
};
</script>

Form Props

Prop Type Default Description
fields Array () => [] Fields the form will render
options Object () => {} Options to pass to the form: parentClasses, formClass
hideSubmit Boolean false Toggle to show the submit button or not
beforeSubmit Function () => {} Do something before the form is officially submitted
onDataChange Function () => {} Watches form on change of form data
options.formClass String "" Classes of the form
options.parentClasses String "" Classes for all parent divs of fields
options.requireAllFields Boolean false Quick way of making every field required


Field options

Option Type Default Description
name String "" The name the generic form will track the input as
placeholder String "" Placeholder
type String "text" Input type
label String "" Label
defaultValue Any "" The default value of the field
rules Object null The validation rules of the field
divClass String "" Parent level div class of the field
inputClasses String "" Classes of the input
show Boolean true Toggle showing the field
customData Object () => {} Bag of custom data to pass to a component

Readme

Keywords

none

Package Sidebar

Install

npm i v-generic-form

Weekly Downloads

3

Version

1.1.13

License

none

Unpacked Size

489 kB

Total Files

12

Last publish

Collaborators

  • tnorthern96