Module: Vue Forms and User Input

Custom input components

Custom Input Components

Vue.js provides a powerful and flexible way to create custom input components, allowing you to encapsulate complex input logic, styling, and validation into reusable units. This is particularly useful for:

  • Specialized Input Types: Inputs beyond the standard <input> elements (e.g., date pickers, color pickers, rich text editors).
  • Consistent UI: Maintaining a uniform look and feel across your application.
  • Code Reusability: Avoiding duplication of input-related code.
  • Improved Maintainability: Centralizing input logic for easier updates and bug fixes.

Basic Custom Input Component

Let's start with a simple example: a custom input component that wraps a standard <input> element and adds a label.

<!-- MyInput.vue -->
<template>
  <div class="my-input">
    <label :for="id">{{ label }}</label>
    <input
      :id="id"
      :type="type"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>

<script>
export default {
  props: {
    label: {
      type: String,
      required: true,
    },
    type: {
      type: String,
      default: 'text',
    },
    modelValue: { // Use modelValue for v-model compatibility
      type: String,
      default: '',
    },
  },
};
</script>

<style scoped>
.my-input {
  margin-bottom: 10px;
}

label {
  display: block;
  margin-bottom: 5px;
}
</style>

Explanation:

  • template: Defines the component's HTML structure. It includes a label and an input element.
  • props: Defines the properties that can be passed to the component:
    • label: The label text for the input. required: true means it must be provided.
    • type: The input type (e.g., 'text', 'password', 'email'). Defaults to 'text'.
    • modelValue: This is crucial for v-model compatibility. Vue 3 uses modelValue as the prop to receive the value and emits an update:modelValue event to update the value.
  • script: Contains the component's JavaScript logic.
  • $emit('update:modelValue', $event.target.value): This line is the key to making the component work with v-model. When the input's value changes (triggered by the @input event), it emits an event named update:modelValue with the new value. This event is caught by the parent component when using v-model.
  • style scoped: Styles are scoped to this component, preventing them from affecting other parts of your application.

Usage in a Parent Component:

<template>
  <div>
    <MyInput label="Name" v-model="name" />
    <MyInput label="Email" type="email" v-model="email" />
    <p>Name: {{ name }}</p>
    <p>Email: {{ email }}</p>
  </div>
</template>

<script>
import MyInput from './MyInput.vue';

export default {
  components: {
    MyInput,
  },
  data() {
    return {
      name: '',
      email: '',
    };
  },
};
</script>

Advanced Custom Input Component: Validation

Let's add validation to our custom input component.

<!-- ValidatedInput.vue -->
<template>
  <div class="validated-input">
    <label :for="id">{{ label }}</label>
    <input
      :id="id"
      :type="type"
      :value="modelValue"
      @input="handleInput"
      :class="{ 'is-invalid': error }"
    />
    <div v-if="error" class="invalid-feedback">{{ error }}</div>
  </div>
</template>

<script>
export default {
  props: {
    label: {
      type: String,
      required: true,
    },
    type: {
      type: String,
      default: 'text',
    },
    modelValue: {
      type: String,
      default: '',
    },
    validation: {
      type: Function,
      default: () => true, // Default: always valid
    },
  },
  data() {
    return {
      error: '',
    };
  },
  methods: {
    handleInput(event) {
      const value = event.target.value;
      this.error = this.validation(value);
      this.$emit('update:modelValue', value);
    },
  },
};
</script>

<style scoped>
.validated-input {
  margin-bottom: 10px;
}

label {
  display: block;
  margin-bottom: 5px;
}

.is-invalid {
  border-color: red;
}

.invalid-feedback {
  color: red;
  font-size: 0.8em;
}
</style>

Changes:

  • validation prop: A function that takes the input value and returns an error message (string) if the value is invalid, or true (or an empty string) if it's valid.
  • error data property: Stores the validation error message.
  • handleInput method: Called when the input value changes. It calls the validation function, updates the error property, and emits the update:modelValue event.
  • class="{ 'is-invalid': error }: Adds the is-invalid class to the input element if there's an error.
  • v-if="error": Displays the error message if the error property is not empty.

Usage with Validation:

<template>
  <div>
    <ValidatedInput
      label="Username"
      v-model="username"
      :validation="validateUsername"
    />
    <p>Username: {{ username }}</p>
  </div>
</template>

<script>
import ValidatedInput from './ValidatedInput.vue';

export default {
  components: {
    ValidatedInput,
  },
  data() {
    return {
      username: '',
    };
  },
  methods: {
    validateUsername(value) {
      if (value.length < 5) {
        return 'Username must be at least 5 characters long.';
      }
      return true; // Valid
    },
  },
};
</script>

Key Considerations

  • v-model Compatibility: Always use modelValue as the prop for the input's value and emit an update:modelValue event to update the value in the parent component.
  • Props: Carefully define the props your component needs to be flexible and reusable.
  • Events: Emit events to communicate changes or actions to the parent component.
  • Styling: Use scoped styles to avoid conflicts with other parts of your application.
  • Accessibility: Ensure your custom input components are accessible to users with disabilities (e.g., proper labels, ARIA attributes).
  • Complex Components: For very complex input components (e.g., date pickers), consider using a dedicated UI library or building a more elaborate component with its own internal state management.

This provides a solid foundation for creating custom input components in Vue.js. Remember to tailor these examples to your specific needs and requirements.