Hone logo
Hone
Problems

Dynamic Form Generation in Vue.js

This challenge focuses on building a flexible and reusable Vue.js component that can generate forms dynamically based on a provided configuration. This is a common requirement in applications where user input fields need to adapt to different data structures or user roles. You will create a DynamicForm component that takes a schema and renders the appropriate input elements, allowing for data binding and basic validation.

Problem Description

You are tasked with creating a Vue.js component named DynamicForm that renders an HTML form based on a configuration schema. The component should accept a schema prop, which is an array of objects, each describing a single form field. For each field defined in the schema, the component should render the corresponding HTML input element.

Key Requirements:

  • Component Structure: Create a Vue.js component (e.g., DynamicForm.vue) that accepts a schema prop.
  • Schema Definition: The schema prop will be an array of objects. Each object will have at least the following properties:
    • name: A string representing the unique name of the field (used for v-model).
    • label: A string for the field's label.
    • type: A string specifying the input type (e.g., "text", "email", "password", "number", "textarea", "select", "checkbox", "radio").
  • Input Rendering: Based on the type property, render the appropriate HTML input element:
    • "text", "email", "password", "number": Render <input :type="field.type">.
    • "textarea": Render <textarea>.
    • "select": Render a <select> element with <option>s. The schema object for select types should also include an options property, which is an array of { value: string, text: string } objects.
    • "checkbox": Render an <input type="checkbox">.
    • "radio": Render a group of <input type="radio"> elements. The schema object for radio types should also include an options property similar to "select".
  • Data Binding: Use v-model to bind the form fields to a data object. The DynamicForm component should emit an update:modelValue event when any of its fields change, allowing parent components to manage the form data.
  • Labeling: Each form field should be associated with a <label> element.
  • Basic Validation (Optional but Recommended): For common types like "text", "email", and "number", consider adding basic required attribute support if specified in the schema (e.g., via a required: boolean property).
  • TypeScript: The entire solution should be written in TypeScript.

Expected Behavior:

When a schema is provided, the DynamicForm component should render a form with inputs, labels, and appropriate elements for each field. Changes made by the user in the form should update the bound data object in the parent component.

Edge Cases:

  • Empty Schema: The component should gracefully handle an empty schema array (render nothing or a message).
  • Invalid Schema Structure: While not explicitly required to handle malformed schema objects rigorously, consider how unexpected types might be handled (e.g., render a default text input or skip the field).
  • select and radio options: Ensure correct rendering and selection when options are provided.

Examples

Example 1:

[
  { "name": "username", "label": "Username", "type": "text", "required": true },
  { "name": "email", "label": "Email Address", "type": "email" },
  { "name": "password", "label": "Password", "type": "password" }
]

Output: A form with three fields:

  1. A label "Username" followed by an <input type="text" name="username" required>.
  2. A label "Email Address" followed by an <input type="email" name="email">.
  3. A label "Password" followed by an <input type="password" name="password">.

Example 2:

[
  { "name": "country", "label": "Country", "type": "select", "options": [
    { "value": "", "text": "Select a country" },
    { "value": "us", "text": "United States" },
    { "value": "ca", "text": "Canada" }
  ] },
  { "name": "subscribe", "label": "Subscribe to newsletter", "type": "checkbox" }
]

Output: A form with two fields:

  1. A label "Country" followed by a <select name="country"> with three <option>s.
  2. A label "Subscribe to newsletter" followed by an <input type="checkbox" name="subscribe">.

Example 3:

[
  { "name": "gender", "label": "Gender", "type": "radio", "options": [
    { "value": "male", "text": "Male" },
    { "value": "female", "text": "Female" },
    { "value": "other", "text": "Other" }
  ] },
  { "name": "bio", "label": "Biography", "type": "textarea" }
]

Output: A form with two fields:

  1. A label "Gender" followed by three radio buttons (name="gender") with labels "Male", "Female", "Other".
  2. A label "Biography" followed by a <textarea name="bio">.

Constraints

  • The schema prop will be an array of objects.
  • Each object in the schema will have at least name, label, and type properties.
  • Supported type values are: "text", "email", "password", "number", "textarea", "select", "checkbox", "radio".
  • For "select" and "radio" types, the options property will be an array of objects with value and text properties.
  • The component must use v-model for data binding and emit update:modelValue.
  • Use TypeScript for component definition and props.

Notes

  • Consider using a computed property or a method to iterate over the schema and render the appropriate template for each field.
  • The v-model directive in Vue handles different input types automatically. You'll need to ensure the correct event is emitted for the parent to update its model.
  • For radio buttons, ensure they share the same name attribute to function as a group.
  • Think about how to structure the DynamicForm component to be clean and maintainable, especially with the conditional rendering of different input types.
  • You can leverage Vue's <component :is="..."> feature for rendering dynamic components, or use v-if / v-else-if for a more straightforward approach in this case.
Loading editor...
typescript