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 aschemaprop. - Schema Definition: The
schemaprop 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 forv-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
typeproperty, 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 anoptionsproperty, 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 anoptionsproperty similar to "select".
- "text", "email", "password", "number": Render
- Data Binding: Use
v-modelto bind the form fields to a data object. TheDynamicFormcomponent should emit anupdate:modelValueevent 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
requiredattribute support if specified in the schema (e.g., via arequired: booleanproperty). - 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
schemaarray (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).
selectandradiooptions: Ensure correct rendering and selection whenoptionsare 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:
- A label "Username" followed by an
<input type="text" name="username" required>. - A label "Email Address" followed by an
<input type="email" name="email">. - 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:
- A label "Country" followed by a
<select name="country">with three<option>s. - 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:
- A label "Gender" followed by three radio buttons (name="gender") with labels "Male", "Female", "Other".
- A label "Biography" followed by a
<textarea name="bio">.
Constraints
- The
schemaprop will be an array of objects. - Each object in the
schemawill have at leastname,label, andtypeproperties. - Supported
typevalues are: "text", "email", "password", "number", "textarea", "select", "checkbox", "radio". - For "select" and "radio" types, the
optionsproperty will be an array of objects withvalueandtextproperties. - The component must use
v-modelfor data binding and emitupdate:modelValue. - Use TypeScript for component definition and props.
Notes
- Consider using a computed property or a method to iterate over the
schemaand render the appropriate template for each field. - The
v-modeldirective 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
nameattribute to function as a group. - Think about how to structure the
DynamicFormcomponent 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 usev-if/v-else-iffor a more straightforward approach in this case.