Bonjour
J'ai une entité annonce, un form annonce aussi donc. J'ai une autre entité image et un nouveau form image
Lorsque dans ma vue, j'affiche le formulaire annonce et son sous formulaires images, j'arrive grace a jquery a rajouter et supprimer des tags (protoype) pour rajouter ou supprimer des images a mon annonce
L'ajout d'image fonctionne tres bien, en revanche la suppression d'image ne provoque pas d'erreur, me renvoie bien sur ma page de l'annonce modifée, le flash affiche bien le message de succes mais les images supprimmées sont toujours présentes y compris en base de données.
Le dump posé dans mon controlleur me renvoie bien un array avec le nb d'images que je souhaite conserver (en cas de suppression d'images) mais au final rien ne se passe mes images supprimées par javascript réapparaissent.
A l'inverse l'ajout d'image marche tres bien
En l'abscence d'erreur ou de plantage je ne parviens pas à définir l'origine du probleme.Mon allow_ad et mon allow_delete sont bien placé dans mon form annonce.
Je pose ici mes fichiers sous Symfony 5 afin de comprendre la raison de ce bug
D'avance merci
Methode Edit de mon controlleur Annonce
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 /** * This method edit the choosen ad form to be modifyed * * @Route("/ad/edit/{slug}", name="edit_ad") * * @return Ad */ public function editAction(Request $request, Ad $ad, EntityManagerInterface $manager) { $form = $this->createForm(AdType::class, $ad); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { foreach ($ad->getImages() as $image) { $image->setAd($ad); $manager->persist($image); } $manager->persist($ad); $manager->flush(); $this->addFlash('success', "Les modifications de l'annonce ont bien été prises en compte"); return $this->redirectToRoute('get_ad', ["slug"=>$ad->getSlug()]); } return $this->render( "ad/forms_ad.html.twig", [ 'titre'=>'Modification de l\'annonce: '.$ad->getTitle(), 'button_label'=> "Modifier cette annonce", 'ad'=>$ad, 'form'=>$form->createView() ] ); }
Methode Get pour afficher une seule annonce
Mon entité annonce
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 /** * This function return the selected ad * * @return Response * * @Route("/ad/{slug}", name="get_ad") */ public function getAction($slug, AdRepository $repo) { $ad = $repo->findOneBySlug($slug); //On peut aussi utiliser le paramConverter return $this->render( 'ad/get_ad.html.twig', [ 'titre' => $ad->getTitle(), 'ad' => $ad ] ); }
Mon entité Image
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260 <?php namespace App\Entity; use App\Entity\Image; use Cocur\Slugify\Slugify; use App\Repository\AdRepository; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * @ORM\Entity(repositoryClass=AdRepository::class) * @ORM\HasLifecycleCallbacks() * @UniqueEntity( * fields = {"title"}, * message ="Une autre annonce à déjà ce titre merci de la modifier" * ) */ class Ad { /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * title * * @ORM\Column(type="string", length=255) * *@Assert\Length( * min=10, * max=50, * minMessage="Le titre doit au minimum contenir {{ limit }} caractères", * maxMessage="Le titre ne peut contenir plus de {{ limit }} caractères" *) * @var string */ private $title; /** * @ORM\Column(type="string", length=255) */ private $slug; /** * @ORM\Column(type="float") * * @Assert\NotNull * @Assert\Regex( * pattern = "/(^[0-9]+)\W?([0-9]{0,2}$)/i", * match=true, * message ="Le prix ne peut contenir que des nombres") * */ private $price; /** * @ORM\Column(type="text") */ private $introduction; /** * @ORM\Column(type="text") */ private $content; /** * @ORM\Column(type="string", length=255) * * @Assert\Url( message="l'url {{ value }}saisi n'est pas correcte") */ private $coverImage; /** * @ORM\Column(type="integer") */ private $rooms; /** * @ORM\OneToMany(targetEntity=Image::class, mappedBy="ad", cascade={"persist"}) * * @Assert\Valid */ private $images; /** * @ORM\ManyToOne(targetEntity=User::class, inversedBy="ads") * @ORM\JoinColumn(nullable=false) */ private $author; public function __construct() { $this->images = new ArrayCollection(); } /** * This function initialize a new slug in case is empty * * @return void * * @ORM\PrePersist * @ORM\PreUpdate */ public function initializeSlug() { if (empty($this->slug)) { $slugTitle = new Slugify(); $this->setSlug($slugTitle->slugify($this->title)); } } public function getId(): ?int { return $this->id; } public function getTitle(): ?string { return $this->title; } public function setTitle(string $title): self { $this->title = $title; return $this; } public function getSlug(): ?string { return $this->slug; } public function setSlug(string $slug): self { $this->slug = $slug; return $this; } public function getPrice(): ?float { return $this->price; } public function setPrice(float $price): self { $this->price = $price; return $this; } public function getIntroduction(): ?string { return $this->introduction; } public function setIntroduction(string $introduction): self { $this->introduction = $introduction; return $this; } public function getContent(): ?string { return $this->content; } public function setContent(string $content): self { $this->content = $content; return $this; } public function getCoverImage(): ?string { return $this->coverImage; } public function setCoverImage(string $coverImage): self { $this->coverImage = $coverImage; return $this; } public function getRooms(): ?int { return $this->rooms; } public function setRooms(int $rooms): self { $this->rooms = $rooms; return $this; } /** * @return Collection|Image[] */ public function getImages(): Collection { return $this->images; } public function addImage(Image $image): self { if (!$this->images->contains($image)) { $this->images[] = $image; $image->setAd($this); } return $this; } public function removeImage(Image $image): self { if ($this->images->contains($image)) { $this->images->removeElement($image); //set the owning side to null (unless already changed) if ($image->getAd() === $this) { $image->setAd(null); } } return $this; } /** * Set the value of images * * @return self */ public function setImages(?ArrayCollection $images) { $this->images = $images; } public function getAuthor(): ?User { return $this->author; } public function setAuthor(?User $author): self { $this->author = $author; return $this; } }
Mon Form annonce
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 <?php namespace App\Entity; use App\Repository\ImageRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity(repositoryClass=ImageRepository::class) */ class Image { /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) * * @Assert\Url(message="Ceci n'est pas une url valide veuillez la modifier merci!") */ private $url; /** * @ORM\Column(type="string", length=255) * * @Assert\Length(min=10, minMessage="Le titre doit faire au moins {{ limit }} caractères") */ private $caption; /** * @ORM\ManyToOne(targetEntity=Ad::class, inversedBy="images") * @ORM\JoinColumn(nullable=false) */ private $ad; public function getId(): ?int { return $this->id; } public function getUrl(): ?string { return $this->url; } public function setUrl(string $url): self { $this->url = $url; return $this; } public function getCaption(): ?string { return $this->caption; } public function setCaption(string $caption): self { $this->caption = $caption; return $this; } public function getAd(): ?Ad { return $this->ad; } public function setAd(?Ad $ad): self { $this->ad = $ad; return $this; } }
Mon form Image
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 <?php namespace App\Form; use App\Entity\Ad; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\UrlType; class AdType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add( 'title', TextType::class, $this->getConfiguration("Titre de l'annonce", 'Saisissez votre titre') ) ->add( 'introduction', TextType::class, $this->getConfiguration("Brève présentation", 'Petite phrase d\'introduction') ) ->add( 'coverImage', UrlType::class, $this->getConfiguration("Image de couverture", 'Url de l\'image de couverture') ) ->add( 'content', TextType::class, $this->getConfiguration("Contenu de l'annonce", 'Saisissez un contenu complet pour l\'annonce') ) ->add( 'price', MoneyType::class, $this->getConfiguration("Prix de la location / nuit", 'Saisissez le prix par nuit') ) ->add( 'rooms', IntegerType::class, $this->getConfiguration("Nombre de chambres à proposer", 'De combien de chambres disposez-vous ?') ) ->add( 'images', CollectionType::class, [ 'label'=>"Images complémentaires", 'label_attr'=>["class"=>"font-weight-bold border-top my-4 border-grey"], 'entry_type'=> ImageType::class, 'entry_options'=>['label'=>false], 'allow_add'=> true, 'allow_delete' => true ] ) ; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => Ad::class, ]); } /** * This function returns the label and the placeholder for each field * * @param string $label * @param string $placeholder * @return array */ private function getConfiguration(string $label, string $placeholder, $required = true):array { return [ 'required'=>$required, 'label'=>$label, 'attr'=>[ 'placeholder'=>$placeholder ] ]; } }
Mon formulaire dans la vue que j'ai repris pour l'ajout d'une annonce(la même alors que pr-rempli en edit)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 <?php namespace App\Form; use App\Entity\Image; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Form\Extension\Core\Type\TextType; class ImageType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add( 'url', UrlType::class, [ 'attr'=>["placeholder"=>"Url de l'image"] ] ) ->add( 'caption', TextType::class, [ 'attr'=>["placeholder"=>"Nom de l'image"] ] ) ; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => Image::class, ]); } }
Code twig : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 {% extends 'base.html.twig' %} {% block title %}{{titre}}{% endblock %} {% form_theme form _self %} {% block body %} <h1 class="text-center mb-5 pb-5 border-bottom border-grey mx-auto">{{titre}}</h1> {{ form_start(form, {'attr':{'class':'form-group'}}) }} {{ form_widget(form) }} <div class="d-flex justify-content-end"> <input type="submit" class="btn btn-primary form-group" {% if button_label is defined %} value="{{ button_label }}" {% else %} value="Créer cette annonce" {% endif %} > </div> {{ form_end(form) }} {% endblock %} {% block _ad_images_widget %} <p>Utlisez ces champs pour rajouter des images</p> {{form_widget(form)}} <div class="form-group"> <button type="button" name="" id="btn-add" class="btn btn-primary"> Ajouter une image </button> </div> {% endblock %} {# suppression des labels des champs dans twig #} {# {cette suppression est commentée car réalisée dans les options du form} #} {# {% block _ad_images_entry_label %} {{form_label(form,null,{'label_attr':{'class':'d-none'}})}} {% endblock %} #} {% block _ad_images_entry_widget %} <div class="row"> <div class="col d-inline"> {{ form_errors(form.caption) }} {{ form_widget(form.caption) }} </div> <div class="col d-inline"> {{ form_errors(form.url) }} {{ form_widget(form.url) }} </div> <div class="col d-inline"> <button type='button' class="btn btn-danger btn-delete"> Supprimer </button> </div> </div> {% endblock %} {% block javascripts %} <script> $(document).ready(function(){ // Add an image when newImage addbutton is typed $(document).on('click','#btn-add',function(){ let tmpl = $('#ad_images').data('prototype').replace(/__name__/g,count); $('#ad_images').append(tmpl); }); // Delete the image choosed by the user typing delete button $(document).on('click','.btn-delete',function(e){ let result = confirm('Confirmez-vous la suppression de cette image ?'); if (result == true){ if ($(this).closest('fieldset.form-group').attr('id')){ $(this).closest('fieldset.form-group').remove(); } else { $('fieldset.form-group:last').remove(); } } }); function count(){ let count = $('#ad_images .row').length; for(let i=0;i<=count;i++){ if(!$('fieldset.form-group:nth-child('+i+')').attr('id')){ $('fieldset.form-group:nth-child('+i+')').attr('id', "block_"+i); } } } count(); }); </script> {% endblock %}
Ma vue d'affichage d'un seul formulaire
Code twig : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 {% extends 'base.html.twig' %} {% block title %} / Liste locations{% endblock %} {% block body %} <div> <div class="row get-ad-div" style="background-image:url('{{ad.coverImage}}');"><div class="overlay"></div> <h1 class="mt-5 mb-3 text-center mx-auto">{{titre}}</h1> <div class="col-8 my-2 mx-auto container"> <div class="card bg-light border border-dark"> <div class="card-header text-center rounded h4"> {{ad.rooms}} {% if ad.rooms > 1 %} Chambres {% else %} Chambre {% endif %} à <strong>{{ad.price}} € / nuit</strong> <div class="my-2"><small>Pas encore noté</small></div> </div> <img src="{{ad .coverImage}}" class="img-fluid img-thumbail rounded" style="width:100%; display:block" alt="image de l'appartement"> <div class="row"> <div class="col-8 card-body p-4 mx-auto"> <h3 class="card-title">{{ad.title}}</h3> <div class="card-subtitle text-muted my-2 h4">{{ad.introduction}}</div> <div class="card-text"> {{ad.content| raw}} </div> {% if ad.images|length >0 %} <div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel" data-interval="false"> <ol class="carousel-indicators"> {% for image in ad.images %} <li data-target="#carouselExampleIndicators" data-slide-to="{{loop.index0}}" {% if loop.first %} class="active" {% endif %} ></li> {% endfor %} </ol> <div class="carousel-inner mx-auto rounded bg-dark mb-4 border border-dark" style="width:60%"> {% for image in ad.images %} <div class="carousel-item {% if loop.first %}active{% endif %}"> <img src="{{image.url}}" class="d-block w-60 img" alt="image {{ad.title}}"> <div class="carousel-caption d-none d-md-block"> <h6>{{ad.title}}</h6> <small>{{image.caption}}</small> </div> </div> {% endfor %} </div> <a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="sr-only">Previous</span> </a> <a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="sr-only">Next</span> </a> </div> {% endif %} <a href="{{path('list_ad')}}" class="btn btn-primary my-2">Retour aux offres</a> <a href="#" class="btn btn-success my-2">Réserver</a> </div> <div class="col-md-10 mx-auto col-lg-4"> <div class="row"> <div class="col-lg-2 col-md-12 col-sm-12 mt-5 ml-sm-5 ml-md-3 mr-md-1"> <img src="http://place-hold.it/64x64" class="rounded img-thumbail d-block" alt="image"> </div> <div class="col-sm-10 col-md-10 col-lg-9 ml-md-1 pl-4 pr-4 pb-4 mt-sm-2 mt-md-2 mt-lg-5 mx-sm-auto"> <h5>Joseph Dupont</h5> <p class="badge badge-primary">3 annonces</p> </div> <div class="row col-11"> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Exercitationem maiores quam eos. Numquam voluptates, provident labore aut itaque modi eligendi! Repellat facilis quae numquam similique iure odio eius officia nihil.</p> <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Hic aut, asperiores officiis eaque, excepturi nobis in amet quisquam assumenda ut porro! Aperiam culpa fuga amet alias quasi! Dicta, eum doloribus?</p> </div> </div> </div> </div> </div> </div> </div> </div> {% endblock %}
Partager