I have a private message bundle/entity that allows my users to send messages between them.
Its fields are as follows:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #Assert\NotBlank(message="private_message.title.blank")
* #ORM\Column(name="title", type="string", length=50)
*/
protected $title;
/**
* #Assert\NotBlank(message="private_message.receiver.blank")
* #AcmeAssert\IsHimself(message="private_message.receiver.himself", groups={"new"})
* #ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
* #ORM\JoinColumn(referencedColumnName="id")
*/
protected $receiver;
/**
* #ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
* #ORM\JoinColumn(referencedColumnName="id")
*/
protected $sender;
/**
* #var string
* #Assert\NotBlank(message="private_message.content.blank")
* #ORM\Column(name="content", type="string")
*/
protected $content;
/**
* #var \DateTime
*
* #ORM\Column(name="sentAt", type="datetime")
*/
protected $sentAt;
/**
* #var boolean
*
* #ORM\Column(name="isSpam", type="boolean")
*/
protected $isSpam = false;
/**
* #var \DateTime
*
* #ORM\Column(name="seenAt", type="datetime",nullable=true)
*/
protected $seenAt = null;
/**
* #ORM\ManyToOne(targetEntity="PrivateMessageBundle\Entity\Message",inversedBy="replies")
* #ORM\JoinColumn(referencedColumnName="id",nullable=true)
*/
protected $replyof;
/**
* #ORM\OneToMany(targetEntity="PrivateMessageBundle\Entity\Message", mappedBy="replyof")
**/
private $replies;
public function __construct() {
$this->replies = new ArrayCollection();
}
Notice the replyof field, it references to another message, and the replies one references to an array of messages. If replyof is null, then the message is not a reply of any message.
I have a twig template with a macro that displays a user's message and all the replies of that message. What I'd like to do is have a reply textfield under each of these, exactly like Gmail has, that allows me to add a reply to each message.
But when I add it to the template, only one is rendered because it has one single Id. How can I add a reply form after each reply? What their FormType should look like?
Here is also my twig template:
{% macro displayReply(reply,replyform) %}
{% import _self as macros %}
{# <li> id: {{ reply.id }} </li>
<li> sent by: {{ reply.sender }} </li>
<li> title: {{ reply.title }} </li>
<li> content: {{ reply.content }} </li>
<li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
reply
<hr> #}
<div class="panel panel-default">
<div class="panel-body">
<div class="message-info">
<input type="hidden" name="messageid" id="messageId" value="{{ reply.id }}">
<div class="message-title clearfix">
<h4 class="pull-left">{{ reply.title }}</h4>
</div>
<hr class="lite-line">
<div class="message-sender clearfix">
<div class="pull-left sender">
{{ reply.sender }}
</div>
<div class="pull-right">
to <b>{{ (reply.receiver==app.user)?'me':reply.receiver }}</b> on <span
class="message-timestamp">{{ reply.sentAt|date('F d, Y H:i:s') }}</span>
<a class="btn btn-start-order" role="button"
href="{{ path('private_message_new',{'msg':reply.id}) }}">Reply</a>
</div>
</div>
<hr class="lite-line">
<div class="message-box clearfix">
<span>{{ reply.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
</div>
{{ form_start(replyform) }}
<input type="submit">
{{ form_end(replyform) }}
</div>
</div>
</div>
{% for reply in reply.replies %}
{% if loop.first %}<div>{% endif %}
{{ macros.displayReply(reply) }}
{% if loop.last %}</div>{% endif %}
{% endfor %}
{% endmacro %}
{% import _self as macros %}
{# use the macro #}
<div class="message-back">
<a class="btn btn-start-order-dark btn-block" role="button"
href="{{ path('private_message',{'page':'inbox'}) }}">
<span class="fa fa-undo"></span> Go back
</a>
</div>
<div class="messages">
<div class="panel panel-default">
<div class="panel-body">
<div class="message-info">
<input type="hidden" name="messageid" id="messageId" value="{{ message.id }}">
<div class="message-title clearfix">
<h4 class="pull-left">{{ message.title }}</h4>
</div>
<hr class="lite-line">
<div class="message-sender clearfix">
<div class="pull-left sender">
{{ message.sender }}
</div>
<div class="pull-right">
to <b>{{ (message.receiver==app.user)?'me':message.receiver }}</b> on <span
class="message-timestamp">{{ message.sentAt|date('F d, Y H:i:s') }}</span> <a
class="btn btn-start-order" role="button"
href="{{ path('private_message_new',{'msg':message.id}) }}">Reply</a>
</div>
</div>
<hr class="lite-line">
<div class="message-box clearfix">
<span>{{ message.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
</div>
{{ form_start(replyform) }}
<input type="submit">
{{ form_end(replyform) }}
</div>
</div>
</div>
</div>
{% for reply in message.replies %}
{% if loop.first %}<div class="replies">{% endif %}
{{ macros.displayReply(reply ,replyform) }}
{% if loop.last %}</div>{% endif %}
{% endfor %}
Notice that I first display the message, then apply the macro to it that displays all its replies as a tree. It will display the replies's replies, too, in a recursive manner, all the way until the leaf nodes. I add a 'replyform' after each, but I'm not sure how the FormType should be.
My reply form type is like this, but I'm pretty sure it is wrong.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content', 'textarea')
;
}
As for the other fields of the reply, I take care of them in the controller. I think I should be doing this after receiving the message from the form though. Something like this, and get the title, content and replyof from the formdata.
$messages = $this->getDoctrine()->getRepository('PrivateMessageBundle:Message');
$isforme = $messages->findOneBy(array('receiver' => $this->getUser(), 'id' => $msg));
$message = new Message();
$message->setSender($this->getUser());
$message->setSentAt(new \Datetime('now'));
$message->setReplyof($isforme);
$message->setReceiver($isforme->getSender());
$form = $this->createForm(new MessageReplyType($em), $message);
EDIT
Ok, so I made something that works, by adding a hidden field and hardcoding multiple forms instead of using FormTypes, but I still think that this can be done in a better, more reusable way.
<form name="privatemessagebundle_message" method="post" action="" id="{{ reply.id }}">
<div><label for="privatemessagebundle_message_title" class="required">Title</label><input
type="text" id="privatemessagebundle_message_title"
name="privatemessagebundle_message[title]" required="required" maxlength="50"></div>
<div><label for="privatemessagebundle_message_content" class="required">Content</label><textarea
id="privatemessagebundle_message_content"
name="privatemessagebundle_message[content]" required="required"></textarea></div>
<input type="hidden" id="privatemessagebundle_message_replyof"
name="privatemessagebundle_message[replyof]" value="{{ reply.id }}">
<input type="submit">
<input type="hidden" id="privatemessagebundle_message__token"
name="privatemessagebundle_message[_token]"
value="{{ csrf_token('privatemessagebundle_message') }}">
</form>
Anyone got any better ideas?
I did it! I used the answer from this question.
Since I'm using foreach loops and they might be a bit low on performance, anyone with a better idea is welcomed. There is still the bounty to receive.
I'm creating a form for each of my forms through createNamedBuilder. They will have different names, thus different id's and Symfony will render them all. Then, I can render them where I want and handle their request just fine through their unique id taken from the database.
$genforms = $this->genReplyForms($isforme); // run the function for my message
$forms_views = $genforms['views']; // pass to the view
$forms= $genforms['forms']; // handle request...
This is the function that generated the form. It recursively generates them for each reply of my message.
public function genReplyForms(Message $message)
{
$id = $message->getId();
$msgreply[$id] = new Message();
$forms[$id] = $this->container
->get('form.factory')
->createNamedBuilder('form_'.$id, new MessageReplyType(), $msgreply[$id])
->getForm();
$forms_views[$id] = $forms[$id]->createView();
$result = array(array(), array());
$result['forms'][$id] = $forms[$id];
$result['views'][$id] = $forms_views[$id];
if (sizeof($message->getReplies())) {
foreach ($message->getReplies() as $reply) {
$child = $this->genReplyForms($reply);
$result['forms'] = $result['forms'] + $child['forms'];
$result['views'] = $result['views'] + $child['views'];
}
}
return $result;
}
MessageReplyType needs just user input. Everything else is handled in the controller
$builder
->add('title')
->add('content', 'textarea')
;
Also, my simplified twig. I've simplified the macro call, too. Was doing an unnecessary foreach loop for the first message instead of simply passing it to the macro.
{% macro displayReply(reply, forms) %}
{% import _self as macros %}
{# <li> id: {{ reply.id }} </li>
<li> sent by: {{ reply.sender }} </li>
<li> title: {{ reply.title }} </li>
<li> content: {{ reply.content }} </li>
<li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
reply
<hr> #}
<div class="panel panel-default">
<div class="panel-body">
<div class="message-info">
<input type="hidden" name="messageid" id="messageId" value="{{ reply.id }}">
<div class="message-title clearfix">
<h4 class="pull-left">{{ reply.title }}</h4>
</div>
<hr class="lite-line">
<div class="message-sender clearfix">
<div class="pull-left sender">
{{ reply.sender }}
</div>
<div class="pull-right">
to <b>{{ (reply.receiver==app.user)?'me':reply.receiver }}</b> on <span
class="message-timestamp">{{ reply.sentAt|date('F d, Y H:i:s') }}</span>
{# <a class="btn btn-start-order" role="button"
href="{{ path('private_message_new',{'msg':reply.id}) }}">Reply</a> #}
</div>
</div>
<hr class="lite-line">
<div class="message-box clearfix">
<span>{{ reply.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
</div>
{{ form_start(forms[reply.id]) }}
<input type="submit">
{{ form_end(forms[reply.id]) }}
{# NU STERGE! #}
{#
<form name="privatemessagebundle_message" method="post" action="" id="{{ reply.id }}">
<div><label for="privatemessagebundle_message_title" class="required">Title</label><input
type="text" id="privatemessagebundle_message_title"
name="privatemessagebundle_message[title]" required="required" maxlength="50"></div>
<div><label for="privatemessagebundle_message_content" class="required">Content</label><textarea
id="privatemessagebundle_message_content"
name="privatemessagebundle_message[content]" required="required"></textarea></div>
<input type="hidden" id="privatemessagebundle_message_replyof"
name="privatemessagebundle_message[replyof]" value="{{ reply.id }}">
<input type="submit">
<input type="hidden" id="privatemessagebundle_message__token"
name="privatemessagebundle_message[_token]"
value="{{ csrf_token('privatemessagebundle_message') }}"></form>#}
{# NU STERGE! #}
</div>
</div>
</div>
{% for reply in reply.replies %}
{% if loop.first %}<div>{% endif %}
{{ macros.displayReply(reply,forms) }}
{% if loop.last %}</div>{% endif %}
{% endfor %}
{% endmacro %}
{% import _self as macros %}
{# use the macro #}
<div class="message-back">
<a class="btn btn-start-order-dark btn-block" role="button"
href="{{ path('private_message',{'page':'inbox'}) }}">
<span class="fa fa-undo"></span> Go back
</a>
</div>
<div class="replies">
{{ macros.displayReply(message, forms) }}
</div>
Again, I'm still looking for better or more efficient alternatives, so please do post them.
Related
I work with Symfony and Twig. I currently have a twig page containing a list of products, I display them with a foreach loop and I put pagination to limit the display of products.
I'm trying to put a form in this page with a checkbox as input and my problem is the following:
the checkboxes appear on all the products on the first page but when I go to the second page, I don't see the checkboxes, I need to reload the page to see them.
there is some code :
view of my loop :
<form>
<div class="row" >
{% for annonce in annonces %}
<div class="col-12 col-md-6 col-lg-4">
<p class="text text--blue text--bold m-0 text--medium mt-2">
{{ annonce._source.titre }}
</p>
<p class="m-0">{{ 'Réf' }}: {{ annonce._source.reference }}</p>
<div class="d-flex mt-2 text--bold">
<div class="d-flex me-2">
{{ annonce._source.ville }}
</div>
</div>
<div>
<input type="checkbox" name="checkbox[]" id="checkbox_pdf" value="{{annonce._id}}" multiple/>
</div>
</div>
{% endfor %}
</div>
<input type="submit" id="pdf_submit" value="Create PDF" name="submit_pdf" class="btn btn-primary">
</form>
view of the pagination :
<div class="col d-flex justify-content-between align-items-center">
<div class="d-flex">
{% if page > 0 %}
<a href="#" data-action="pagination" data-uri="{{ path('ajax_annonce_pagination',{'page':0, 'type':'frontoffice'}) }}" data-target="pagination-target">
«
</a>
<a href="#" data-action="pagination" data-uri="{{ path('ajax_annonce_pagination',{'page':page-1, 'type':'frontoffice'}) }}" data-target="pagination-target">
{{ 'Précédent' }}
</a>
{% else %}
<a href="#" disabled="disabled" >
{{ 'Précédent' }}
</a>
{% endif %}
</div>
<div>
<ul class="list-unstyled pagination m-0">
{% for i in (page+1)..(page+4) %}
{% if i <= numberOfMaxPage %}
{% if i == (page+1) %}
<li>
<a href="#" data-action="pagination" data-uri="{{ path('ajax_annonce_pagination',{'page':(i-1), 'type':'frontoffice'}) }}" data-target="pagination-target">
{{ i }}
</a>
</li>
{% else %}
<li>
<a href="#" data-action="pagination" data-uri="{{ path('ajax_annonce_pagination',{'page':(i-1), 'type':'frontoffice'}) }}" data-target="pagination-target">
{{ i }}
</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
</div>
<div class="d-flex">
{% if page < (numberOfMaxPage-1) %}
<a href="#" data-action="pagination" data-uri="{{ path('ajax_annonce_pagination',{'page':page+1, 'type':'frontoffice'}) }}" data-target="pagination-target">
{{ 'Suivant' }}
</a>
<a href="#" data-action="pagination" data-uri="{{ path('ajax_annonce_pagination',{'page':numberOfMaxPage-1, 'type':'frontoffice'}) }}" data-target="pagination-target">
»
</a>
{% endif %}
</div>
</div>
JS of the pagination :
$( document ).ready(function() {
$(document).on('click', 'a.pagination',function(e) {
e.preventDefault();
$.ajax({
url: $(this).data('uri'),
}).done(function(html) {
$('#pagination-target').html(html);
$('html,body').animate({scrollTop: $('#pagination-target').offset().top - 80}, 200);
var $scrollable = document.getElementById('pagination-target');
$scrollable.scrollIntoView();
});
});
});
In my controller I render my view like this :
return $this->render('front/annonce/list.html.twig', array(
'annonces' => $results['hits']['hits'],
'total' => $results['hits']['total']['value'],
'website' => $website,
'page' => $request->query->getInt('page')
));
What is this problem related to? is this due to the fact that it is not possible to add inputs in a looped element?
Is it related to pagination?
thanks you in advance
The following error occurred in Symfony4.
There are many posts with the same error, but I couldn't find the one in this case.
When I deleted the //Error part in the Twig file, the error disappeared, so I know it's the cause, but I don't know how to fix it.
What are your good ideas?
However, deleteAction is not used in //Error part and is incomprehensible.
The CSRF token is invalid. Please try to resubmit the form.
Article.php
protected function deleteAction(Request $request, $ids)
{
$token = $request->request->get('_csrf_token');
$csrf_token = new CsrfToken('authenticate', $token);
if (!$this->get("security.csrf.token_manager")->isTokenValid($csrf_token)) {
//Error code
throw new HttpException("400", "The CSRF token is invalid. Please try to resubmit the form.");
}
}
index.html.twig
{{ form_start(form) }}
<div class="formGroup trendTags"
data-prototype="{{ macros.trendTagForm(form.trendTags.vars.prototype)|e }}"
data-index="{{ ec_tag_contents|length }}">
<label>Tag</label>
<button type="button" class="add btn">Add</button>
<ul class="trend-tag-list2" {% if not ec_tag_contents|default %} style="display:none"{% endif %} id="trendTagsWrap">
{% for tag in ec_tag_contents|default %}
<li>
<div class="tagForm">
<div class="input-trendtag-display-name"> {{ tag.name }}</div>
<div class="input-trendtag-display-term">({{ tag.str_tag_display }} {{ tag.str_term }})</div>
<br>
{% for category in tag.categories|split(',') %}
<div class="tag-form__category-sticker" name="{{ category }}">{{ category }}</div>
{% endfor %}
<button class="removeTrendTag"><i class="icon-remove"></i></button>
</div>
<div id="brandevent_trendTags_{{ loop.index0 }}">
<input type="hidden" id="brandevent_trendTags_{{ loop.index0 }}_trendTagId" name="brandevent[trendTags][{{ loop.index0 }}][trendTagId]" required="required" value="{{ tag.tag_id }}">
</div>
</li>
{% endfor %}
{% do form.trendTags.setRendered(true) %}
</ul>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
{% set url = path("app_hq_article_index", {"articleType": "article", "num": 5, "modal": true}) %}
<div id="relatedArticleDialog" title="Article Selection" data-url="{{ url }}">
</div>
//Error part
{% set q = app.request.query.get("q")|default({}) %}
{% set trendTagUrl = path("app_hq_article_trendtag", {"q[sex]": q.sex|default(0), "q[brand_id]": q.brand_id|default(), "q[del_flg]": 0}) %}
<div id="trendTagDialog" title="Tag Selection" data-url="{{ trendTagUrl }}">
</div>
trendTag.html.twig
<div class="search">
{% include '#AppBundle/Shop/Article/trendTagForm.html.twig' %}
</div>
trendTagForm.html.twig
<form id="frm-trend-tag-search" name="frm-trend-tag-search" method="get" action="{{path('app_hq_article_trendtag')}}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
...
</form>
Add the {{form_widget(form._token)}} hidden field to the main form
or
Disable the CSRF token in form config:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
I don't know how to do it and I've been doing it for a while and I can't find a solution, as it says in the title, I don't know why at the time of filtering, the KNP_paginator, the links of the paginator, at the time of filtering are restarted.
I can't find the solution and here is my code:
This is the controller of the entity where we render the index together with the filters:
/**
* #Route("/", name="coche_index", methods={"GET","POST"})
*/
public function index(CocheRepository $cocheRepository, Request $request): Response
{
//Está filtrando
$estaFiltrando = false;
$valueModelo = $request->request->get('term_modelo');
$valueMarca = $request->request->get('term');
$valueAno = $request->request->get('term_ano');
$valueActivo = $request->request->get('term_activo');
$valorActivo = 0;
if ($valueActivo == true || $valueActivo == 1) {
$valorActivo = 1;
} else {
$valorActivo = 0;
}
if ($valueModelo == null && $valueMarca == null && $valueAno == null && $valueActivo == null) {
$estaFiltrando = false;
} else {
$estaFiltrando = true;
}
$marcas = $cocheRepository
->todasLasMarcas();
$hayMarcas = false;
if ($marcas == null) {
$hayMarcas = false;
} else {
$hayMarcas = true;
}
$paginator = $this->get('knp_paginator');
if ($estaFiltrando == true) {
$cochesQuery = $cocheRepository
->findModeloMarcasAnosActivos($valueModelo, $valueMarca, $valueAno, $valorActivo);
// Paginate the results of the query
$coches = $paginator->paginate(
// Doctrine Query, not results
$cochesQuery,
// Define the page parameter
$request->query->getInt('page', 1),
// Items per page
3
);
} else {
$cochesQuery = $cocheRepository->findCochesMarcas();
// Paginate the results of the query
$coches = $paginator->paginate(
// Doctrine Query, not results
$cochesQuery,
// Define the page parameter
$request->query->getInt('page', 1),
// Items per page
3
);
}
return $this->render('coche/index.html.twig', [
'coches' => $coches,
'marcas' => $marcas,
'idMarca' => $valueMarca,
'valueAno' => $valueAno,
'valueModelo' => $valueModelo,
'valueActivo' => $valueActivo,
'hayMarcas' => $hayMarcas,
'estaFiltrando' => $estaFiltrando,
]);
}
Here is the repository where I do the query and the filtering:
/**
* #param $modelo
* #param $idMarca
* #param $ano
* #param $activo
* #return Coche[]
*/
public function findModeloMarcasAnosActivos($modelo, $idMarca, $ano, $activo)
{
$qb = $this->createQueryBuilder('c');
if ($modelo != null || $modelo != '') {
$qb
->andWhere("c.modelo LIKE :searchModelo")
->setParameter('searchModelo', $modelo);
}
if ($idMarca != null) {
$qb
->andWhere("c.marca = :searchTerm")
->setParameter('searchTerm', $idMarca);
}
if ($ano != null || $ano > 0) {
$qb
->andWhere("c.ano = :searchAno")
->setParameter('searchAno', $ano);
}
if ($activo != null || $activo >= 0) {
$qb
->andWhere("c.activo = :searchActivo")
->setParameter('searchActivo', $activo);
}
return $qb
->getQuery()
->getResult();
}
findModeloMarcasAnosActivos It is the method where I filter and call it in the controller. Then I assign that filter to the pager, if it is filtering, if not, it displays the index.
Twig index.html.twig
{% block body %}
<h1 class="text-center">Coches</h1>
<div class="container">
<form class="pb-3" name="form" method="post" action="{{ path('coche_index') }}">
<div id="form">
<label for="vehicle1">Filtrar por modelo</label>
{% if valueModelo is defined %}
<input type="text" name="term_modelo" id="form_term" value="{{valueModelo}}">
{% else %}
<input type="text" name="term_modelo" id="form_term">
{% endif %}
<span>Filtrar por Marca:
</span>
<select name="term" id="form_term">
<option value="">-- Selecciona una marca --</option>
{% for marca in marcas %}
{% if idMarca is defined and marca.id == idMarca %}
<option value="{{ marca.id }}" selected>{{ marca.nombre }}</option>
{% else %}
<option value="{{ marca.id }}">{{ marca.nombre }}</option>
{% endif %}
{% endfor %}
</select>
<label for="vehicle1">Filtrar año</label>
{% if valueAno is defined %}
<input type="number" name="term_ano" id="form_term" value="{{valueAno}}">
{% else %}
<input type="number" name="term_ano" id="form_term">
{% endif %}
<label for="vehicle1">Activo</label>
{% if valueActivo is defined and valueActivo == 1 or valueActivo == true %}
<input type="checkbox" id="vehicle2" name="term_activo" id="form_term" value="1" checked>
{% else %}
<input type="checkbox" id="vehicle2" name="term_activo" id="form_term" value="1">
{% endif %}
{% if estaFiltrando == true and estaFiltrando is defined %}
<span>Resultados de búsqueda por:
<strong>{{ idMarca }}</strong>
</span>
<button class="btn btn-primary" type="submit" id="form_save" name="save">Filtrar</button>
<p>
<a class="btn btn-danger" href="{{ path('coche_index') }}">Borrar filtro</a>
</p>
{% else %}
<button class="btn btn-primary" type="submit" id="form_save" name="save">Filtrar</button>
{% endif %}
</div>
</form>
<hr>
{% if app.user %}
{% if hayMarcas == true %}
<a class="btn btn-outline-primary" href="{{ path('coche_new') }}">Crear nuevo coche</a>
{% else %}
<span>No existe ninguna marca, debes de crear primero una marca!</span>
Crear nueva marca
{% endif %}
{% endif %}
<div class="row" id="coches">
{% for coche in coches %}
<div class="col-sm-4 p-2">
<div class="card text-center">
<img src="{{ asset ('uploads/coches/' ~ coche.imagenCoche) }}" class="card-img-top img-fluid" alt="...">
<div class="card-body">
<h3 class="card-title">{{ coche.nombre }}</h3>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong>Marca:</strong>
{{ coche.marca.nombre }}</li>
<li class="list-group-item">
<strong>Modelo:</strong>
{{ coche.modelo }}</li>
<li class="list-group-item">
<strong>Descripcion</strong>
{{ coche.descripcion }}</li>
<li class="list-group-item">
<strong>Fecha de alta:</strong>
{{ coche.fechaAlta|date("d/m/Y") }}</li>
<li class="list-group-item">
<strong>Año:</strong>
{{ coche.ano }}</li>
<li class="list-group-item">
<strong>Activo:</strong>
{% if coche.activo == 1 %}
Activo</li>
{% else %}
No activo</li>
{% endif %}
<li class="list-group-item">
<strong>Fecha de Modificacion:</strong>
{{ coche.fecha_modificacion|date() }}</li>
{% if app.user %}
<li class="list-group-item">
<div>
<a class="btn btn-outline-primary" href="{{ path('coche_show', {'id': coche.id}) }}">Ver</a>
<a class="btn btn-outline-primary" href="{{ path('coche_edit', {'id': coche.id}) }}">Editar</a>
{{ include('coche/_delete_form.html.twig') }}
</div>
</li>
{% endif %}
</ul>
</div>
</div>
{% else %}
<div class="col-sm-12 p-2">
<div class="card">
<div class="card-body">
<h3 class="card-title">No se ha encontrado ningún dato!</h3>
</div>
</div>
</div>
{% endfor %}
</div>
{% if app.user %}
{% if hayMarcas == true %}
<a class="btn btn-outline-primary" href="{{ path('coche_new') }}">Crear nuevo coche</a>
{% else %}
<span>No existe ninguna marca, debes de crear primero una marca!</span>
<a class="btn btn-outline-primary" href="{{ path('marca_new') }}">Crear nueva marca</a>
{% endif %}
{% endif %}
<div class="pagination justify-content-center">
{{ knp_pagination_render(coches) }}
</div>
</div>{% endblock %}
The pager is at the bottom and the form is at the top of the page.
Here is when I filter, 2 pages appear in the pager
When I click on page 2, the other pager appears and it is as if it restarts and the filter disappears. This is the failure I have... Any solution?
EDIT
I will explain a little more about the pager:
I'm already calling the pager, in the code itself I make the sample of how to call it, however, this is not the problem I'm talking about, it's when I make the form, and I get the values from the form when filtering.
$valueModelo = $request->request->get('term_modelo');
$valueMarca = $request->request->get('term');
$valueAno = $request->request->get('term_ano');
$valueActivo = $request->request->get('term_activo');
Then I call the $paginator = $this->get('knp_paginator'); to get the paginator and then what I do is to filter, I put the query from the repository filter to the paginator:
$coches = $paginator->paginate(
// Doctrine Query, not results
$cochesQuery,
// Define the page parameter
$request->query->getInt('page', 1),
// Items per page
3
);
The problem is that the paginator, when I click on one of the links, it restarts and returns to the non-filtered paginator, in the images I also make the sample.
So you should use dependency injection first of all.
use Knp\Component\Pager\PaginatorInterface;
then in your function:
public function someNameAction(PaginatorInterface $paginator) {
...Before you return $qb do the following:
$query = $paginator->paginate(
$qb,
1,
50
);
}
You would pass your query ($qb) into $query and return $query instead of $qb. The 1 and 50 is part of the page numbers.
Note: You can call that in another function and pass $paginator if that is how you are calling the function.
I have a wired situation here. On persisting symfony inserts multiple records instead of one. I can't find any problem with my code and I am not sure how to debug this error because it seems all fine.
Logic behind application is that user can select two bus station and create a route. Also he needs to define price and group (minimum and maximum people). Additionally, user needs to select bus vehicles that will drive this particular route
Does someone knows where is the problem?
Here is the output of entity when form is submitted
StationStandardPrice {#553 ▼
-id: null
-company: Company {#549 ▶}
-busVehicleGroupSize: BusVehicleGroupSize {#1233 ▶}
-departureStation: Stations {#1247 ▶}
-destinationStation: Stations {#1236 ▶}
-currency: Currencies {#1015 ▶ …2}
-price: 99.0
-busVehicles: ArrayCollection {#554 ▼
-elements: array:2 [▼
0 => BusVehicle {#1023 ▶}
1 => BusVehicle {#1208 ▶}
]
}
}
This is entity of route (shorted version)
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \AppBundle\Entity\Company
*
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
* })
*/
private $company;
/**
* #var \AppBundle\Entity\BusVehicleGroupSize
*
* #ORM\ManyToOne(targetEntity="BusVehicleGroupSize")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="bus_vehicle_group_size_id", referencedColumnName="id")
* })
*/
private $busVehicleGroupSize;
/**
* #var \AppBundle\Entity\Stations
*
* #ORM\ManyToOne(targetEntity="Stations")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="departure_station_id", referencedColumnName="id")
* })
*/
private $departureStation;
/**
* #var \AppBundle\Entity\Stations
*
* #ORM\ManyToOne(targetEntity="Stations")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="destination_station_id", referencedColumnName="id")
* })
*/
private $destinationStation;
/**
* #var \AppBundle\Entity\Currencies
*
* #ORM\ManyToOne(targetEntity="Currencies")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="currency_id", referencedColumnName="id")
* })
*/
private $currency;
/**
* #var float
*
* #ORM\Column(name="price", type="decimal", precision=10, scale=2, nullable=false)
*/
private $price;
/**
* Many groups can have many bus vehicles
*
* #ORM\ManyToMany(targetEntity="BusVehicle", inversedBy="busVehicleGroup")
* #ORM\JoinTable(name="standard_station_price_bus_groups",
* joinColumns={#ORM\JoinColumn(name="station_standard_price_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="bus_vehicle_id", referencedColumnName="id")}
* )
**/
private $busVehicles;
/**
* Add bus vehicle
* #param BusVehicle $busVehicles
*/
public function addBusVehicles(BusVehicle $busVehicles)
{
if ($this->busVehicles->contains($busVehicles)) {
return;
}
$this->busVehicles->add($busVehicles);
$busVehicles->addBusVehicleGroup($this);
}
/**
* Remove bus vehicle
* #param BusVehicle $busVehicles
*/
public function removeBusVehicles(BusVehicle $busVehicles)
{
if (!$this->busVehicles->contains($busVehicles)) {
return;
}
$this->busVehicles->remove($busVehicles);
$busVehicles->removeBusVehicleGroup($this);
}
Controller:
/**
* Creates a new stationStandardPrice entity.
* #Template
*/
public function newAction(Request $request)
{
$stationStandardPrice = new Stationstandardprice();
$form = $this->createForm(StationStandardPriceType::class, $stationStandardPrice, array(
'user' => $this->getUser()
)
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($stationStandardPrice);
$em = $this->getDoctrine()->getManager();
$em->persist($stationStandardPrice);
$em->flush();
$this->addFlash('success', 'admin.stationstandardprice.created');
//return $this->redirectToRoute('stationstandardprice_show', array('id' => $stationStandardPrice->getId()));
}
return [
'stationStandardPrice' => $stationStandardPrice,
'form' => $form->createView(),
];
}
Database output:
Many to Many table status:
EDIT 1: Added twig
{% extends 'AdminBundle::layout.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('resources/public/css/datatables.min.css', 'busrent_admin') }}" rel="stylesheet"/>
<link href="{{ asset('resources/public/css/smart_wizard.min.css', 'busrent_admin') }}" rel="stylesheet"/>
<link href="{{ asset('resources/public/css/smart_wizard_theme_dots.min.css', 'busrent_admin') }}" rel="stylesheet"/>
{% endblock %}
{% block title %} {{ 'admin.stationstandardprice.new.title'|trans }} {% endblock %}
{% block breadcrumb %}
<div class="col-lg-10">
<h2>{{ 'admin.stationstandardprice.new.title'|trans }}</h2>
<ol class="breadcrumb">
<li>
{{ 'admin.dashboard.index.title'|trans }}
</li>
<li>
{{ 'admin.stationstandardprice.index.title'|trans }}
</li>
<li class="active">
<strong>{{ 'admin.stationstandardprice.new.title'|trans }}</strong>
</li>
</ol>
</div>
<div class="col-lg-2">
</div>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ 'admin.stationstandardprice.new.title'|trans }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="row">
<div class="col-lg-12">
{{ form_start(form) }}
<div id="smartwizard">
<ul>
<li><a href="#step-1">Step Title<br/>
<small>Step description</small>
</a>
</li>
<li><a href="#step-2">Step Title<br/>
<small>Step description</small>
</a>
</li>
<li><a href="#step-3">Step Title<br/>
<small>Step description</small>
</a>
</li>
</ul>
<div>
<div id="step-1" class="">
{{ form_row(form.departureStation) }}
{{ form_row(form.destinationStation) }}
</div>
<div id="step-2" class="">
{{ form_row(form.busVehicleGroupSize) }}
{{ form_row(form.price) }}
{{ form_row(form.currency) }}
</div>
<div id="step-3" class="">
{% if app.user.company is not null %}
<div class="ibox-content">
<table id="busVehicleTable" class="table table-striped">
<thead>
<tr>
<th>{{ 'admin.busvehicle.form.licencePlate'|trans }}</th>
<th>{{ 'admin.busvehicle.form.chassisNumber'|trans }}</th>
<th>{{ 'admin.busvehicle.form.passengerSeatsNumber'|trans }}</th>
<th>{{ 'admin.busvehicle.form.busType'|trans }}</th>
<th>{{ 'admin.busvehicle.form.emissionClass'|trans }}</th>
<th>{{ 'admin.busvehicle.form.fullDayPrice'|trans }}</th>
<th>{{ 'admin.busvehicle.form.halfDayPrice'|trans }}</th>
<th>{{ 'admin.busvehicle.form.pricePerKm'|trans }}</th>
<th>{{ 'admin.busvehicle.form.amenities'|trans }}</th>
<th>{{ 'actions'|trans }}</th>
</tr>
</thead>
<tbody>
{% set inc = 0 %}
{% for busVehicle in app.user.company.busVehicle %}
<tr>
<td>{{ busVehicle.licencePlate }}</td>
<td>{{ busVehicle.chassisNumber }}</td>
<td>{{ busVehicle.passengerSeatsNumber }}</td>
<td>{{ busVehicle.busType.type }}</td>
<td>{{ busVehicle.emissionClass.name }}</td>
<td>{{ busVehicle.fullDayPrice }}</td>
<td>{{ busVehicle.halfDayPrice }}</td>
<td>{{ busVehicle.pricePerKm }}</td>
<td>
<i style="cursor: pointer;" class="fa fa-search"
aria-hidden="true"
data-toggle="collapse"
data-target="#amenities{{ inc }}"></i>
</td>
<td>
<div id="addBusVehicleDiv{{ busVehicle.id }}">
<button data-id="{{ busVehicle.id }}" href="#"
class="addBusVehicle btn btn-primary btn-sm">
<i class="fa fa-plus"></i>
<span class="bold"> Add bus vehicle</span>
</button>
</div>
<div id="removeBusVehicleDiv{{ busVehicle.id }}"
style="display: none;">
<div id="removeBusVehicleDiv{{ busVehicle.id }}">
<button data-id="{{ busVehicle.id }}"
href="#"
class="removeBusVehicle btn btn-danger btn-sm">
<i class="fa fa-times"></i>
<span class="bold"> Remove bus vehicle</span>
</button>
</div>
</div>
</td>
</tr>
<tr id="amenities{{ inc }}" class="collapse">
<td>
>{{ 'admin.busvehicle.form.amenities'|trans }}:
</td>
<td>
<div>
<p>
{% for busAmenity in busVehicle.busVehiclesAmenities %}
{% if loop.last %}
{{ busAmenity.name }}
{% else %}
{{ busAmenity.name }},
{% endif %}
{% endfor %}
</p>
</div>
</td>
</tr>
{% set inc = inc + 1 %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="{{ asset('resources/public/js/jquery.smartWizard.min.js', 'busrent_admin') }}"></script>
<script src="{{ asset('resources/public/js/busVehicleGroup.js', 'busrent_admin') }}"></script>
{% endblock %}
JS:
// Smart Wizard
$('#smartwizard').smartWizard({
selected: 0, // Initial selected step, 0 = first step
keyNavigation:true, // Enable/Disable keyboard navigation(left and right keys are used if enabled)
autoAdjustHeight:true, // Automatically adjust content height
cycleSteps: false, // Allows to cycle the navigation of steps
backButtonSupport: true, // Enable the back button support
showStepURLhash: true,
useURLhash: true, // Enable selection of the step based on url hash
lang: { // Language variables
next: 'Next',
previous: 'Previous'
},
toolbarSettings: {
toolbarPosition: 'bottom', // none, top, bottom, both
toolbarButtonPosition: 'right', // left, right
showNextButton: true, // show/hide a Next button
showPreviousButton: true, // show/hide a Previous button
toolbarExtraButtons: [
$('<button disabled id="finishForm" type="submit"></button>').text('Finish')
.addClass('btn btn-info')
]
},
anchorSettings: {
anchorClickable: false, // Enable/Disable anchor navigation
enableAllAnchors: false, // Activates all anchors clickable all times
markDoneStep: true, // add done css
enableAnchorOnDoneStep: true // Enable/Disable the done steps navigation
},
contentURL: null, // content url, Enables Ajax content loading. can set as data data-content-url on anchor
disabledSteps: [], // Array Steps disabled
errorSteps: [], // Highlight step with errors
theme: 'dots',
transitionEffect: 'fade', // Effect on navigation, none/slide/fade
transitionSpeed: '400'
});
$("#smartwizard").on("showStep", function(e, anchorObject, stepNumber, stepDirection) {
if (stepNumber == 2){
$('#finishForm').attr("disabled", false);
}
else{
$('#finishForm').attr("disabled", true);
}
});
the problem seems to be in:
public function addBusVehicles(BusVehicle $busVehicles)
{
if ($this->busVehicles->contains($busVehicles)) {
return;
}
$this->busVehicles->add($busVehicles);
$busVehicles->addBusVehicleGroup($this);
}
when call
$busVehicles->addBusVehicleGroup($this);
your are adding a new route to the relation, try without calling this method.
I have simple edit form where the user will have to fill up required fields. The problem is the validation of required fields doesn't show, and also the fields or data don't change.
UPDATE I var_dump the $form->isSubmitted() and it shows bool(false)
Here's my controller:
public function editAction(Request $request, $id) {
$company = $this->getDoctrine()
->getRepository('SwipeBundle:Company')
->find($id);
if(!$company) {
throw $this->createNotFoundException(
'No Company found for id '.$id
);
}
$form = $this->createForm(CompanyType::class, $company, array(
'action'=>$this->generateUrl('swipe_backend_company_edit', array('id'=>$company->getId())),
'method'=>'PUT'
));
if ($request->getMethod() == "POST") {
if ($form->isSubmitted()) {
// $em->persist($company);
// $em->flush();
echo "Update";
}
}
return $this->render('Backend/Company/edit.html.twig', array(
'form'=>$form->createView(),
'company'=>$company
));
}
And here's my twig template:
{% extends '::Backend/base.html.twig' %}
{% block body %}
<!-- Section -->
<section class="sections">
<!-- Side Bar -->
{% include '::Backend/side_menu_bar.html.twig' %}
<!-- Wrapper -->
<div id="administrator" class="wrapper">
<div class="mt40 pt30"> <!-- Container -->
<h1 class="mb10 bold">Edit Company</h1>
<p class="mb30">Fill up all the required fields for Company.</p>
{% if not form.vars.valid %}
<p class="alert note-error">
There are errors in your form. Please check the fields marked in red.
</p>
{% endif %}
<div class="alert note-error">
<p>Fields with asterisk (*) are required</p>
</div>
{% set url = path('swipe_backend_company_edit', { 'id': company.id }) %}
<form novalidate method="post" action="{{ url }}" class="p20 card mb30">
<div class="sections pb30 pt10">
<fieldset class="col span6">
{% set attr = {} %}
{% if form_errors(form.name) is not empty %}
{% set attr = attr|merge({ 'class': 'alert error'}) %}
{% endif %}
<label for="" class="input-required">
<strong>Company Name<span class="highlight-red">*</span>
</strong>
</label>
{{ form_widget(form.name, { 'attr': attr } ) }}
{% if not form.name.vars.valid %}
<p class="mt10" style="color: #DC2B1B;">
{{ form.name.vars.errors[0].message }}
</p>
{% endif %}
</fieldset>
<fieldset class="col span6">
<strong>
{{ form_label(form.website) }}
</strong>
{{ form_widget(form.website) }}
</fieldset>
</div>
<div class="sections pb30 pt10">
{% set attr = {} %}
{% if form_errors(form.email_address) is not empty %}
{% set attr = attr|merge({ 'class': 'alert error'}) %}
{% endif %}
<label for="" class="input-required">
<strong>Company Email Address<span class="highlight-red">*</span>
</strong>
</label>
{{ form_widget(form.email_address, { 'attr': attr } ) }}
{% if not form.email_address.vars.valid %}
<p class="mt10" style="color: #DC2B1B;">
{{ form.email_address.vars.errors[0].message }}
</p>
{% endif %}
</div>
<div class="sections pb30 pt10">
<fieldset class="col span6">
<strong>
{{ form_label(form.telephone_no) }}
</strong>
{{ form_widget(form.telephone_no) }}
</fieldset>
<fieldset class="col span6">
<strong>
{{ form_label(form.mobile_no) }}
</strong>
{{ form_widget(form.mobile_no) }}
</fieldset>
</div>
<div class="sections pb30 pt10">
{% set attr = {} %}
{% if form_errors(form.address) is not empty %}
{% set attr = attr|merge({ 'class': 'alert error'}) %}
{% endif %}
<label for="" class="input-required">
<strong>Company Address<span class="highlight-red">*</span>
</strong>
</label>
{{ form_widget(form.address, { 'attr': attr } ) }}
{% if not form.address.vars.valid %}
<p class="mt10" style="color: #DC2B1B;">
{{ form.address.vars.errors[0].message }}
</p>
{% endif %}
</div>
<div class="text-right mt20 mb10">
<button class="btn btn-positive mt10 mr5">Update Company</button>
<button class="btn btn-positive mt10 mr5">Cancel</button>
</div>
{{ form_rest(form) }}
</form>
</div> <!-- Container End -->
</div> <!-- Wrapper End -->
</section> <!-- Section End -->
{% endblock %}
Only one action for edit / Update the record
public function editAction($id) {
$em = $this->getDoctrine()->getManager();
$company = $em
->getRepository('SwipeBundle:Company')
->find($id);
if(!$company) {
throw $this->createNotFoundException(
'No Company found for id '.$id
);
}
$form = $this->createForm(CompanyType::class, $company, array(
'action'=>$this->generateUrl('swipe_backend_company_update', array('id'=>$company->getId())),
'method'=>'PUT'
));
if ($request->getMethod() == "POST") {
if ($form->isValid()) {
$em->persist($company);
$em->flush();
}
}
return $this->render('Backend/Company/edit.html.twig', array(
'form'=>$form->createView(),
'company'=>$company
));
}
and also Change the form action in twig same a edit action
Make sure this is useful to you.