Customizing Layout
#Element Layout
The basic concept you need to know about an element's layout is that it has 3 different parts:
- The first is
element
itself which acts like a container. - The second is
label
which renders the label if there's any. - The third is
field
where the element component will be placed.
Here it is more visually:
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:
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 its schema as a single number:
public function schema() {
return [
'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).
public function schema() {
return [
'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.
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:
<?php
namespace App\Forms;
class ColumnsForm extends \Laraform
{
public $columns = [
'element' => [
'md' => 6,
'sm' => 12
]
];
public function 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:
/*
|--------------------------------------------------------------------------
| Columns
|--------------------------------------------------------------------------
|
| Default column settings.
|
*/
'columns' => [
'element' => 12,
'label' => 12,
'field' => 12,
],
Of course if a form or an element has its 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 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.
#Form Layout
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 a custom structure, or even elements or groups on their own as you'll see later in this section.
#Custom Form Layout
When you create a new form component using Laraform
mixin, it uses a default <template>
Laraform provides. You can override it simply and create more advanced structure, if you need to:
<template>
// Your template
</template>
<script>
export default {
mixins: [Laraform],
// ...
}
</script>
Check out @laraform/laraform/src/components/Laraform.vue
to see how the default template looks like.
#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 necessarily 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 at resources/js/components/forms/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 change our form and define a custom <template> where we place some custom HTML along with inline elements:
<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 must be used for every single element component, because that's how Laraform knows they should be part of the form.
<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.