Rendering

#Elements

You can define the elements of a form via it's schema property:

export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      // element definitions
    }
  })
}

The schema is an object which contains the schema of each element by their names:

export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      firstname: {
        // element schema
      },
      lastname: {
        // element schema
      },
      email: {
        // element schema
      },
    }
  })
}

Element schemas contain information about the element, like it's type, label, default value, etc.

First of all, an element has a type property which accepts a string that will define what kind of element it will render. For example text would define a simple type="text" input:

Vue HTML
          
// TextInputForm.vue

<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        name: {
          type: 'text'
        }
      }
    })
  }
</script>
        
          
<div id="app">
  <text-input-form></text-input-form>
</div>
        

Let's add some other fields and some extra properties:

Vue HTML
          
// JohnDoeForm.vue

<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        firstname: {
          type: 'text',
          label: 'First name',
          placeholder: 'eg. John'
        },
        lastname: {
          type: 'text',
          label: 'Last name',
          placeholder: 'eg. Doe',
        },
        email: {
          type: 'text',
          inputType: 'email',
          label: 'Email address',
          placeholder: 'eg. john@doe.com'
        },
      }
    })
  }
</script>
        
          
<div id="app">
  <john-doe-form></john-doe-form>
</div>
        

Now it looks much more like an actual form, with useful fields to fill in. Last name and First name looks kinds dull under each other. Shouldn't they placed next to each other with a let's say a single Name label?

Most form generators out there fail at this point, because in order to do that you have to do some CSS hacks. But not Laraform! Laraform was built with nested elements in mind so placing two fields next to each other shouldn't be a problem. Keep reading!

#Nested Elements

One of the most powerful features of Laraform has is that it enables you to nest elements and therefore build even the most complex forms with ease. In this section we will learn how to use different type of nesting methods.

#Group Element

Group element is to give you a wrapper around the elements. It collects a set of element and renders them as if they were one, contained by an other element.

To really understand the use of this nesting type, let's stick for a while with our previous example and see how First name and Last name can be placed next to each other with a single label.

Define an element in the schema with group type and put the elements it should contain under it's schema property:

          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        name: {
          type: 'group',
          label: 'Name',
          schema: {
            firstname: {
              type: 'text',
              placeholder: 'First name',
              columns: 6
            },
            lastname: {
              type: 'text',
              placeholder: 'Last name',
              columns: 6
            }
          }
        },
        email: {
          type: 'text',
          inputType: 'email',
          label: 'Email address',
          placeholder: 'eg. john@doe.com'
        },
      }
    })
  }
</script>
        

And that's it! Our fields are now listed in a group, each of them taking up half of the space. You might notice the columns property that has been set to 6 at both elements. That means that those elements take up 6-6 columns space in the place they are rendered and from which you will learn about later.

#Object Element

Object is very similiar to Group, except one thing: they not only nest the elements visually, but also nest their data model.

Although we haven't learned about data model yet, you might suspect that at some point form's data will be submitted and elements need to have their data model represented somehow. This is what the form's data property contains for example, but there are other alternatives to it. As for now that's all you need to know to understand how Object element works.

In our previous example with Group, if the form's data was dumped it would've looked like this:

// `data` of previous Group example

{
  firstname: 'John',
  lastname: 'Doe',
  email: 'john@doe.com',
}

Wrapping the firstname and lastname in the name Object element would nest their data model:

Form `data`:

      
          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        name: {
          type: 'object',
          label: 'Name',
          schema: {
            firstname: {
              type: 'text',
              placeholder: 'First name',
              columns: 6
            },
            lastname: {
              type: 'text',
              placeholder: 'Last name',
              columns: 6
            }
          }
        },
        email: {
          type: 'text',
          inputType: 'email',
          label: 'Email address',
          placeholder: 'eg. john@doe.com'
        },
      }
    })
  }
</script>
        

This is useful if you intend to use the data on the backend as a relationship or store as JSON object for example. In fact this is how you can deal with a hasOne type ORM relationship when processing data.

#List Element

List element is one of a kind in a way that it allows the user to repeat certain type of inputs in a predefined form.

Let's say you have a football team and you want to add the name of different players. To do that you can create a List element which will contain the names of the players and define the repeatable element type under it's element property:

// Form's `data`:

        
          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        team_name: {
          type: 'text',
          label: 'Team name'
        },
        team_members: {
          type: 'list',
          label: 'Team Members',
          element: {
            type: 'text',
            placeholder: 'Name'
          }
        }
      }
    })
  }
</script>
        

Yes, but what if we need to have other informations about the players like their shirt numbers or birthdays? They can be defined by using the object property instead of element:

// Form's `data`:

        
          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        team_name: {
          type: 'text',
          label: 'Team name'
        },
        team_members: {
          type: 'list',
          label: 'Team Members',
          object: {
            name: {
              type: 'text',
              placeholder: 'Name'
            },
            shirt_number: {
              type: 'text',
              placeholder: 'Shirt number'
            },
            birthday: {
              type: 'date',
              placeholder: 'Birthday'
            }
          }
        }
      }
    })
  }
</script>
        
To nest on multiple levels always make sure you use the object property as element is strictly for single element types.

Sorting List Items

In some lists it might be important to enable the user to sort the items of the list. If you want to allow the users to do that set sort property to true and the items will become sortable by dragging.

Let's see the example of a very simple to do list:

          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        todo: {
          type: 'list',
          label: 'To-Do',
          sort: true,
          element: {
            type: 'text'
          }
        }
      }
    })
  }
</script>
        

To store the order of the items you may define a storeOrder property which points to the element of a repeatable object:

// 'data' property of the form:

        
          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        todo: {
          type: 'list',
          label: 'To-Do',
          sort: true,
          storeOrder: 'order',
          object: {
            schema: {
              name: {
                type: 'text'
              },
              order: {
                type: 'meta',
              }
            }
          }
        }
      }
    })
  }
</script>
        

Learn more about the capabilities of List at List element page.

#Buttons

You can define different buttons at the bottom of your form using buttons property. The buttons property accepts an array of objects which define the buttons' behavior:

By default if you are adding any type of button to the form it will trigger the form's .submit() method when clicked:

export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      // ...
    },
    buttons: [
      {
        label: 'Submit'
      }
    ]
  })
}

The form submission can be prevented by setting prevent to true and you can define custom behavior via onClick property which accepts a function:

export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      // ...
    },
    buttons: [
      {
        label: 'Submit'
      },
      {
        label: 'Reset',
        prevent: true,
        onClick() {
          this.form$.reset()
        }
      }
    ]
  })
}

As you can see we are using this in the onClick function of Reset button which accesses form$ property which is a reference to the root Laraform component.

Buttons also accept a class property which will append class to the buttons class list:

buttons: [
  {
    label: 'Submit',
    class: 'btn-primary'
  },
  {
    label: 'Reset',
    prevent: true,
    onClick() {
      this.form$.reset()
    },
    class: 'btn-secondary'
  }
]

They can also define their disabled status by creating a function which returns either true or false:

buttons: [
  {
    label: 'Submit',
    class: 'btn-primary',
    disabled() {
      return this.form$.disabled
    }
  }
]

In this case the Submit button will be disabled if the form's disabled status is true.

You can define Vue Lifecycle Hooks for each button via it's schema:

buttons: [
  {
    label: 'Submit',
    class: 'btn-primary',
    created() {
      // do something
    }
  }
]

Now let's see a complete example to check out buttons in action:

          
<script>
export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      name: {
        type: 'text',
        label: 'Name',
        rules: 'required'
      }
    },
    buttons: [
      {
        label: 'Submit',
        class: 'btn-primary',
        disabled() {
          return this.form$.disabled
        }
      },
      {
        label: 'Reset',
        class: 'btn-secondary',
        prevent: true,
        onClick() {
          this.form$.reset()
        }
      }
    ]
  })
}
</script>
        

#Form

By default Laraform renders a complete form with all the elements included in schema and also helper components for displaying errors, showing buttons, etc. In case you need to define a less straightforward form with unique template you might render different elements or groups on their own as you'll see in the following.

#Inline Elements

When form loops through it's schema property actually what it does is that it renders element components and pass on their element schema and name. For example a text type will render <TextElement> a group a <GroupElement> and so on. As you will see in the following, these elements can also used as inline components within the form.

Rendering Single Elements

Laraform renders all elements defined in schema by default. This is not the neccesarily the expected result you always want to have, so let's see how you can render elements one by one and place whatever else you want in a form's template.

Let's create a simple form with basic fields:

// SingleElementsForm.vue

<script>
export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      name: {
        type: 'text',
        label: 'Name'
      },
      email: {
        type: 'text',
        label: 'Email'
      },
      phone: {
        type: 'text',
        label: 'Phone'
      },
    }
  })
}
</script>

If everything stays like that, this form will render the elements under each other as you would expect. Now let's define a custom template where we place some custom HTML along with inline elements:

// SingleElementsForm.vue

<template>
  <form
    @submit.prevent="submit"
  >
    <h5>Personal details</h5>
    <TextElement
      name="name"
      :schema="schema.name"
      v-ref:elements$ 
    />
    <h5>Contact details</h5>
    <TextElement
      name="email"
      :schema="schema.email"
      v-ref:elements$ 
    />
    <TextElement
      name="phone"
      :schema="schema.phone"
      v-ref:elements$ 
    />
  </form>
</template>

<script>
export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      name: {
        type: 'text',
        label: 'Name'
      },
      email: {
        type: 'text',
        label: 'Email'
      },
      phone: {
        type: 'text',
        label: 'Phone'
      },
    }
  })
}
</script>

Let's see what we have here. First, there's a <form> tag which will invoke the form's .submit().

Then we have <h5> headers and <TextElement> components which have two attributes: name and schema. The name must be the same as the element's key in the schema, while :schema references the element's schema.

And there is the v-ref directive, which is a custom directive enabling us to collect multiple elements under the same reference. This special directive is required to be included anytime you use a single element component, because that's how Laraform knows they should be part of the form.

When using inline elements like <TextElement> always make sure to add the v-ref:elements$ directive, because that's how Laraform knows they are part of the form.

So what all of this gives us is a nice form where fields are placed under different headers:

          
<template>
  <form
    @submit.prevent="submit"
  >
    <h5>Personal details</h5>
    <TextElement
      name="name"
      :schema="schema.name"
      v-ref:elements$ 
    />
    <h5>Contact details</h5>
    <TextElement
      name="email"
      :schema="schema.email"
      v-ref:elements$ 
    />
    <TextElement
      name="phone"
      :schema="schema.phone"
      v-ref:elements$ 
    />
  </form>
</template>

<script>
export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      name: {
        type: 'text',
        label: 'Name'
      },
      email: {
        type: 'text',
        label: 'Email'
      },
      phone: {
        type: 'text',
        label: 'Phone'
      },
    }
  })
}
</script>
        

This is great, but does it really make sense to render the elements under Contact details one by one? Wouldn't it be better to render a single element which collects all of them so we don't have to add them manually? That's where using a Group element can help us!

Rendering Element Groups

We've already discussed Group element previously where we used it as a form of nesting elements. It's specialty was that it can collect elements but does not modify their data model. It can also be very useful to render more elements when used as an inline element.

Let's change our previous example a bit and create a group element type for the contact details:

          
<template>
  <form
    @submit.prevent="submit"
  >
    <h5>Personal details</h5>
    <TextElement
      name="name"
      :schema="schema.name"
      v-ref:elements$ 
    />
    <h5>Contact details</h5>
    <GroupElement
      name="contect"
      :schema="schema.contact"
      v-ref:elements$ 
    />
  </form>
</template>

<script>
export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      name: {
        type: 'text',
        label: 'Name'
      },
      contact: {
        type: 'group',
        schema: {
          email: {
            type: 'text',
            label: 'Email'
          },
          phone: {
            type: 'text',
            label: 'Phone'
          }
        }
      }
    }
  })
}
</script>
        

That's pretty much it. As you can see nothing has changed, we just simplified our template. Now instead of adding the email and phone elements to the template one by one, we added the contact group which contains these two elements.

#Element Layout

Let's move a little bit back to our elements. The basic concept you need to understand about an element's layout is that it has 3 different parts:

  1. The first is element itself which acts like a container.
  2. The second is label which renders the label if there's any.
  3. The third is field where the element component will be placed.

Here it is more visually:

element
label
field

Each time an element is used, whether it's regular or nested will follow the same pattern. In case of a nested element the field part will contain all other elements:

element
label
field
element
label
field
element
label
field

Labels of course are not always meant to be next to the input field, so let's see how size can be defined for different parts of the element layout.

#Defining Column Sizes

Most modern CSS frameworks use columns to easily define the width of different parts of elements. Laraform aims to accommodate to this concept by having columns property for it's elements.

The simplest way to define the element size is by adding a columns property to it's schema as a single number:

schema: {
  name: {
    type: 'text',
    label: 'Name',
    columns: 6
  }
}

This will render the element 6 columns wide, which is 50% of a 12 column grid theme (like Bootstrap by default).

schema: {
  name: {
    type: 'text',
    label: 'Name',
    columns: {
      element: 6,
      label: 4,
      field: 8
    }
  }
}

In this case the element will be the same 6 columns wide, but now label will take up 4 columns of the element's space and field will be 8 columns.

When defining label and field you set the column size relative to the element which contains both of them.

Defining Column Sizes For Different Breakpoints

Of course it's not enough to set static column sizes for our elements, we need to think about responsivity. To tackle this Laraform provides a simple way to define column sizes for different breakpoints:

columns: {
  element: {
    md: 6,
    sm: 12
  }
}

Breakpoints are defined by the theme uniquely for different CSS frameworks.

Defining Column Sizes For Form

It would be a nightmare to define column sizes for each element in our form, so luckily we can do that on a form level too:

export default {
  mixins: [Laraform],
  data: () => ({
    columns: {
      element: {
        md: 6,
        sm: 12
      }
    },
    schema: {
      // ...
    }
  })
}

This form will have all of it's elements in full width while label sizes specified for different breakpoints. Elements which have their own columns specified will override form rules.

Defining Column Sizes Globally

It's not much fun to define column sizes for even each of our forms if we are building an admin panel with 50+ forms for example. Therefore you may define default column sizes in global Configuration:

import Vue from 'vue'
import Laraform from '@laraform/laraform'

Laraform.config({
  columns: {
    element: {
      md: 6,
      sm: 12
    }
  }
})

Vue.use(Laraform)

const app = new Vue({
  el: '#app'
})

Of course if a form or an element has it's own columns property defined the default config values will be overwritten by them.

Rendering Column Classes

In fact when you set the columns as defined above the final result will be some extra classes added to the element based on the theme's column definition pattern. In Bootstrap 3 for example this pattern is col-${breakpoint}-${size}. So column sizes are transformed to classes and added to different parts of the element layout in templates the theme provides.

That's all you need to know right now, if you want to understand themes better check out Style & Theme chapter and see the next section to learn more about binding classes the element layout.

#Element Slots

Basically each part of the elements, like label, error message or the field itself can be replaced via slots. To demonstrate this let's look at an example:

<template>
  <form submit.prevent="submit">
    <TextElement name="code" :schema="schema.code" v-ref:elements$>
        <label
          slot="label"
          slot-scope="{ el$ }"
          :for="el$.id"
        >{{ el$.label }}</label>
    </TextElement>
  </form>
</template>

<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        code: {
          type: 'text',
          label: 'Code'
        }
      }
    })
  }
</script>

In this scenario we are using inline element and we have an easy job using slots. You can find the list of available slots at each element's API reference and the common ones at Base Element.

As elements are usually not inline, but defined in the schema, let's see how we can still use slots in them:

export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      code: {
        type: 'text',
        label: 'Code',
        slots: {
          label: Vue.extend({
            props: ['el$'],
            template: `
              <label
                slot="label"
                slot-scope="{ el$ }"
                :for="el$.id"
              >{{ el$.label }}</label>
            `
          })
        }
      }
    }
  })
}

Using the slots option of the element will enable you to achieve the same results by creating components on the fly as if they were inline slots.