Collection Delete & allow_delete
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeRight now, this Genus
is related to four GenusScientists. Cool... but what if one of those users stopped studying the Genus
- how could we remove that one?
The Delete UI & JavaScript
Let's plan out the UI first: I want to be able click a little x icon next to each embedded form to make it disappear from the page. Then, when we submit, it should fully delete that GenusScientist
record from the database. Cool?
Inside the embedded form, add a new class to the column: js-genus-scientist-item
:
{{ form_start(genusForm) }} | |
// ... lines 2 - 24 | |
{% for genusScientistForm in genusForm.genusScientists %} | |
<div class="col-xs-4 js-genus-scientist-item"> | |
// ... lines 27 - 32 | |
</div> | |
{% endfor %} | |
// ... lines 35 - 37 | |
{{ form_end(genusForm) }} |
We'll use that in JavaScript in a second. Below that, add a little link with its own js-remove-scientist
class... and put the cute little "x" icon inside:
{{ form_start(genusForm) }} | |
// ... lines 2 - 24 | |
{% for genusScientistForm in genusForm.genusScientists %} | |
<div class="col-xs-4 js-genus-scientist-item"> | |
<a href="#" class="js-remove-scientist pull-right"> | |
<span class="fa fa-close"></span> | |
</a> | |
// ... lines 30 - 32 | |
</div> | |
{% endfor %} | |
// ... lines 35 - 37 | |
{{ form_end(genusForm) }} |
Brilliant!
Time to hook up some JavaScript! Since this template is included by edit.html.twig
and new.html.twig
, I can't override the javascripts
block from here. Instead, open edit.html.twig
and override the block javascripts
there:
// ... lines 1 - 2 | |
{% block javascripts %} | |
{{ parent() }} | |
// ... lines 5 - 18 | |
{% endblock %} | |
// ... lines 20 - 32 |
We'll worry about adding JS to the new template later.
Start with the always-exciting document.ready
function:
// ... lines 1 - 2 | |
{% block javascripts %} | |
{{ parent() }} | |
<script> | |
jQuery(document).ready(function() { | |
// ... lines 8 - 16 | |
}); | |
</script> | |
{% endblock %} | |
// ... lines 20 - 32 |
Oh, but back in _form.html.twig
, add one more class to the row that's around the entire section called js-genus-scientist-wrapper
:
{{ form_start(genusForm) }} | |
// ... lines 2 - 23 | |
<div class="row js-genus-scientist-wrapper"> | |
// ... lines 25 - 34 | |
</div> | |
// ... lines 36 - 37 | |
{{ form_end(genusForm) }} |
Ok, back to the JavaScript! Add var $wrapper =
then use jQuery
to select that wrapper element. Register a listener on click
for any .js-remove-scientist
element - that's the delete link. Start that function with my favorite e.preventDefault()
:
// ... lines 1 - 2 | |
{% block javascripts %} | |
// ... lines 4 - 5 | |
<script> | |
jQuery(document).ready(function() { | |
var $wrapper = $('.js-genus-scientist-wrapper'); | |
$wrapper.on('click', '.js-remove-scientist', function(e) { | |
e.preventDefault(); | |
// ... lines 12 - 15 | |
}); | |
}); | |
</script> | |
{% endblock %} | |
// ... lines 20 - 32 |
Then... what next? Well, forget about Symfony and the database: just find the .js-genus-scientist-item
element that's around this link and... remove it!
// ... lines 1 - 2 | |
{% block javascripts %} | |
// ... lines 4 - 5 | |
<script> | |
jQuery(document).ready(function() { | |
var $wrapper = $('.js-genus-scientist-wrapper'); | |
$wrapper.on('click', '.js-remove-scientist', function(e) { | |
e.preventDefault(); | |
$(this).closest('.js-genus-scientist-item') | |
.fadeOut() | |
.remove(); | |
}); | |
}); | |
</script> | |
{% endblock %} | |
// ... lines 20 - 32 |
Simple! Refresh the page, click that "x", and be amazed.
Missing Fields: The allow_delete Option
But this is superficial: it didn't delete anything from the database nor can we submit the form and expect something to magically delete this GenusScientist
, just because we removed it from the page. Or can we?
Submit! Well, I guess not. Huge error from the database!
UPDATE genus_scientist SET years_studied and user_id to null.
Hmm. So our form is not expecting this embedded form to simply disappear. Instead, because the fields are missing from the submitted data, it thinks that we want to set that Genus Scientist's yearsStudied
and user
fields to null! No! I want to delete that entire object from the database!
How can we do that? First, in GenusFormType
, we need to tell the genusScientists
field that it's ok if one of the embedded form's fields is missing from the submit. Set a new allow_delete
option to true
:
// ... lines 1 - 18 | |
class GenusFormType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
// ... lines 24 - 46 | |
->add('genusScientists', CollectionType::class, [ | |
// ... line 48 | |
'allow_delete' => true, | |
]) | |
; | |
} | |
// ... lines 53 - 59 | |
} |
This tells the CollectionType
that it's ok if one of the GenusScientist
forms is missing when we submit. And, if a GenusScientist
form is missing, it should remove that GenusScientist
from the genusScientists
array property. In other words, when we remove a GenusScientist
form and submit, the final array will have three GenusScientist
objects in it, instead of four.
Ready? Submit!
Hmm, no error... but it still doesn't work. Why not? Hint: we already know the answer... and it relates to Doctrine's inverse relationships. Let's fix it.