DEV Community

NN3
NN3

Posted on

Making Powerful and Scalable Forms (ft. Vue.js)

Intro


Want it or not, u will have to build a form at some point in your life, every developer has to, eventually. Are you a web developer looking to collect data from users?! Perhaps you are a small business owner gathering customer information, conducting market research, handle inquiries, or receive orders. Or are u a teacher making a quiz for your students? Do u work on HR, are u a Marketing professional, non profit organization, making a event, do u work in healthcare? Those are just some of the occupations where forms are used on a frequent basis.

As a programmer I can relate to the troubles of having to code all the input field and rearranging them in the correct way, perform validation, styling, and making field specific adjustments. Even though not hard it gets annoying and repetitive very quick, and I don’t know about you but that’s not how I envisioned spending my weekend. While forms will vary from person to person, a lot of these problems can be reduced and made easier to manage and control. In the next 4 to 7 minutes 30 seconds I will show you how u can abuse the power of Vue.js to build a powerful and scalable form that is both functional and esthetic.


Lets first get a basic concept of how we want our little app to function. We want to simplify the production of a form with multiple input fields where the user can enter information. Ideally we also want each input field to look and function in an acceptable manner (not just a div with 10 input tags). To accomplish that functionality we can add attributes to each input field. Only problem is that we have to set each attribute, and then set a value, and do that for all fields… That sounds too tedious, a better way to approach this is to have an object that holds only the essential information that changes from field to field and keep the things that persist in all fields as one variable.
We can accomplish that by having a .json file that holds all that information. Lets get a basic sketch of how its going to look

{
     "caseDependendInformation": [
        {obj1},
        {obj2},
        {obj3}
],
    "persistantInformation":"type=text",

}
Enter fullscreen mode Exit fullscreen mode

Each object inside that array represents specific changes that we want in one of the fields but not nesseseraly the others

{
     "caseDependendInformation": [
        {"placeholder":"your name"},
        {"placeholder":"your last name" , "id":"1"},
        {"type”:”number"}
],
    "persistantInformation":"type=password",

}
Enter fullscreen mode Exit fullscreen mode

That’s it with the information we are going to provide. We might make changes later to keep the structure of the .json file inline with our code. For now lets shift our focus in the main .vue file. For starters in the app.vue file in the template we make a title and also place a <form> tag

<template>
  <div>
    <h1>My Supper Cool Awesome Form</h1>
    <form>
      Stuff will go here
    </form>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

This is the starting place of our application. All the inputs, labels and buttons will live here.
To produce all the different input fields we can use the v-for directive.
When u place the v-for directive in a tag that then produces a specified amount of tags with similar logic.
The v-for directive has a simple enough syntax to pick up. So I will give u an example that should make things easier for you to pick up on your own, But basically the deal is that u provide it with either an object or an array and it will create as many tags as the length of said object or array (with some extra functionality of returning each item in every iteration)

<script setup>
const items = ["john","bill","ted"]
</script>

<template>

  <div v-for="item in items">
    <h1>hi {{item}} </h1>
  </div>

</template>
Enter fullscreen mode Exit fullscreen mode

Output:
output of code above picture

The item is an instance of that array. It’s a naming convention (that I personally don’t like) to use a word that represents a single instance of what your array is named. For example:

  • Item in items
  • Apple in apples
  • Page in book
  • Organ in human (perhaps in a medical application ???)
  • Hair in hair (as in a single hair in the hair of the top of your head😄)

(u can go on annoying who reviews your code like that. Try it its fun)
But my point is that it can be named anything. The following would also work

 <div v-for="i in items">
<h1>hi {{i}} </h1></div>
Enter fullscreen mode Exit fullscreen mode

Output:

output of code above picture

In the first iteration item has the value of john, in the second it has the value of bill and so on
Also when the {{}} is used, it means that whatever u write inside of the brackets can be interpreted as a valid javascript expression.
(expression means anything that can be printed. For example:

  • acceptable
1. 2+2
2. “hello”.toUpperCase()
3. 100==100
Enter fullscreen mode Exit fullscreen mode
  • Not acceptable
1. Var a
2. Function greet(msg){
3. Console.log msg}
4. If(a==b){/*do something*/}
Enter fullscreen mode Exit fullscreen mode

Lets do one more example that also uses the .json file we have

<script setup>
import data from "./data.json";
</script>

<template>

  <div v-for="i in data.caseDependendInformation">
    <input type="data.persistantInformation">
  </div>

</template>
Enter fullscreen mode Exit fullscreen mode

Output:

output of code above picture

Ops we have to change the syntax of the .json a little bit

{
     "caseDependendInformation": [
        {obj1},
        {obj2}
],
    "persistantInformation":"password",

}
Enter fullscreen mode Exit fullscreen mode

Output:

output of code above picture

We see now it works just fine. I the output we have 2 input fields of type text. But we see that the other stuff like the placeholders or maybe the id’s don’t get implemented. To fix that we can use another directive know as v-bind.

Big Boring Definition:

In Vue.js, v-bind is a powerful and essential directive that facilitates dynamic data binding between the Vue instance and HTML elements. Often represented by the shorthand notation :, v-bind enables seamless synchronization of data changes with the user interface by establishing connections between Vue data properties and DOM element attributes. One of the key usages of v-bind is in the form of v-bind="someObject", where someObject is a JavaScript object containing key-value pairs, representing attribute names and their corresponding values. By using this syntax, multiple attributes of an HTML element can be bound to data properties in a single operation, streamlining the process of applying dynamic changes to the UI based on the Vue instance's data. This approach is particularly useful when working with components that require a set of attributes to be conditionally applied or when managing complex CSS classes. By leveraging v-

Oversimplified Definition:

In reality when u provide an object to the v-bind it breaks it into attribute, value pairs

When u use v-bind on a tag and give it an object (say )

<script setup>
import data from "./data.json";
</script>

<template>

  <div v-for="item in data.caseDependendInformation">
    <input v-bind="i"/>
  </div>

</template>
Enter fullscreen mode Exit fullscreen mode

Vue looks at this and says

<input v-bind="{“placeholder”:”your last name” , “id”:”1”},”/>
Enter fullscreen mode Exit fullscreen mode

Which in turn becomes

<input placeholder=your last name id=1/>
Enter fullscreen mode Exit fullscreen mode

Notice that that is the exact effect we want to achieve. So by using v-bind in a v-for loop we can create as many div’s as we have objects inside the array of the .json file and we can also conditionally add attributes from the caseDependendInformation.

At this point our code would look something like this

<template>
  <div>
    <h1>My Supper Cool Awesome Form</h1>
    <form>
      <div v-for="i in data.caseDependendInformation"> 
          <div>
            <input v-bind="i" />
          </div>
      </div>
    </form>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Doing it this way works.. But remember, the hole reason we are doing this is in order to write as little as possible in the .json file. If we want 10 type=text input fields it would be smarter to somehow use that persistantInformation variable that we have. To do that we can use yet another Vue directive(s) called the v-if, v-else-if and v-else directives. Again the way those directives function is pretty self explanatory but the point is that if whatever is inside the "" is true, the tag that the directive is placed in, appears on the screen. Else it and all its children and family tree inside that tag gets ignored. Here is an example to better understand that concept

<template>

  <div v-if="true">
    <h1>Hello there</h1>
  </div>
  <div v-else-if="true">
    <h1>this wont get rendered as the above one was true</h1>
  </div>
  <div v-else="true">
    <h1>same with this one</h1>
  </div>

  <div v-if="false">
    <h1>Hello the return</h1>
  </div>
  <div v-else-if="true">
    <h1>the first statment failed so i will appear instead :)</h1>
  </div>
  <div v-else-if="false">
    <h1>this will never get rendered</h1>
  </div>

  <div v-if="false">
    <h1>Hello the last sceene</h1>
  </div>
  <div v-else-if="false">
    <h1>im running out of stuff to write :(</h1>
  </div>
  <div v-else-if="true">
    <h1>if all else fails write error 404 or u know... let the user know</h1>
  </div>

</template>
Enter fullscreen mode Exit fullscreen mode

Output:

output of code above picture

We can use the v-if directives to make it that when we have persisting information it goes in a different block, a block who’s code supports that action. Here is how something like that could look like

<template>
  <div>
    <h1>My Supper Cool Awesome Form</h1>
    <form>
      <div v-for="i in data.formFields" :key="i.id">
        <div>
          <div v-if="data.persistantType == ''">
            <input v-bind="i" />
          </div>
          <div v-else>
            <input :type="data.persistantType" v-bind="i" />
          </div>
        </div> 
      </div>
    </form>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Here as we iterate the objects in the data array and we check if there is a persistantType. If so we go in the block that initializes the persistent type (v-else directive). Else we go in the other one where all the information about the attributes is stored in the array objects (v-if directive).

Believe it or not that’s it. With the above code we should be able to accomplish what we initially intended (to make forms with as few lines of code). Of course its not done until we have some styling. And we can add some finishing touches for it to look prettier but also to function better, but the main idea is done. Here is a few more things we can do (of course this is according to one’s preference. This is how I thing this form should look for my needs, feel free to make any changes that better express your final view of YOUR implementation of this app):

  • Add a label that describes the text field on top of it.
  • Some way to render messages inbetween the input fields.
  • A submit button.
  • Styles… of course.
  • A little code inside the script tag to auto generate id’s.
  • U can the v-if before the v-for so u don’t have to check every time (this will improve performance on forms that are bigger than David Blane’s balls, for the most part negledgable performance boost. Still its nice to have).

And here is the end product

<script setup>
import d from "./data.json";

const data = d
const newData = data.formFields.map((field, index) => {
  return { ...field, id: index.toString() };
});
const submitForm = (event) => {/*do something*/}
</script>

<template>
  <div>
    <h1>My Supper Cool Awesome <span class="rainbow-text">Fabulous</span> Form</h1>
    <form>
      <div v-for="i in data.formFields" :key="i.id">
        <div class="msg" v-if="i.type =='label'">
          <h3> {{ i.msg }}</h3>
          <hr/>
        </div>
        <div v-else>
          <div v-if="data.persistantType == ''">
            <label>{{ i.label }}</label>
            <input v-bind="i" />
          </div>
          <div v-else>
            <label>{{ i.label }}</label>
            <input :type="data.persistantType" v-bind="i" />
          </div>

          <!--<input v-bind="{ 'type':'inputType', 'value': 'inputValue', 'disabled': 'isDisabled'}">-->
        </div> 
      </div>

      <div class="form-container">
        <button type="submit" id="submit" @click.prevent="submitForm">Submit</button>
      </div>
    </form>
  </div>
</template>

<style scoped>
  @keyframes rainbow {
    0% { color: rgb(255, 0, 0); }
    10% { color: rgb(255, 127, 0); }
    20% { color: rgb(255, 255, 0); }
    30% { color: rgb(0, 255, 0); }
    40% { color: rgb(0, 255, 255); }
    50% { color: rgb(0, 255, 215); }
    60% { color: rgb(0, 0, 255); }
    70% { color: rgb(75, 0, 130); }
    80% { color: rgb(148, 0, 211);}
    100% { color: rgb(255, 0, 0);}
}
  .rainbow-text {
    animation: rainbow 2s linear infinite;
  }
    label {
      display: inline-block;
      margin-bottom: 0.5rem;
      text-transform: uppercase;
      color: rgb(61, 59, 59);
      font-weight: 700;
      font-size: 0.8rem;
      padding-left: 50px;
    }
  input {
    display: block;
    width: 90%;
    padding: 0.5rem;
    margin: 0 auto 1.5rem auto;
  }
  div .msg{
    margin: 50px 0px;
  }
  div .msg label{
    padding-left: 0px;
  }
  .form-container {
      display: flex;
      flex-direction: column;
      align-items: end ;
      justify-content: center;
      height: 15vh; /* Adjust the height as needed */
    }
  button[type="submit"] {
        background-color: rgb(65,105,225);
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        font-size: 16px;
        cursor: pointer;
      }
      /* Hover effect */
      button[type="submit"]:hover {
        background-color: rgb(65,105,225);
      }
</style>
Enter fullscreen mode Exit fullscreen mode

Using this a data.json, we have

{
     "formFields": [

        { "type": "label", "id": "0" ,"msg":"About You:"},
        { "label":"first name" , "type":"text", "id":"1" , "name":"firstname" , "placeholder":"First Name" },
        { "label":"last name" , "type":"text", "id":"2" , "name":"firstname" , "placeholder":"Last Name" },
        { "label":"Age" , "type":"number", "id":"3" , "name":"firstname" , "placeholder":"Age" },
        { "label":"phone number" , "type":"tel", "id":"4" , "name":"firstname" , "placeholder":"Phone Number" },
        { "label":"adress" , "type":"text", "id":"5" , "name":"firstname" , "placeholder":"Adress" },
        { "label":"email" , "type":"email", "id":"6" , "name":"firstname" , "placeholder":"Example@Gmailcom" },
        { "type": "label", "id": "3" ,"msg":"About the job"},
        { "label":"job" , "type":"text", "id":"7" , "name":"firstname" , "placeholder":"Job Title" },
        { "label":"department" , "type":"text", "id":"8" , "name":"firstname" , "placeholder":"Department" },
        { "type": "label", "id": "15" ,"msg":"Your Skills"},
        { "label":"JS" , "type":"checkbox" , "id":"9" , "name":"JS" , "value":"JS" },
        { "label": "HTML" , "type":"checkbox" , "id":"10" , "name":"HTML" , "value":"HTML" },
        { "label": "CSS" , "type":"checkbox" , "id":"11" , "name":"CSS" , "value":"CSS" },
        { "type": "label", "id": "3" ,"msg":""},
        { "label":"expert" , "type":"radio", "id":"12", "name":"confidence_lvl" , "value":"expert" },
        { "label":"mediocre" , "type":"radio", "id":"13", "name":"confidence_lvl", "value":"mediocre" },
        { "label":"bad" , "type":"radio", "id":"14", "name":"confidence_lvl" , "value":"bad" }
    ],
    "persistantType":"",
    "tip":"u can write a label with an empty msg to have that line effect :)",
    "tip 2":"u dont need to put id's manually. I coded a thing that assighns id's from 0 to however many items u have"
}
Enter fullscreen mode Exit fullscreen mode

Output:

output of code above pictureoutput of code above pictureoutput of code above picture

Top comments (0)