Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Tweaking the Form Layout

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $8.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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:

... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 159

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':

... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 159

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:

... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 159

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

... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 159

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:

... 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 - 158

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

... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... lines 100 - 151
new:
fields:
... line 154
- '-slug'
... lines 156 - 158

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':

... 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 - 170

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:

... 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 - 170

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...:

... 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 - 170

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:

... 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 - 170

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:

... 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 - 170

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!

9
Login or Register to join the conversation
Tim-K Avatar
Tim-K Avatar Tim-K | posted 2 years ago | edited

Hi, thanks for the tutorial, which helped me a lot also in using EasyAdmin3.
But I struggle to implement a "two column" layout.

This is "reducing" the width correctly

<br />public function configureFields(string $pageName): iterable<br />{ <br />[...]<br />yield FormField::addPanel('Some Headline 1')->setCssClass('col-sm-6')<br />yield FormField::addPanel('Some Headline 2')->setCssClass('col-sm-6')<br />[...]<br />}<br />

but the blocks are not next to each other. Do you have a tip what I can do?

Reply

Hey @Tim!

> Hi, thanks for the tutorial, which helped me a lot also in using EasyAdmin3

Happy to hear it's useful even if it's not WAY old :).

> but the blocks are not next to each other. Do you have a tip what I can do?

I'm not familiar with the HTML and layout that EasyAdmin 3 uses. For this to work, in the rendered HTML, both those elements would need to be inside their own <div class="row"> element. What do you see in the rendered HTML?

Cheers!

Reply
amcastror Avatar
amcastror Avatar amcastror | posted 5 years ago

Hi, thanks for the tutorial.

You said that we were going to fix the order later (for the slug field). Is that in another chapter of the EasyAdminBundle tutorial?

Reply

Hey Matias,

Yes, it should be in further chapters :)

Cheers!

Reply
Kaizoku Avatar
Kaizoku Avatar Kaizoku | posted 5 years ago

Hi there !
Do you think it's possible to hide a whole group in the 'new' view ?

Regards.

Reply

Hey Kaizoku!

Sorry for the slow reply! Hmm, I doubt this is possible :/. The groups don't even have named keys in yml, so there would be no way to even do a -groupName type of thing to remove it :/. I think, unfortunately, that your best option is to specify new and edit forms independently (the unfortunate part is the duplication). I had another possible idea, which was to use a custom form class for the entire form, and build the fields & groups yourself (and then you could just some PHP logic), but that's not currently possible.

Cheers!

Reply
Kaizoku Avatar

Thanks for the answer.
Actually it's not a big deal as I'm removing the fields with a -field, so I just have the panel title with and empty body.
I guess I can use some JS to hide the panel, but it's really not important.

Reply

Hey Kaizoku ,

Yeah, JS can do the trick, but I bet it's possible to override some templates and hide it on the backend level instead.

Cheers!

Reply

This worked for me:

            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-scientist-field }
            edit:
                fields:
                    -
                        type: group
                        css_class: col-sm-6 new-row
                        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 }
            # new:
            #     fields:
            #         - '-id'
            #         - '-slug'```

But, of course, it forces the separated groups to be at the end of the form.
Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built on an older version of Symfony & EasyAdminBundle. Many of the concepts are the same, but you can expect major differences in newer versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.7
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.17.0
        "sensio/distribution-bundle": "^5.0", // v5.0.25
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.4
        "knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "stof/doctrine-extensions-bundle": "^1.2", // v1.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "javiereguiluz/easyadmin-bundle": "^1.16" // v1.17.21
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.7
        "symfony/phpunit-bridge": "^3.0", // v3.4.40
        "nelmio/alice": "^2.1", // v2.3.5
        "doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
    }
}
userVoice