# SchemaForm

The SchemaForm requires one prop, schema, which is the meta-data of your form. You must also import the useSchemaForm composable which we will use in our setup function to initialize the form's model where the user's data is kept.

<template>
  <SchemaForm :schema="mySchema" />
</template>

<script>
import { ref } from 'vue'
import { SchemaForm, useSchemaForm } from 'formvuelate'

export default {
  components: { SchemaForm },
  setup () {
    const formModel = ref({})
    useSchemaForm(formModel)

    const mySchema = ref({
      // Schema here
    })

    return {
      mySchema
    }
  }
}
</script>

SchemaForm will automatically update the state within your formModel when your components update.

# v-model 2.x

Earlier versions of FormVueLate used v-model as the default way of keeping the two way binding with the form's state. This forced the whole form to re-render whenever any of the child inputs emitted a new value.

v-model is no longer supported, and will not update your form's model. Please use useSchemaForm instead.

# 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" />
</template>

<script>
  import { SchemaForm, useSchemaForm } 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({})
      useSchemaForm(formData)

      return {
        schema
      }
    }
  }
</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" />
</template>

<script>
  import { SchemaForm, useSchemaForm } 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({})
      useSchemaForm(formData)

      return {
        schema
      }
    }
  }
</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 form's model bound by useSchemaForm 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 update the form's model 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.

# useCustomFormWrapper 3.7.0

In some cases, specially when using component libraries, you may need to set a custom form tag for your form as a wrapper.

In these scenarios you can use the prop useCustomFormWrapper of SchemaForm to disable the automatic generation of a wrapping form tag. Be warned that this will also remove the submit event listener that FormVueLate usually adds. So you will have to make sure that either you, or the library are listening and reacting to it.

An basic example using QForm with Quasar would look like this:

<q-form>
  <SchemaForm
    :schema="schema"
    useCustomFormWrapper
  />
</q-form>

# 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"
    :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>