Chapters
28 Chapters
|
2:24:59
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
25.
Customizing the Collection Form Prototype
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
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.
This course is built on Symfony 3, but most of the concepts apply just fine to newer versions of Symfony.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.1.3",
"symfony/symfony": "3.4.*", // v3.4.49
"doctrine/orm": "^2.5", // 2.7.5
"doctrine/doctrine-bundle": "^1.6", // 1.12.13
"doctrine/doctrine-cache-bundle": "^1.2", // 1.4.0
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.23.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
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.9.0
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.47
"nelmio/alice": "^2.1", // v2.3.6
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
21 Comments
Hi when I updated to Symfony5.0 I have problem with the little tricky about Avoiding the Weird New Label because this widget is already rendered.
An exception has been thrown during the rendering of a template ("Field has already been rendered, save the result of previous render call to a variable and output that instead.").
If I delete the line {{ form_widget(genusForm.genusScientists) }} it works but in new action I see the "awful" label. Any idea to fix this?
Thanks
Hey Francisco C.
Does it happen on both action (create and edit)? If it only happens on edit action (because the form actually contains some genus scientists), then, the trick Ryan showed us doesn't work anymore on Symfony 5. What you can do is to check if there are any genus scientists before calling
{{ form_widget(genusForm.genusScientists) }}(after thefor). Or, disable the label of that form field directly on the FormType class https://symfony.com/doc/current/reference/forms/types/text.html#labelCheers!
Hi, I have two different collection of forms and in an effort to change the way the fields were displaying, horizontally instead of vertically, after many failed attempts, I created two macros were I added my desired layout and then imported them into my template. Works great! But, if my form doesn't pass validation after submit the page is reloaded and my two collection of forms revert to the vertical layout my macros were overriding. Is there a way to fix this? Perhaps, another type of macro? Thank you in advance!
One of my macros:
How I import it in my template:
How I display it:
Hey Brentmtc,
Here's the reference to the docs page about how to customize form rendering: https://symfony.com/doc/cur... . But what you want to do is related to customizing form theme I believe, see: https://symfony.com/doc/cur... . If you use Bootstrap 4 layout for your forms - there's another form theme called "bootstrap_4_horizontal_layout.html.twig", see https://symfony.com/doc/cur... - if you will try to use it on the specific page you need (see https://symfony.com/doc/cur... ) - it will automatically render all your form fields horizontally. This would be the easiest win for you I think.
If the horizontal layout is still not render perfectly, like you really want to customize how fields are rendered - take a look at overriding Symfony form theme. You can find how to override form theme on the same docs page: https://symfony.com/doc/cur... - this way you will be able to override some part of the form layout and make it render as you want. But it's kinda advanced level, I'd recommend you to try the built-in horizontal layout first to see if it solves the problem for you.
I hope this helps!
Cheers!
Hello, I have a form with a dropdown of accounts associated with the logged in user. The form also has two collections, products and materials. Currently, on my materials collection I have a query builder which has a user id where clause. It's working good. My question is how can I get the account number value chosen in the dropdown into this query so I can have two where clauses, user id and account number?
My repository method:
`public function findAllMaterialByUser()
`
My materials form type:
`public function buildForm(FormBuilderInterface $builder, array $options)
Hey Brent
It depends on where does the account number comes from. If you can get it at your controller's action level, then you can just pass it to the FormType as an "option". If that's not the case, then you may want to inject a service into your FormType so you can use it to get the account number at the right moment
Cheers!
The aforementioned dropdown of account number is not part of my form that submits. I populate an html dropdown from a query result when my template loads. A user can select an account number when they do an ajax call is made and data for some of the fields in the form gets populated via that ajax call. I don't think passing an account number to the FormType as an 'option' will work because to my knowledge that would be a static value and if a different account number was chosen from the dropdown that account number option would be 'out of date'.
My follow up question is if I were to try and inject a service into my FormType can an account number in the where clause in the query that populates my Materials Collection be updated via javascript or ajax call? Or is there someway I can update the dropdown options in the Material Collection prototype via javascript or ajax? I did create my collection almost exactly as this screencast and Symfony's page describes with addCollection removeCollection functions.
I apologize this is so wordy I am trying to explain how I have this working so far and what I am trying to do. Thank you in advance!
Interesting, in that case, whenever a user selects an account number, then, you should re-render the FormType and update your HTML using Javascript when the AJAX request finishes.
Hello, I was able to get this working. I'll list what I did in case it can help someone else.
1. Set the choices in my MaterialsFormType EntityType to null 2. Added a method in my controller to query for the data by user id and account number (what I was trying to do in my repository) 3. Added an ajax call to that same method in my addMaterialCollectionForm function (after the card creation) 4. Looped through the response to update the select options html in my Materials Form Collection.
Interesting though looking at injecting a service into a FormType. I've have never done that nor am I exactly sure the use case to do so but thank you for your time!
It's rare when you need to inject a service into a FormType but it's easy to do, FormType classes work just like any other service, you only have to add a constructor and add arguments to it
first, thank you for sharing this easy way to manage Collections in a form.
I come across an "issue" or may be I don't understand one mechanism: how does Doctrine know what items from your collection are new or updates when you submit?
I mean, I had a look at the generated html for the form and i don't see any ids for the form fields managing the scientists data.
It works perfectly with my case with One-To-Many relations but now I want to add a many-to-many relations between some POI and zones.
In my form, i would like to be able to link my POI to existing zones (then i will have to manage a selectbox with the existing ones, fine!) or/and link it to a new zone (using what is explained here so fine as well).
But how could i manage to do this if we don't provide any id(id of one or several existing zones) to the collection form ?
thank you in advance
Hey Olivier Mellinger!
Sorry for the slow reply - nice to chat with you!
That is an excellent question! It's a team effort. You're correct that there are no ids in the form fields managing the scientists. But, each "does" have an "index". What I mean is, if you have 3 scientists, and so 3 rows, the first is submitted with an index 0 as part of its name, the second as 1 and the third as 2. This is important! Here is the process that happens when you submit. And, by the way, you can see this logic here - https://github.com/symfony/symfony/blob/31ee43a8c67897a2f61782060965f5c8a81f58a2/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php#L93-L112 - that is a complex class, and there are other pieces of logic beyond these lines, but it can still give you an idea. Anyways, let's look at some examples:
A) You originally have 3 rows (so, indexes 0, 1 and 2 - called "$name" in that class I linked above as it's looping). You delete the 2nd row. This means you submit indexes 0 and 2 only. This causes the field with index "1" to be removed from the form.
B) You originally have 3 rows (indexes 0, 1 and 2) and you add a new one, with index 3. In submit, the new one is added to the form.
So this is the first part of the equation. The form uses these indexes to add or remove these "embedded fields" from the form.
But, how does that ultimately get communicated to adding/removing scientists to your entity? This relates to the by_reference => false option we set - https://symfonycasts.com/screencast/collections/by-reference-adder-remover#codeblock-92435a5c26
Basically, if the form detects that index "1" has been removed, then it grabs whatever GenusScientist is attached to that form, and calls
$genus->removeGenusScientist($genusScientist). That causes us to remove it from that property, which is ultimately why Doctrine will save the Genus without this relation. The same happens when a new row is added: the for system calls ->addGenusScientist() and passes it the new GenusScientist. It all works... but is pretty crazy :p.So... in theory, all of this "works the same way" with a ManyToMany relationship. The form system actually doesn't know the difference. Think about it: if we changed Genus to be a ManyToMany to GenusScientist, the Genus object would still have addGenusScientist(), removeGenusScientist() and getGenusScientists() methods that all do the exact same thing. The form would call those methods in the same way, and everything would save the same (Doctrine handles inserting and deleting rows in the "join" table automatically for ManyToMany as you add/remove GenusScientist to Genus).
The tricky thing will be that I don't think it would be easy with the form collection system to have a checkbox to select form existing zones AND an embedded form to add a new zone "on the fly". It's because either your "zones" field on your form needs to either be a EntityType that renders as checkboxes OR a CollectionType, which renders as embedded forms. Personally, I would probably use EntityType. And if you want a Zone to be created "on the fly", I would add a "Create zone" link which, for example, opens a modal that allows the user to create the Zone in a separate form. On AJAX submit and save of that form, you would add a new checkbox to the main form. Not the easiest thing to do, but it's a better user experience anyways.
Phew! Let me know if any of this helps ;).
Cheers!
There is an issue with the next index calculation after you delete an item. I noticed that the children object preserves the index, so for example if you delete the first item from {0: 'A', 1: 'B', 2: 'C'}, instead of getting {0: 'B', 1: 'C'} we actually get {1: 'B', 2: 'C'}. After the submit, this obviously messes up the index, as it will suggest 2 for the next item, but that is already taken so will be overwritten. This is how Collections behave in general, had issues with it earlier, converting the collection to an array then back to Collection in the back-end fixes the index and eliminates the gaps.
If that is not possible, my front-end solution is to use the following formula in the template: data-index="{{ (genusForm.genusScientists|length > 0) ? (max(genusForm.genusScientists.children|keys)+1) : 0 }}" This finds the largest index and adds one to it, seems to be working, but please let me know if there is a better workaround.
Hey BlackWiCKED
Excellent point. I'm glad you noticed it. We talk about that edge case here and proposed a solution: https://symfonycasts.com/sc...
Cheers!
Nice one, thank you for sharing. Using a listener is more elegant than my scrappy patch, I'll go with yours. ;)
Thank you so much for sharing.
Hi .. nice course !
But, how to add a first field automatically if there is not already one (for example, a new ad) ? (genusScientistForm if index == 0)
Hey Hervé BOYER
Do you mean something like automatically hitting the "Add another scientist" button if the Genus object doesn't have one already ? If so, you could do a check like this
I hope it helps you, if not, let me know!
Have a nice day :)
Hi Diego !
Thank you very much for your answer.
I'll trying when I'll better in JS ^^
Would not it be better if we paste <script src="{{ asset('js/GenusAdmonForm.js') }}"></script> into formLayout.html.twig instead of two files edit.html.twig and new.html.twig?
Hey Petr,
Yes, fair point! It makes sense if you extends formLayout.html.twig only with edit.html.twig and new.html.twig templates.
Cheers!
"Houston: no signs of life"
Start the conversation!