Buy

We've talked a lot about customizing the forms... which mostly means using the config to change the field types or adding a custom form theme to control how the individual fields look.

But, what about the form layout? Like, what if I wanted to put the email & full name fields in a section that floats to the left, and these other two fields in a section that floats to the right?

Well... that's not really part of the form component: we usually do this by adding markup to our template. But of course, the template lives inside EasyAdminBundle!

In turns out, there are two great ways to control your form's layout. First..., well, you could just override the template and go crazy! For example, inside the bundle, find new.html.twig. It has a block called entity_form. And, to render the form, it just calls the form() function. This means that there's no form layout at all by default: it just barfs out all the fields.

But... that's awesome! Because we could override this template, replace just the entity_form block, and go bonkers by rendering the form however we need. And since we can override each template on an entity-by-entity basis... well... suddenly it's super easy to customize the exact form layout for each section.

Form Layout Config Customizations

Phew! But... there is an even easier way that works for about 90% of the use-cases. And that is... of course... with configuration. EasyAdminBundle comes with a bunch of different ways to add dividers, sections and groups inside the form.

So, let's do it! Start with User. Let's reorganize things: put fullName on top, then add a new type called divider. Put avatarUri after the divider, then another divider, email, divider, isScientist and universityName:

203 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 182
User:
... lines 184 - 192
form:
fields:
- { property: 'fullName', type: 'text', help: 'First then Last' }
- { type: 'divider' }
- avatarUri
- { type: 'divider' }
- email
- { type: 'divider' }
- isScientist
- universityName

On a technical level... this is kind of geeky cool: divider is a fake form field! I mean, in the bundle itself, it is literally a form field. And you can see a few others, like section and group. We'll use all three.

Try out the page! Hello divider! It's nothing too fancy, but it's nice.

Adding a Section

To go further, we can divide things into sections by using type: section. For example, start here by saying type: section, label: 'User Details':

209 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 182
User:
... lines 184 - 192
form:
fields:
- { type: 'section', label: 'User details' }
- { property: 'fullName', type: 'text', help: 'First then Last' }
- { type: 'divider' }
- avatarUri
... lines 199 - 209

And then, inside, we'll have fullName, keep the divider, keep avatarUri, but replace the next divider with the expanded syntax: type: section, label: 'Contact Information'. And like many other places, you can add an icon, a help message and a css_class:

209 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 182
User:
... lines 184 - 192
form:
fields:
- { type: 'section', label: 'User details' }
- { property: 'fullName', type: 'text', help: 'First then Last' }
- { type: 'divider' }
- avatarUri
-
type: 'section'
label: 'Contact information'
icon: 'phone'
help: 'Only for emergencies!'
css_class: 'any-you-want'
... lines 205 - 209

With email in its own section, change the last divider to type: section, label: Education:

209 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 182
User:
... lines 184 - 192
form:
fields:
- { type: 'section', label: 'User details' }
- { property: 'fullName', type: 'text', help: 'First then Last' }
- { type: 'divider' }
- avatarUri
-
type: 'section'
label: 'Contact information'
icon: 'phone'
help: 'Only for emergencies!'
css_class: 'any-you-want'
- email
- { type: 'section', label: 'Education' }
- isScientist
- universityName

Ok, let's see how this looks!

Not bad! Each field appears inside whatever section is above it.

Reorganizing all Fields under form

The last organizational trick is the group, which gives you mad control over the width and float of a bunch of fields.

To see an example, go up to the Genus form.

And first, remember how we organized most fields under the form key... but then tweaked a few final things under new and edit? Well, when EasyAdminBundle reads this, all of the fields under form are added first... and then any extra fields under new or edit are added. That means, in edit, our slug field is printed last on the form. And... there's not really a good way to control that. This gets even a little bit more problematic when you want to organize fields into sections or groups. How could you organize the slug field into the same section as name? Right now, you can't!

For that reason, it's best to configure all of your fields under form. Then, use new and edit only to remove fields you don't want. Copy the slug field and remove edit entirely. Then, under form, paste this near the top:

208 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 130
form:
fields:
-
property: id
type_options: {disabled: true}
-
property: 'slug'
help: 'unique auto-generated value'
type_options: { disabled: true }
... lines 140 - 208

To keep slug off of the new form, just add -slug:

208 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 151
new:
fields:
... line 154
- '-slug'
... lines 156 - 208

The end result is the same, but with complete control over the field order.

Adding Form Groups

Ok, back to adding groups. First, move id and slug to the end of the form. Then, on top, add a new group: type: group, css_class: 'col-sm-6', label: 'Basic Information':

220 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 130
form:
fields:
- { type: 'group', css_class: 'col-sm-6', label: 'Basic information' }
... lines 134 - 220

You can picture what this is doing: adding a div with col-sm-6, putting a header inside of it, and then printing any fields below that, but in the div.

And that's huge! Because thanks to the col-sm-6 CSS class, we can really start organizing how things look.

Move funFact and isPublished a bit further down. Then, after subFamily, add a section labeled Optional:

220 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 130
form:
fields:
- { type: 'group', css_class: 'col-sm-6', label: 'Basic information' }
- name
- speciesCount
- { property: 'firstDiscoveredAt', type_options: { widget: 'single_text' }}
- { property: 'subFamily', type: 'easyadmin_autocomplete' }
- { type: 'section', label: 'Optional' }
... lines 139 - 220

Yep, you can totally mix-and-match groups and sections.

At this point, funFact and isPublished will still be in the group, but they'll also be in a section within that group. And since genusScientists is pretty big, let's put that in their own group with css_class: col-sm-6 and label: Studied by...:

220 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 130
form:
fields:
- { type: 'group', css_class: 'col-sm-6', label: 'Basic information' }
- name
- speciesCount
- { property: 'firstDiscoveredAt', type_options: { widget: 'single_text' }}
- { property: 'subFamily', type: 'easyadmin_autocomplete' }
- { type: 'section', label: 'Optional' }
- { property: 'funFact', type: 'textarea', css_class: 'js-markdown-input' }
- isPublished
- { type: 'group', css_class: 'col-sm-6', label: 'Studied by ...' }
... lines 143 - 220

Finally, at the bottom, add one more group. I'll use the expanded format this time: css_class: col-sm-6 and label: Identification. And yep, groups can have icon and help keys:

220 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 130
form:
fields:
- { type: 'group', css_class: 'col-sm-6', label: 'Basic information' }
- name
- speciesCount
- { property: 'firstDiscoveredAt', type_options: { widget: 'single_text' }}
- { property: 'subFamily', type: 'easyadmin_autocomplete' }
- { type: 'section', label: 'Optional' }
- { property: 'funFact', type: 'textarea', css_class: 'js-markdown-input' }
- isPublished
- { type: 'group', css_class: 'col-sm-6', label: 'Studied by ...' }
-
property: 'genusScientists'
type: 'text'
type_options:
mapped: false
attr: { class: 'js-genus-scientists-field' }
-
type: 'group'
css_class: 'col-sm-6'
label: 'Identification'
icon: 'id-card-o'
help: 'For administrators'
-
property: id
type_options: {disabled: true}
-
property: 'slug'
help: 'unique auto-generated value'
type_options: { disabled: true }
... lines 163 - 220

Phew! While I did this, I added some line breaks just so that this all looks a bit more clear: here's one group, here's a second group, the last group is at the bottom.

But what does it actually look like? Let's find out! Refresh!

Oh, this feels good. The "Basic Information" group is on the left with the "Optional section" at the bottom. The other two groups float to the right.

Now, sometimes, you might want to force the "Identification" group to go onto its own line. Basically, you want to add a CSS clear after the first two groups.

To do that, on the group, add a special CSS class, called new-row:

220 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 130
form:
fields:
... lines 133 - 149
-
type: 'group'
css_class: 'col-sm-6 new-row'
label: 'Identification'
icon: 'id-card-o'
help: 'For administrators'
... lines 156 - 220

And now it floats to the next line. So, groups are a really, really neat way to control how things are rendered. It adds some nice markup, and we can add whatever classes we need. So, there's not much you can't do.

Leave a comment!