Asked  7 Months ago    Answers:  5   Viewed   33 times

I've got two forms in a same page.

My problem is when I tried to submit a form, it's like it tried to submit the second form below in the page as well.

As follow, you can find my 2 forms :

public function createSuiviForm() {

    return $form = $this->createFormBuilder(null)
            ->add('numero', 'text', array('label' => 'N° : ',
                'constraints' => array(
                    new AssertNotBlank(array('message' => 'XXXX')),
                    new AssertLength(array('min' => 19, 'max' => 19, 'exactMessage' => 'XXX {{ limit }} XXX')))))
            ->add('xxxx', 'submit')
            ->getForm();
}

public function createModificationForm() {

    return $form = $this->createFormBuilder(null)
            ->add('modification', 'submit', array('label' => 'XXXXXXXXXXXXXXXXXXXX'))
            ->getForm();
}

My second form as only a submit button.

I passed them to my render and display them by using :

<div class="well">
    <form method="post" action='' {{form_enctype(form)}} >
        {{ form_widget(form) }}
        <input type="submit" class="btn btn-primary"/>
    </form>
    <div class='errors'>
        {{ form_errors(form) }}
     </div>
</div>

'form' is the name of my variable to the first form and 'update' for my second form.

When I attempted to submit my second form, I need to click twice and finally I get :

"This form should not contain extra fields."
And all non valid input for the remainding form.

I tried to add validation_group to false but to no avail.

I don't understand why I got this error because my forms are not embedded at all

I hope you will understand...

 Answers

59

You have to treat the forms separately:

if('POST' === $request->getMethod()) {
 
    if ($request->request->has('form1name')) {
        // handle the first form  
    }

    if ($request->request->has('form2name')) {
        // handle the second form  
    }
}

This is perfectly explained in Symfony2 Multiple Forms: Different From Embedded Forms (temporarily unavailable - see below)

Update

As the link provided above is temporarily unavailable, you can see an archive of that resource here.

Wednesday, March 31, 2021
 
Bharanikumar
answered 7 Months ago
38

These two types are not the same. In the first case you use option 'multiple' => true that means that form expects collection of Category entity. From your controller I see that you have (One|Many)-To-Many relation Task-Category. Category here is ArrayCollection of Category entities and therefore your form is working.

In the second case you have ->add('category', new CategoryType()) that means that Category can be the only one, according to your controller and Task entity it is not true. You need to create collection of CategoryType() here.

->add('category', 'collection', array('type' => new CategoryType()))

Also I think you must provide this line with more options to fit your application.

Wednesday, March 31, 2021
 
mikelovelyuk
answered 7 Months ago
88

You need to declare your theme before displaying your form.

You should try :

{% form_theme form 'ThemeX.html.twig' %}
    {{ form(formX) }}

{% form_theme form 'ThemeY.html.twig' %}
    {{ form(formY) }}

If that doesn't work (ie. second form theme declaration doesn't overwrite the first one), just use a separate template for your second form and insert it in your parent template using an include.

Try [http://symfony.com/doc/current/cookbook/form/form_customization.html#child-forms](this page) of symfony doc for more information

Saturday, May 29, 2021
 
superhero
answered 5 Months ago
85

The below code work for me

<?php
$config = require('config.php');
?>
<html>
  <head>
    <title>reCAPTCHA demo</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

    <!-- Boostrap Validator --> 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.9/validator.min.js" ></script>

    <script type="text/javascript">
    $(document).ready(function(){
        var demo1Call = 0;
        var demo2Call = 0;

        $('#demo-form1').validator().on('submit', function (e) {
          if (e.isDefaultPrevented()) {
            // handle the invalid form...
            console.log("validation failed");
          } else {
            // everything looks good!
            demo1Call++;

            e.preventDefault();         
            console.log("validation success");

            if(demo1Call==1)
            {
                widgetId1 = grecaptcha.render('recaptcha1', {
                'sitekey' : '<?php echo $config['client-key']; ?>',
                'callback' : onSubmit1,
                'size' : "invisible"
                });
            }

            grecaptcha.reset(widgetId1);

            grecaptcha.execute(widgetId1);          
          }
        });

        $('#demo-form2').validator().on('submit', function (e) {
          if (e.isDefaultPrevented()) {
            // handle the invalid form...
            console.log("validation failed");
          } else {
            // everything looks good!
            demo2Call++;

            e.preventDefault();
            console.log("validation success");

            if(demo2Call==1)
            {
                widgetId2 = grecaptcha.render('recaptcha2', {
                'sitekey' : '<?php echo $config['client-key']; ?>',
                'callback' : onSubmit2,
                'size' : "invisible"
                });
            }

            grecaptcha.reset(widgetId2);

            grecaptcha.execute(widgetId2);

          }
        });

    });

    function onSubmit1(token){
            document.getElementById("demo-form1").submit();
    };

    function onSubmit2(token){
            document.getElementById("demo-form2").submit();
    };

    </script>


  </head>
  <body>
    <div class="container">
    <br>
        <div class="row">
            <div class="col-md-5 col-md-offset-3">
                <form id="demo-form1" data-toggle="validator" role="form"  action="admin.php" method="POST" >
                    <div class="form-group">
                        <label for="inputName" class="control-label">Name</label>
                        <input type="text" class="form-control" id="inputName" placeholder="Geordy James" required/>
                    </div>
                    <div id='recaptcha1' ></div>
                    <button class="btn btn-block btn-primary"  type="submit">Submit</button>
                </form>
            </div>
        </div>

    <br>
        <div class="row">
            <div class="col-md-5 col-md-offset-3">
                <form id="demo-form2" data-toggle="validator" role="form"  action="admin2.php" method="POST" >
                    <div class="form-group">
                        <label for="inputName" class="control-label">Name</label>
                        <input type="text" class="form-control" id="inputName" placeholder="Geordy James" required/>
                    </div>
                    <div id='recaptcha2' ></div>
                    <button class="btn btn-block btn-primary"  type="submit">Submit</button>
                </form>
            </div>
        </div>  
    </div>
    <script src="https://www.google.com/recaptcha/api.js" async defer ></script>
  </body>
</html>

I used Unofficial Google Invisible reCAPTCHA PHP library in this program and you can download it from https://github.com/geordyjames/google-Invisible-reCAPTCHA . If this method doesn't work for you please comment below.

Wednesday, August 11, 2021
 
phi
answered 3 Months ago
phi
12

You're not tackling the problem from the right angle. If there should be a main tag, then this property should not be added in the Tag entity itself, but in the entity that contains it!

I'm speaking of the data_class entity related to the form having the tags attribute. This is the entity that should have a mainTag property.

If defined properly, this new mainTag attribute will not be a boolean, for it will contain a Tag instance, and thus will not be associated to a checkbox entry.

So, the way I see it, you should have a mainTag property containing your instance and a tags property that conatins all other tags.

The problem with that is that your collection field will no longer contain the main tag. You should thus also create a special getter getAllTags that will merge your main tag with all others, and change your collection definition to:

$builder->add('allTags', 'collection', array(
    'type' => new TagType(),
    'label' => false,
    'allow_add' => true,
    'allow_delete' => true,
    'by_reference' => false
));

Now, how do we add the radio boxes, you may ask? For this, you will have to generate a new field:

$builder->add('mainTag', 'radio', array(
    'type' => 'choice',
    'multiple' => false,
    'expanded' => true,
    'property_path' => 'mainTag.id', // Necessary, for 'choice' does not support data_classes
));

These are the basics however, it only grows more complex from here. The real problem here is how your form is displayed. In a same field, you mix the usual display of a collection and the display of a choice field of the parent form of that collection. This will force you to use form theming.

To allow some room to reusability, you need to create a custom field. The associated data_class:

class TagSelection
{
    private mainTag;

    private $tags;

    public function getAllTags()
    {
        return array_merge(array($this->getMainTag()), $this->getTags());
    }

    public function setAllTags($tags)
    {
        // If the main tag is not null, search and remove it before calling setTags($tags)
    }

    // Getters, setters
}

The form type:

class TagSelectionType extends AbstractType
{
    protected buildForm( ... )
    {
        $builder->add('allTags', 'collection', array(
            'type' => new TagType(),
            'label' => false,
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false
        ));

        // Since we cannot know which tags are available before binding or setting data, a listener must be used
        $formFactory = $builder->getFormFactory();
        $listener = function(FormEvent $event) use ($formFactory) {

            $data = $event->getForm()->getData();

            // Get all tags id currently in the data
            $choices = ...;
            // Careful, in PRE_BIND this is an array of scalars while in PRE_SET_DATA it is an array of Tag instances

            $field = $this->factory->createNamed('mainTag', 'radio', null, array(
                'type' => 'choice',
                'multiple' => false,
                'expanded' => true,
                'choices' => $choices,
                'property_path' => 'mainTag.id',
            ));
            $event->getForm()->add($field);
        }

        $builder->addEventListener(FormEvent::PRE_SET_DATA, $listener);
        $builder->addEventListener(FormEvent::PRE_BIND, $listener);
    }

    public function getName()
    {
        return 'tag_selection';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'TagSelection', // Adapt depending on class name
            // 'prototype' => true,
        ));
   }
}

Finally, in the form theme template:

{% block tag_selection_widget %}
    {% spaceless %}
    {# {% set attr = attr|default({})|merge({'data-prototype': form_widget(prototype)}) %} #}
    <ul {{ block('widget_attributes') }}>
        {% for child in form.allTags %}
        <li>{{ form_widget(form.mainTag[child.name]) }} {{ form_widget(child) }}</li>
        {% endfor %}
    </ul>
    {% endspaceless %}
{% endblock tag_selection_widget %}

Lastly, we need to include that in your parent entity, the one that originally contained tags:

class entity
{
    // Doctrine definition and whatnot
    private $tags;

    // Doctrine definition and whatnot
    private $mainTag;

    ...
    public setAllTags($tagSelection)
    {
        $this->setMainTag($tagSelection->getMainTag());
        $this->setTags($tagSelection->getTags());
    }

    public getAllTags()
    {
        $ret = new TagSelection();
        $ret->setMainTag($this->getMainTag());
        $ret->setTags($this->getTags());

        return $ret;
    }

    ...
}

And in your original form:

$builder->add('allTags', new TagSelection(), array(
    'label' => false,
));

I recognize the solution I propose is verbose, however it seems to me to be the most efficient. What you are trying to do cannot be done easily in Symfony.

You can also note that there is an odd "prototype" option in the comment. I just wanted to underline a very useful property of "collection" in your case: the prototype option contains a blank item of your collection, with placeholders to replace. This allow to quickly add new items in a collection field using javascript, more info here.

Monday, September 20, 2021
 
Jon
answered 1 Month ago
Jon
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :