0

I have an issue that I haven't been able to solve. I have 2 entities:

<?php
namespace ...\Entity;

// ...

/**
 * Pregunta
 *
 * @ORM\Table(name="pregunta", indexes={@ORM\Index(name="fk_respuesta_tipo_respuesta1_idx", columns={"tipo_respuesta_id"}))})
 * @ORM\Entity 
 */
class Pregunta {

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="...\Entity\Respuesta", mappedBy="pregunta")
     */
    private $respuesta;


    public function __construct() {
        $this->tipoPrueba = new \Doctrine\Common\Collections\ArrayCollection();
        $this->respuesta = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add respuesta
     *
     * @param ...\Entity\Respuesta $respuesta
     * @return Pregunta
     */
    public function addRespuesta(...\Entity\Respuesta $respuesta) {
        $this->respuesta[] = $respuesta;

        return $this;
    }

    /**
     * Remove respuesta
     *
     * @param ...\Entity\Respuesta $respuesta
     */
    public function removeRespuesta(...\Entity\Respuesta $respuesta) {
        $this->respuesta->removeElement($respuesta);
    }

    /**
     * Get respuesta
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getRespuesta() {
        return $this->respuesta;
    }

    function setRespuesta(\Doctrine\Common\Collections\Collection $respuesta) {
        $this->respuesta = $respuesta;
    }
}

Then I have the Respuesta entity:

<?php
class Respuesta {

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="texto_respuesta", type="text", nullable=false)
     */
    private $textoRespuesta;

    // ...

I have a PreguntaType which has its fields and a collection of RespuestaType:

/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
            ->add('titulo', 'textarea', array("label" => "Enunciado: ", "required" => true, "attr" => array('class' => 'form-control')))
            ->add('numeroPagina', 'integer', array("label" => "Página: ", "required" => true, "attr" => array('class' => 'form-control')))
            ->add('areaConocimiento', 'entity', array('class' => 'UciBaseDatosBundle:AreaConocimiento', 'required' => false, 'attr' => array('style' => 'width: 100%')))
            ->add('trianguloTalento', 'entity', array('class' => 'UciBaseDatosBundle:TrianguloTalento', 'required' => false, 'attr' => array('style' => 'width: 100%')))
            ->add('capitulo', 'entity', array('class' => 'UciBaseDatosBundle:Capitulo', 'required' => false, 'attr' => array('style' => 'width: 100%')))
            ->add('grupoProcesos', 'entity', array('class' => 'UciBaseDatosBundle:GrupoProcesos', 'required' => false, 'attr' => array('style' => 'width: 100%')))
            ->add('tipoPrueba', 'entity', array('class' => 'UciBaseDatosBundle:TipoPrueba', 'expanded' => true, 'multiple' => true, 'required' => false, 'attr' => array('style' => 'width: 100%')))
            ->add('libro', 'entity', array('class' => 'UciBaseDatosBundle:Libro', 'required' => false, 'attr' => array('style' => 'width: 100%')))
            ->add('respuesta', 'collection', array(
                'type' => new RespuestaType(),
                'prototype' => true,
                'allow_add' => true,
                'by_reference' => false,
                'allow_delete' => true,
                'label' => ' '
    ));
}

/**
 * @param OptionsResolverInterface $resolver
 */
public function setDefaultOptions(OptionsResolverInterface $resolver) {
    $resolver->setDefaults(array(
        'data_class' => 'Uci\Bundle\BaseDatosBundle\Entity\Pregunta'
    ));
}

However, when I debug my form submission, if i set my collection to 'required' => true, it throws this error An invalid form control with name='...[respuesta][Respuesta0][RespuestaField]' is not focusable. On the other hand, If I set it to 'required' => false, my collections' fields are always empty.

This is my TWIG file:

<form action="{{ path('uci_administrador_registrarPregunta', { 'idTipoRespuesta': tipoRespuesta.id }) }}" name="formulario" method="POST" enctype="multipart/form-data">             
    <h3 class="thin text-center">Registrar una nueva pregunta {{ tipoRespuesta.nombre }}</h3>
    <p class="text-center text-muted">{{ tipoRespuesta.explicacion }}</p>
    <hr>

    {% if error %}
        <div style="color:red">{{ error }}</div>
    {% endif %}

    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.titulo) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.numeroPagina) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.areaConocimiento) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.capitulo) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.grupoProcesos) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.trianguloTalento) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.tipoPrueba) }}
        </div>
    </div>
    <div class="row top-margin">
        <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
            {{ form_row(form.libro) }}
        </div>
    </div>
    <br>
    <hr>
    <h3>Respuestas</h3><br>
    <div class="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
        {# iterate over each existing tag and render its only field: name #}
        {% for respuesta in form.respuesta %}
            <div class="row top-margin">
                <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
                    {{ form_row(respuesta.correcta) }}
                </div>
            </div>
        {% endfor %}
    </div>
    <br><br>
    <div class="row">
        <div class="col-lg-8">                     
        </div>
        <div class="col-lg-4 text-right">
            <button class="btn btn-action" type="submit">Registrar</button>
        </div>
    </div>
    {{ form_rest(form) }}
</form>

I use some javascript to add my collection forms:

var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('<a href="#" class="add_tag_link">Añadir respuesta</a>');
var $newLinkLi = $('<div></div>').append($addTagLink);
function addTagForm($collectionHolder, $newLinkLi) {
    // Get the data-prototype explained earlier
    var prototype = $collectionHolder.data('prototype');
    // get the new index
    var index = $collectionHolder.data('index');
    // Replace '__name__' in the prototype's HTML to
    // instead be a number based on how many items we have
    var newForm = prototype.replace(/__name__/g, 'Respuesta' + index);
    // increase the index with one for the next item
    $collectionHolder.data('index', $collectionHolder.find(':input').length);
    // Display the form in the page in an li, before the "Add a tag" link li
    var $newFormLi = $('<div style="background-color:#F6F6F6; border-radius:10px;padding: 25px;border: 5px solid #003c70;margin: 5px;"></div><br>').append(newForm);
    $newLinkLi.before($newFormLi);
}
document.ready = function () {
    // Get the ul that holds the collection of tags
    $collectionHolder = $('div.respuestas');
    // add the "add a tag" anchor and li to the tags ul
    $collectionHolder.append($newLinkLi);
    // count the current form inputs we have (e.g. 2), use that as the new
    // index when inserting a new item (e.g. 2)
    $collectionHolder.data('index', $collectionHolder.find(':input').length);
    $addTagLink.on('click', function (e) {
        // prevent the link from creating a "#" on the URL
        e.preventDefault();
        // add a new tag form (see next code block)
        addTagForm($collectionHolder, $newLinkLi);
    });

    // ...
}

I would really appreciate any help.

Thanks.

paulgv
  • 1,818
  • 16
  • 20
  • How do you display the form in the front-end ? Please add some code from your template in your question. – paulgv Oct 26 '15 at 23:31

2 Answers2

1

My problem was in my javascript. I don´t know why my previous code didn't work but I changed and it worked. My javascript is the following:

var collectionHolder = $('#respuestas');
var prototype = collectionHolder.attr('data-prototype');
var form = prototype.replace(/__name__/g, collectionHolder.children().length); //importante
var removeFormA = $('<a href="#" onclick="addTagFormDeleteLink(event, this);">Borrar</a>');
var newLi = $('<li></li>');
newLi.append(form);
newLi.append(removeFormA);
collectionHolder.append(newLi);

And this is part of my TWIG file:

<ul id="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{% for respuesta in form.respuesta %}
      <li> {{ form_row(respuesta) }}</li>
{% endfor %}
</ul>

I hope it helps someone else.

Regards.

0

Are you trying to hide some fields in your form ? It looks like some fields are required but not actually displayed in the page, which prevents the browser from validating the form. Refer to that answer : https://stackoverflow.com/a/28340579/4114297

I wonder why you're only rendering respuesta.correcta in the loop. You might want to render the while respuesta item :

<div class="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
    {# iterate over each existing tag and render its only field: name #}
    {% for respuesta in form.respuesta %}
        <div class="row top-margin">
            <div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
                {{ form_row(respuesta) }} {# <- Here #}
            </div>
        </div>
    {% endfor %}
</div>

If you need some fields to be hidden and/or pre-filled, you could do that RespuestaType

Community
  • 1
  • 1
paulgv
  • 1,818
  • 16
  • 20
  • Thanks for your help @paulvg. I don't have hidden fields. When I try to submit the form it says: "An invalid form control with name='uci_bundle_basedatosbundle_pregunta[respuesta][Respuesta0][textoRetroalimentacion]' is not focusable.", and the same with the other Respuesta fields. I changed the loop but it throws the same error. – Ricardo Leandro Oct 27 '15 at 14:58