# SchemaForm

The SchemaForm requires two props. The first is the schema, which is the meta-data of your form. The second one is modelValue, which will hold the state of the form.

<SchemaForm :schema="mySchema" :modelValue="formData" />

The SchemaForm will $emit update:modelValue events when your components update. This means that you are able to either:

  • use v-model on it
  • or, manually capture the @update:modelValue event with a method of your own while injecting the :modelValue property.

Example with v-model:

<template>
  <SchemaForm :schema="mySchema" v-model="formData" />
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const formData = ref({})
    const mySchema = ref({
      // some schema here
    })

    return {
      formData,
      mySchema
    }
  }
}
</script>

Example with manual bindings:

<template>
  <SchemaForm
    :schema="mySchema"
    :modelValue="formData"
    @update:modelValue="updateForm"
  />
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const formData = ref({})
    const mySchema = ref({
      // some schema here
    })

    const updateForm = form => {
      formData.value = form
    }

    return {
      formData,
      mySchema,
      updateForm
    }
  }
}
</script>

# Props

# schema

The SchemaForm component requires you to pass it a schema property. This schema can be either an object or an array.

In its simplest form, the schema requires you to provide an object with a modelName: value pair for each of the form components you want to add to your form.

Let’s assume for this example that you have a component in your project called FormText which exposes an <input> tag with some CSS.

<template>
  <SchemaForm :schema="schema" v-model="formData" />
</template>

<script>
  import { SchemaForm } from 'formvuelate'
  import FormText from 'path/to/FormText'
  import { ref, markRaw } from 'vue'

  markRaw(FormText)

  export default {
    components: { SchemaForm },
    setup() {
      const schema = ref({
        name: {
          component: FormText
        },
        lastName: {
          component: FormText
        }
      })
      const formData = ref({})

      return {
        schema,
        formData
      }
    }
  }
</script>

TIP

In the above example, we use the component FormText that we imported as the value of the component property of each element.

You can use the name of the component as a String instead, for example 'FormText', but be aware that the component needs to either be imported globally.

If you need to declare your components locally, you can leverage the second parameter of the SchemaFormFactory component. Please refer to the plugins documentation for more information on how to accomplish this.

# Array Schemas

For array based schemas, we need to provide an object for each element of the form, but instead of providing a modelName: value structure, we declare a model property inside of each object.

Here's the above example again using array format.

<template>
  <SchemaForm :schema="schema" v-model="formData" />
</template>

<script>
  import { SchemaForm } from 'formvuelate'
  import FormText from 'path/to/FormText'
  import { ref, markRaw } from 'vue'

  markRaw(FormText)

  export default {
    components: { SchemaForm },
    setup() {
      const schema = ref([
        {
          component: FormText,
          model: 'name'
        },
        {
          component: FormText,
          model: 'lastName'
        }
      ])
      const formData = ref({})

      return {
        schema,
        formData
      }
    }
  }
</script>

An important feature about array schemas is that they allow us the flexibility to create not only vertical forms, but horizontally aligned inputs.

Consider the following meta schema.

const SCHEMA = [
  [ {}, {}, {} ], // 3 inputs in a row
  {}, // 1 input in a row
  [ {}, {} ] // 2 inputs in a row
]

The first and second elements of the schema array are sub-arrays. These sub arrays will be displayed by SchemaForm horizontally by applying a display of flex to their containing div element.

TIP

The div will have a class named schema-row to apply the layout. You can target this class to modify the behavior, or even add style or css classes to your inputs by passing them through the schema.

The example below applies a margin-right style to the first input.

# schemaRowClasses

If you ever require to add additional classes to each structural row in the generated form, you can use the schemaRowClasses property of the SchemaForm component to inject them.

These classes will be appended, and will not substitute the schema-row class that FormVueLate already provides.

The schemaRowClasses prop accepts [String, Object, Array] as valid types, conforming to Vue's class and style binding syntax (opens new window)

<SchemaForm
  schemaRowClasses="my-custom-class-a my-custom-class-b"
  :schema="mySchema"
/>

Example output:


<form>
  <div class="schema-row custom-class-a custom-class-b">
    [...]
  </div>
</form>

# preventModelCleanupOnSchemaChange

By default SchemaForm cleans up the value output of properties that are no longer present inside the schema every time the schema changes.

That means that if at runtime the schema deletes one of the elements inside of it, the output of the modelValue of SchemaForm will no longer contain the user's data if it was already present.

Let's pretend that you have a form that is built with the following schema.

name: {
  label: 'Name',
  component: FormText
},
lastName: {
  label: 'Last name',
  component: FormText
}

If the user fills out both of the inputs, you can expect an output like the following.

{
  name: 'Bobba',
  lastName: 'Fett'
}

If at this point your schema changes, and deletes the lastName property, SchemaForm is smart enough to remove that from the output and emit a new update:modelValue event since that field is effectively gone.

{
  name: 'Bobba'
}

If you want to disable this behavior, set the preventModelCleanupOnSchemaChange to true in your SchemaForm component.

<SchemaForm
  :preventModelCleanupOnSchemaChange="true"
  :schema="mySchema"
/>

Now SchemaForm will not automatically delete the lastName property, even if schema removes the property, and you will preserve the value of the input if it was already present.

# markRaw

You will notice that on our examples we use markRaw(MyImportedComponent) whenever we import a component that is going to be used directly in a reactive schema.

When making a schema reactive by either using ref or computed and setting the components inside directly as the import as in the following example, we are technically also making the component itself reactive.

import MyComponent from 'components/MyComponent.vue'

[...]
const schema = ref({
  name: {
    component: MyComponent
  }
})
[...]

Vue will throw a warning in these cases letting you know that making a component reactive is not something you want to do. It will also hint that you can use either shallowRef or markRaw to address the problem.

We will not go into detail of what these do, because the Vue 3 documentation (opens new window) already covers it in more detail - but we have opted to show the markRaw solution because when creating a computed schema, which can not also be a shallowRef, marking your components with markRaw gets the job done without much further thought.

The previous example then, should be enhanced like so.

import { ref, markRaw } from 'vue'
import MyComponent from 'components/MyComponent.vue'

markRaw(MyComponent)

[...]
const schema = ref({
  name: {
    component: MyComponent
  }
})
[...]

TIP

markRaw is not needed when working with String format for component names in the schema, since FormVueLate leverages :is from Vue's component behind the scenes. Read more about using locally imported components

# Handling submit

SchemaForm will automatically create a <form> wrapper for you on the top level SchemaForm in the case of single and multi dimensional schemas, and fire a submit event when the form is submitted.

This submit event will preventDefault so you can handle the submit on your end.

In order to react and listen to the submit events, simply add a @submit listener to the SchemaForm component in your template.

<template>
  <SchemaForm
    @submit="onSubmit"
    v-model="myData"
    :schema="mySchema"
  />
</template>

# Slots

SchemaForm provides two slots for you to add additional elements to your form.

A beforeForm slot will be provided before the top-most rendered SchemaForm.

Use this for scenarios where you want to provide some element to your form after the <form> tag, but before the SchemaForm.

<form>
  <!-- beforeForm slot content goes here -->
  <SchemaForm />
</form>

An afterForm slot will be provided after the rendered SchemaForm.

Use this to add elements after the SchemaForm and before the wrapping </form> tag. A good example would be a submit button.

<form>
  <SchemaForm />
  <!-- afterForm slot content goes here -->
</form>

TIP

Always use the afterForm slot to add your type="submit" button, that way it will be rendered inside the form tags.

You don't have to listen to this submit button's click events, as SchemaForm will take care of emitting a submit event whenever it is clicked, or the form is submitted in any other way. Read more about handling form submits

# Component Requirements

Now that you have your schema bound into the schema prop, you need to make sure that your components are understood by SchemaForm.

First, make sure that your component accepts a modelValue property. SchemaForm will bind into this property to pass down the current value of the input.

Next, make sure that your component emits an update:modelValue event with the payload of the new input's value whenever it changes. This will allow SchemaForm to update the data internally and emit the update event to the parent.

Example of a simple input component:

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script>
export default {
  props: {
    modelValue: {
      required: true,
      type: [String, Number]
    }
  }
}
</script>