One of the first things most of us want to do when we hear about components is build form elements. This makes sense, because they feature a lot of repetitive markup that needs to be reused site wide, which is exactly what components are designed for. However, it’s easy to get yourself in a tangle of custom events, global event buses and a myriad of props just to build a simple reusable input box, which can have you questioning the very purpose of components in the first place, but actually, with a little knowhow, building form elements is simple.

Building a Reusable Input Box

In order to demonstrate how to correctly create form elements using components, let’s build a reusable input box using bootstrap. So, first we need a template:

<template id="my-input">
  <div class="form-group">
   <label>{{label}}</label>
   <input type="text" class="form-control">
  </div>
</template>

And our component Vue instance:

props: {
  label: {
   type: String,
   default: ""
  }
}

That’s pretty self-explanatory, we will simply pass in a prop called “label” to render the “label” markup. Next we need to emit the input back to the parent so we can get the information the user has typed, so let’s add that to our input:

<input type="text" class="form-control"  @input="$emit('input', $event.target.value)">

This part is very important. I’m emitting an event called “input” which allows us to use v-model on our component and this is the thing many developers miss. Internally, v-model listens for the input event to update data, so we are essentially overriding the default input event with our own, allowing us to seamlessly pass the component’s input back to our parent, which means we can now add the following to our parent:

<my-input v-model="name" label="name"></my-input>

Check out this JSFiddle to see what I mean: https://jsfiddle.net/vuetiful/3fwegmfs/

Another thing we want to be able to do is pass in a default value, once again v-model applies a “value” attribute to your component, so you can simply add a “value” prop to your component and bind that to your component’s input “value” attribute:

<input type="text" class="form-control"  @input="$emit('input', $event.target.value)" :value="value">

And our Vue instance now looks like this:

 props: {
   label: {
     type: String,
     default: ""
   },
   value: {
     type: String,
     default: ""
   }
 }

All we’ve done is added the “value” prop and bound it to our input, there’s no need to touch the component in the parent. Now if you set a value in the parent it will set it as the default in the component, you can see that here: https://jsfiddle.net/vuetiful/j34rmtd0/

Now, we have an input that is always of type “text”, but we may want it to be another input type such as “password”, so let’s quickly add a type prop that we can bind to our input’s “type” attribute:

<input :type="type" class="form-control"  @input="$emit('input', $event.target.value)" :value="value">

Let’s add that to our props:

props: {
  label: {
    type: String,
    default: ""
  },
  value: {
    type: String,
    default: ""
  },
  type: {
    type: String,
    default: "text"
  }
}

This is all getting pretty exciting, but there’s one final thing. We may want to add other attributes to our text input, such as “placeholder”, but there are loads of them and that list may get updated. We don’t want to manually add all of them as props, then have to update them when more attributes are added, we just want to pass through the ones we want when we want. So, let’s add a prop called “attrs” that’s an object, and use v-bind to bind the object to our text input:

<input :type="type" class="form-control"  @input="$emit('input', $event.target.value)" :value="value" v-bind="attrs">

Our final component view model looks like this:

export default {
  props: {
    label: {
      type: String,
      default: ""
    },
    value: {
      type: String,
      default: ""
    },
    type: {
      type: String,
      default: "text"
    },
    attrs: {
      type: Object,
      default: function() {
        return {};
      }
    }
  }
}

And here’s the end result: https://jsfiddle.net/vuetiful/djwrhyqg/

That’s it, a fully functioning, reusable input that you can use in the same way as a standard input box, without having to deal with a bunch of custom events. Next up I’ll be looking at how to handle checkboxes and radio inputs in Vue 2.2.

Advertisements