ninsuo / symfony-collection

[NOT MAINTAINED] A jQuery plugin that manages adding, deleting and moving elements from a Symfony form collection
https://symfony-collection.fuz.org/
MIT License
444 stars 88 forks source link

Problem with removing and moving buttons #45

Open r3m4k3 opened 8 years ago

r3m4k3 commented 8 years ago

Hi there!

This plugin is great, I'm really grateful for sharing it, good job! Now managing collections is pretty easy.

But I've got 2 problems: 1st: I can add new element to collection, I just click '+", enter the data, and then click submit/save and it's done. But how can I remove the element? Ok, when I click '-' the element disappear, I need to submit the form, but I can't. The submit button is deleted too. Each form of collection has own submit button, but in the examples there's only one button to submit. How can I handle with my case? I enclose screenshot of my form.

Screenshot: here

2nd: When I set allow_up: false and allow_down: false all my controls have been gone. I can't even add the element. Now I'm using custom theme. What am I doing wrong?

I'd be grateful for any help or advice. Thank you in advance.

Best regards

ninsuo commented 8 years ago

Hello!

Can you please share your form theme and the way you display it?

Difficult to reproduce without any code :-)

Thanks,

A

r3m4k3 commented 8 years ago

Hi!

Thank you very much for quick response. Before sharing my code, I need to provide more information. So - I have user entity and user form. In user's entity there are few entities that are using oneToMany relationships - e.g. Language. In user's form I embed forms from these entities - e.g. LanguageType.

Displaying form:

{% extends 'base.html.twig' %}
{% block content %}

<main class="registration registration-second">
<div class="container">
<h1>Edytuj profil</h1>

{% form_theme form 'jquery.collection.html.twig' %}
{{ form_start(form, {'attr':{'novalidate':'novalidate', 'class':'registration-form form-edit' }}) }}
<div class="boxes">
<div class="roll-down">
<a class="toggle-menu"><span class="">Podstawowe informacje</span></a>
</div>
<div class="registration-form-wrapper opened">

<div class="form-input form-text-field {% if not form.firstname.vars.valid %}has-error{% endif %}">
{{ form_errors(form.firstname) }}
<span>{{ form_label(form.firstname) }}</span>
{{ form_widget(form.firstname) }}
<span>wymagane</span>
</div>

<div class="form-input form-text-field {% if not form.lastname.vars.valid %}has-error{% endif %}">
{{ form_errors(form.lastname) }}
<span>{{ form_label(form.lastname) }}</span>
{{ form_widget(form.lastname) }}
<span>wymagane</span>
</div>

<div class="form-input form-text-field {% if not form.birthday.vars.valid %}has-error{% endif %}">
{{ form_errors(form.birthday) }}
<span>{{ form_label(form.birthday) }}</span>
{{ form_widget(form.birthday) }}
<span>wymagane</span>
</div>

<div class="form-input form-text-field {% if not form.residence.vars.valid %}has-error{% endif %}">
{{ form_errors(form.residence) }}
<span>{{ form_label(form.residence) }}</span>
{{ form_widget(form.residence) }}
<span>wymagane</span>
</div>

<div class="text-field"><span>Twój adres e-mail służy do zalogowania</span></div>
<div class="form-toggle">
{{ form_row(form.save) }}
</div>
</div>
</div>
{{ form_end(form) }}

</div>

</main>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
    $('.my-selector').collection();
    $('.my-selector2').collection();
    $('.my-selector3').collection();
    $('.my-selector4').collection();
</script>
{% endblock %}

My form - UserType.php:

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

use Doctrine\ORM\EntityRepository;
use AppBundle\Form\EducationType;
use AppBundle\Form\CourseType;
use AppBundle\Form\ExperienceType;
use AppBundle\Form\LanguageType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class EmployeeAccountType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        //var_dump($options['user']); exit;
        $builder
        ->add('firstname', TextType::class, array(
            'label' => 'Imię',
            'attr' => array(
                'maxlength' => 70, 
                'minlength' => 1,
                'placeholder' => 'Twoje imię'
                )
            ))
        ->add('lastname', TextType::class, array(
            'label' => 'Nazwisko',
            'attr' => array(
                'maxlength' => 70, 
                'minlength' => 1,
                'placeholder' => 'Twoje nazwisko'
                )
            ))
        ->add('birthday', TextType::class, array(
            'label' => 'Data urodzenia: ',
            'attr' => array(
                'maxlength' => 70, 
                'minlength' => 1,
                'placeholder' => 'Twoja data urodzenia'
                )
            ))
        ->add('residence', TextType::class, array(
            'label' => 'Miejsce zamieszkania: ',
            'attr' => array(
                'maxlength' => 70, 
                'minlength' => 1,
                'placeholder' => 'Twoje miejsce zamieszkania'
                )
            ))
        ->add('educations', CollectionType::class, array(
            'entry_type' => EducationType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'delete_empty' => true,
            'prototype' => true,
            'by_reference' => false,
            'attr' => array(
                'class' => 'my-selector',
                ),
            ))
        ->add('courses', CollectionType::class, array(
            'entry_type' => CourseType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'delete_empty' => true,
            'prototype' => true,
            'by_reference' => false,
            'attr' => array(
                'class' => 'my-selector2',
                ),
            ))
        ->add('experiences', CollectionType::class, array(
            'entry_type' => ExperienceType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'delete_empty' => true,
            'prototype' => true,
            'by_reference' => false,
            'attr' => array(
                'class' => 'my-selector3',
                ),
            ))
        ->add('languages', CollectionType::class, array(
            'entry_type' => LanguageType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'delete_empty' => true,
            'prototype' => true,
            'by_reference' => false,
            'attr' => array(
                'class' => 'my-selector4',
                ),
            ))
        ->add('save', SubmitType::class, array(
            'label' => 'Zapisz i zaaktualizuj informacje',
            'attr' => array('class' => 'button button-primary button-md')
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\User',
            'user' => null
        ));
    }

}

User entity - User.php:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

use Gedmo\Timestampable\Traits\Timestampable;
use Gedmo\Timestampable\Traits\TimestampableEntity;

use AppBundle\Entity\Company;

/**
 * User
 *
 * @ORM\Table(name="users")
 * @ORM\Entity
 * @UniqueEntity("email",message="Taki adres e-mail jest już zarejestrowany w bazie. ")
 *
 */
class User implements AdvancedUserInterface, \Serializable
{

    use TimestampableEntity;

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=80, unique=true)
     * @Assert\NotBlank(groups={"employer_registration", "employee_registration"}, message="To pole jest wymagane. ")
     * @Assert\Email(
     *     message = "Wprowadzony adres e-mail: {{ value }} nie jest poprawny.",
     *     checkMX = true
     * )
     *
     */
    private $email;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(groups={"employer_registration", "employee_registration", "change_password"}, message="To pole jest wymagane. ")
     */
    private $password;

    /**
     * @ORM\Column(name="account_non_expired", type="boolean")
     */
    private $accountNonExpired = true;

    /**
     * @ORM\Column(name="account_non_locked", type="boolean")
     */
    private $accountNonLocked = true;

    /**
     * @ORM\Column(name="credentials_non_expired", type="boolean")
     */
    private $credentialsNonExpired = true;

    /**
     * @ORM\Column(type="boolean")
     */
    private $enabled = true;

    /**
     * @ORM\Column(name = "action_token", type="string", length=32, nullable=true)
     */
    private $actionToken;

    /**
     * @ORM\Column(name="roles", type="array")
     */
    protected $roles;

    /**
     * @ORM\Column(name="terms", type="boolean")
     * @Assert\NotBlank(groups={"employer_registration", "employee_registration"}, message="Akceptacja regulaminu jest wymagana. ")
     */
    private $terms = false;

    /**
     * @ORM\Column(name="giodo_contact", type="boolean")
     */
    private $giodoContact = false;

    /**
     * @ORM\Column(name="giodo_information", type="boolean")
     */
    private $giodoInformation = false;

    /**
     * @ORM\Column(type="text", length=500, nullable=true)
     */
    private $description;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\NotBlank(groups={"employee_registration", "employer_profile_edit"}, message="To pole jest wymagane. ")
     */
    private $firstname;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\NotBlank(groups={"employee_registration", "employer_profile_edit"}, message="To pole jest wymagane. ")
     */
    private $lastname;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\NotBlank(groups={"employer_profile_edit"}, message="To pole jest wymagane. ")
     */
    private $birthday;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\NotBlank(groups={"employer_profile_edit"}, message="To pole jest wymagane. ")
     */
    private $residence;

    /**
     * @Assert\Type(type="AppBundle\Entity\Company")
     * @Assert\Valid()
     * @ORM\OneToOne(targetEntity="Company")
     */
    protected $company;

    /**
     * @Assert\Valid()
     * @ORM\OneToMany(targetEntity="Experience", mappedBy="user",  cascade={"persist", "remove"})
     */
    private $experiences;

    /**
     * @Assert\Valid()
     * @ORM\OneToMany(targetEntity="Education", mappedBy="user",  cascade={"persist", "remove"})
     */
    private $educations;

    /**
     * @Assert\Valid()
     * @ORM\OneToMany(targetEntity="Language", mappedBy="user", cascade={"persist", "remove"})
     */
    private $languages;

    /**
     * @Assert\Valid()
     * @ORM\OneToMany(targetEntity="Course", mappedBy="user",  cascade={"persist", "remove"})
     */
    private $courses;

    public function getCompany()
    {
        return $this->company;
    }

    public function setCompany(Company $company = null)
    {
        $this->company = $company;
    }

    public function isEmployee()
    {
        return in_array('ROLE_EMPLOYEE', $this->roles);
    }

    public function isEmployer()
    {
        return in_array('ROLE_EMPLOYER', $this->roles);
    }

    public function isEqualTo(UserInterface $user) {
        return $this->email === $user->getEmail();
    }

    /**
     * @inheritDoc
     */
    public function eraseCredentials()
    {

    }

    public function serialize()
    {
        return \serialize(array(
            $this->id,
            $this->email
        ));
    }
    public function unserialize($serialized)
    {
        list (
            $this->id,
            $this->email
        ) = \unserialize($serialized);
    }

    public function isAccountNonExpired()
    {
        return true;
    }

    public function isAccountNonLocked()
    {
        return true;
    }

    public function isCredentialsNonExpired()
    {
        return true;
    }

    public function isEnabled()
    {
        return $this->enabled;
    }

    public function getSalt()
    {
        return null;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getRoles()
    {
        return $this->roles;
    }

    public function getUsername()
    {
        return $this->email;
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set email
     *
     * @param string $email
     *
     * @return User
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set password
     *
     * @param string $password
     *
     * @return User
     */
    public function setPassword($password)
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Set accountNonExpired
     *
     * @param boolean $accountNonExpired
     *
     * @return User
     */
    public function setAccountNonExpired($accountNonExpired)
    {
        $this->accountNonExpired = $accountNonExpired;

        return $this;
    }

    /**
     * Get accountNonExpired
     *
     * @return boolean
     */
    public function getAccountNonExpired()
    {
        return $this->accountNonExpired;
    }

    /**
     * Set accountNonLocked
     *
     * @param boolean $accountNonLocked
     *
     * @return User
     */
    public function setAccountNonLocked($accountNonLocked)
    {
        $this->accountNonLocked = $accountNonLocked;

        return $this;
    }

    /**
     * Get accountNonLocked
     *
     * @return boolean
     */
    public function getAccountNonLocked()
    {
        return $this->accountNonLocked;
    }

    /**
     * Set credentialsNonExpired
     *
     * @param boolean $credentialsNonExpired
     *
     * @return User
     */
    public function setCredentialsNonExpired($credentialsNonExpired)
    {
        $this->credentialsNonExpired = $credentialsNonExpired;

        return $this;
    }

    /**
     * Get credentialsNonExpired
     *
     * @return boolean
     */
    public function getCredentialsNonExpired()
    {
        return $this->credentialsNonExpired;
    }

    /**
     * Set enabled
     *
     * @param boolean $enabled
     *
     * @return User
     */
    public function setEnabled($enabled)
    {
        $this->enabled = $enabled;

        return $this;
    }

    /**
     * Get enabled
     *
     * @return boolean
     */
    public function getEnabled()
    {
        return $this->enabled;
    }

    /**
     * Set actionToken
     *
     * @param string $actionToken
     *
     * @return User
     */
    public function setActionToken($actionToken)
    {
        $this->actionToken = $actionToken;

        return $this;
    }

    /**
     * Get actionToken
     *
     * @return string
     */
    public function getActionToken()
    {
        return $this->actionToken;
    }

    /**
     * Set roles
     *
     * @param array $roles
     *
     * @return User
     */
    public function setRoles($roles)
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * Set terms
     *
     * @param boolean $terms
     *
     * @return User
     */
    public function setTerms($terms)
    {
        $this->terms = $terms;

        return $this;
    }

    /**
     * Get terms
     *
     * @return boolean
     */
    public function getTerms()
    {
        return $this->terms;
    }

    /**
     * Set description
     *
     * @param string $description
     *
     * @return User
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set firstname
     *
     * @param string $firstname
     *
     * @return User
     */
    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;

        return $this;
    }

    /**
     * Get firstname
     *
     * @return string
     */
    public function getFirstname()
    {
        return $this->firstname;
    }

    /**
     * Set lastname
     *
     * @param string $lastname
     *
     * @return User
     */
    public function setLastname($lastname)
    {
        $this->lastname = $lastname;

        return $this;
    }

    /**
     * Get lastname
     *
     * @return string
     */
    public function getLastname()
    {
        return $this->lastname;
    }

    /**
     * Set birthday
     *
     * @param string $birthday
     *
     * @return User
     */
    public function setBirthday($birthday)
    {
        $this->birthday = $birthday;

        return $this;
    }

    /**
     * Get birthday
     *
     * @return string
     */
    public function getBirthday()
    {
        return $this->birthday;
    }

    /**
     * Set residence
     *
     * @param string $residence
     *
     * @return User
     */
    public function setResidence($residence)
    {
        $this->residence = $residence;

        return $this;
    }

    /**
     * Get residence
     *
     * @return string
     */
    public function getResidence()
    {
        return $this->residence;
    }

    /**
     * Set giodoContact
     *
     * @param boolean $giodoContact
     *
     * @return User
     */
    public function setGiodoContact($giodoContact)
    {
        $this->giodoContact = $giodoContact;

        return $this;
    }

    /**
     * Get giodoContact
     *
     * @return boolean
     */
    public function getGiodoContact()
    {
        return $this->giodoContact;
    }

    /**
     * Set giodoInformation
     *
     * @param boolean $giodoInformation
     *
     * @return User
     */
    public function setGiodoInformation($giodoInformation)
    {
        $this->giodoInformation = $giodoInformation;

        return $this;
    }

    /**
     * Get giodoInformation
     *
     * @return boolean
     */
    public function getGiodoInformation()
    {
        return $this->giodoInformation;
    }

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->experiences = new \Doctrine\Common\Collections\ArrayCollection();
        $this->educations = new \Doctrine\Common\Collections\ArrayCollection();
        $this->languages = new \Doctrine\Common\Collections\ArrayCollection();
        $this->courses = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add experience
     *
     * @param \AppBundle\Entity\Experience $experience
     *
     * @return User
     */
    public function addExperience(\AppBundle\Entity\Experience $experience)
    {
        $experience->setUser($this);
        $this->experiences[] = $experience;

        return $this;
    }

    /**
     * Remove experience
     *
     * @param \AppBundle\Entity\Experience $experience
     */
    public function removeExperience(\AppBundle\Entity\Experience $experience)
    {
        $this->experiences->removeElement($experience);
    }

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

    /**
     * Add education
     *
     * @param \AppBundle\Entity\Education $education
     *
     * @return User
     */
    public function addEducation(\AppBundle\Entity\Education $education)
    {
        $education->setUser($this);
        $this->educations[] = $education;

        return $this;
    }

    /**
     * Remove education
     *
     * @param \AppBundle\Entity\Education $education
     */
    public function removeEducation(\AppBundle\Entity\Education $education)
    {
        $this->educations->removeElement($education);
    }

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

    /**
     * Add language
     *
     * @param \AppBundle\Entity\Language $language
     *
     * @return User
     */
    public function addLanguage(\AppBundle\Entity\Language $language)
    {
        $language->setUser($this);
        $this->languages[] = $language;

        return $this;
    }

    /**
     * Remove language
     *
     * @param \AppBundle\Entity\Language $language
     */
    public function removeLanguage(\AppBundle\Entity\Language $language)
    {
        $this->languages->removeElement($language);
    }

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

    /**
     * Add course
     *
     * @param \AppBundle\Entity\Course $course
     *
     * @return User
     */
    public function addCourse(\AppBundle\Entity\Course $course)
    {
        $course->setUser($this);
        $this->courses[] = $course;

        return $this;
    }

    /**
     * Remove course
     *
     * @param \AppBundle\Entity\Course $course
     */
    public function removeCourse(\AppBundle\Entity\Course $course)
    {
        $this->courses->removeElement($course);
    }

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

And for example - language entity - Language.php:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Language
 *
 * @ORM\Table(name="languages")
 * @ORM\Entity
 *
 */
class Language
{

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="To pole jest wymagane. ")
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=256)
     * @Assert\NotBlank(message="To pole jest wymagane. ")
     */
    private $level;

    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="languages")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     *
     */
    private $user;

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Language
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set level
     *
     * @param string $level
     *
     * @return Language
     */
    public function setLevel($level)
    {
        $this->level = $level;

        return $this;
    }

    /**
     * Get level
     *
     * @return string
     */
    public function getLevel()
    {
        return $this->level;
    }

    /**
     * Set user
     *
     * @param \AppBundle\Entity\User $user
     *
     * @return Language
     */
    public function setUser(\AppBundle\Entity\User $user = null)
    {
        $this->user = $user;

        return $this;
    }

    /**
     * Get user
     *
     * @return \AppBundle\Entity\User
     */
    public function getUser()
    {
        return $this->user;
    }
}

And language form - LanguageType.php:

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

use Doctrine\ORM\EntityRepository;

class LanguageType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', ChoiceType::class, array(
                'choices'  => array(
                    'angielski' => 'angielski',
                    'niemiecki' => 'niemiecki'
                ),
                'label' => 'Język'
            ))
            ->add('level', ChoiceType::class, array(
                'choices'  => array(
                    'A1' => 'A1',
                    'A2' => 'A2',
                    'B1' => 'B1',
                    'B2' => 'B2',
                    'C1' => 'C1',
                    'C2' => 'C2'
                ),
                'label' => 'Poziom'
            ))
            ->add('save', SubmitType::class, array(
                'label' => 'Zapisz i zaaktualizuj informacje',
                'attr' => array('class' => 'button button-primary button-md')
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Language',
        ));
    }

}

To sum up - I don't display form in the custom way, only the method from the documentation. As I wrote above the main problem is how to save item after removing it, when there's no submit button. If you need more details, please let me know.

I really appreciate your help.

Thanks, best regards

PS I don't know if there's any possibility to turn on syntax highlighting on github issues. Sorry for that.

r3m4k3 commented 8 years ago

@ninsuo Any idea what am I doing wrong?

Quovandius commented 7 years ago

Hi,

you can try to delete on your form the submit button. And add it on twig view.

You can write something like this :

{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.field1) }}

 <button type="submit" class="your-class" title="">Submit</button>
{{ form_rest(form) }}
{{ form_end(form) }}

That's print you form row by row, and create one submit button at the end of the form.

For the control button, i don't know sorry.