1

I want to create form, that serve for adding, editing (and removing when url field is empty) menu items. Problem is that the count of rows/items are variable. (As you can see on the first picture)

Questions:

1)How to write a form that has variable number of fields.

2)How to parse data into the fields at this form.

class GalleryType extends AbstractType {

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->add(
    //...some textType, fileType fields,...etc
    );
}

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults([
        //...
        //some data/data_class option that parse data into the field
    ]);
}

example of form (administration part)

Extra Information:

I am working on own simple content management system with Symfony 3 framework. I want to allow user to add menu item with information like: URL, Title and for instance FA icon, background image,..etc .

-There is always one empty row for adding item and the rest of fields are fulfilled with existing data (menu item/s). When you confirm the form, this row is added into the form (and empty row as well).

-There are few different kind of menu: main menu, slider, side menu, that has diferent type of fields. (you can see it on the second picture)

-Main menu has: title, url and some item can have children items (as sub menu)

-Slider has: title, url, color of title, background image

-Side menu has: title, url and Font Awesome Icon

I have already done form for navigation menu (footer) where is just 2 fields(title and link), but I feel this is not propertly way how to programming it... for illustrative purposes here is how I've done navigation example of menus design Controller:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\SMBundle\Navigation;
use AppBundle\Entity\Sett;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Form\Extension\Core\Type\TextType;

class SettingsController extends Controller {

//....

/**
 * @Route("/admin/menu/navigation", name="navigation")
 */
public function navigationAction(Request $request) {
    $set = $this->getDoctrine()->getRepository('AppBundle:Sett')->findOneByName('navigation');
    $navigation = $this->deserializeFromStringToObject('navigation');

    if (!$navigation) {
        $set = new Sett();
        $navigation = new Navigation();
    }

    $form = $this->createFormFromArray($navigation->getLinksArray());
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $set->setEditedAt(new \DateTime());
        $set->setName('navigation');

        $this->brutalHack($navigation, $form);

        $set->setContent($this->serializeFromObjectToString($navigation));

        // Save
        $this->save($set);
        return $this->redirect($this->generateUrl('navigation'));
    }
    return $this->render("viewSM/menu/navigation.html.twig", array('form' => $form->createView()));
}

 private function deserializeFromStringToObject($name) {
    $object = $this->getDoctrine()->getRepository('AppBundle:Sett')->findOneByName($name);
    if (!$object) {
        return null;
    }
    $serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new JsonEncoder()));
    return $serializer->deserialize($object->getContent(), 'AppBundle\\Entity\\SMBundle\\' . ucfirst($name), 'json');
}

private function serializeFromObjectToString($object) {
    $serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new JsonEncoder()));
    return $serializer->serialize($object, 'json');
}

   private function createFormFromArray(array $collection) {
    $i = 0;
    $formBuilder = $this->createFormBuilder();

    foreach ($collection as $key => $value) {
        $formBuilder
                ->add('url' . $i, TextType::class, ['label' => 'URL ', 'data' => '' . $key, 'attr' => ['class' => 'form-control']])
                ->add('name' . $i, TextType::class, ['label' => 'Titulek ', 'data' => '' . $value, 'attr' => ['class' => 'form-control']]);
        $i++;
    }
    $formBuilder
            ->add('url' . $i, TextType::class, ['label' => 'URL ', 'attr' => ['class' => 'form-control']])
            ->add('name' . $i, TextType::class, ['label' => 'Titulek ', 'attr' => ['class' => 'form-control']])
            ->add('submit', \Symfony\Component\Form\Extension\Core\Type\SubmitType::class, ['label' => 'Uložit', 'attr' => ['class' => 'btn btn-primary']]);
    $form = $formBuilder->getForm();
    return $form;
}

private function save($set) {
    $em = $this->getDoctrine()->getManager();
    $em->persist($set);
    $em->flush();
}

private function brutalHack($navigation, $form) {
    $nav = array();
    if (count($navigation->getLinksArray()) == 0) {
        $nav[$form['url0']->getData()] = $form['name0']->getData();
    }
    for ($i = 0; $i < count($navigation->getLinksArray()); $i++) {
        $key = $form['url' . $i]->getData();
        $value = $form['name' . $i]->getData();
        if ($key != NULL && $value != NULL) {
            $nav[$key] = $value;
        }
    }
    $key = $form['url' . $i]->getData();
    $value = $form['name' . $i]->getData();
    if ($key != NULL && $value != NULL) {
        $nav[$key] = $value;
    }
    $navigation->setLinksArray($nav);
}
//...
}

Entity:

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="sett")
 * @ORM\HasLifecycleCallbacks
 */
class Sett  
{
 /**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @ORM\Column(name="name", length=255)
 */
private $name;

/**
 * @ORM\Column(name="content", type="json_array")
 */
private $content;  

     /**
 * @ORM\Column(name="edited_at", type="datetime")
 */
private $editedAt;

 /**
 * @ORM\Column(name="created_at", type="datetime")
 */
private $createdAt;

/**
 * @ORM\PrePersist
 */
public function onPrePersist()
{
    $this->createdAt = new \DateTime();
}

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

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

    return $this;
}

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

/**
 * Set content
 *
 * @param array $content
 *
 * @return Set
 */
public function setContent($content)
{
    $this->content = $content;

    return $this;
}

/**
 * Get content
 *
 * @return array
 */
public function getContent()
{
    return $this->content;
}

/**
 * Set editedAt
 *
 * @param \DateTime $editedAt
 *
 * @return Set
 */
public function setEditedAt($editedAt)
{
    $this->editedAt = $editedAt;

    return $this;
}

/**
 * Get editedAt
 *
 * @return \DateTime
 */
public function getEditedAt()
{
    return $this->editedAt;
}

/**
 * Set createdAt
 *
 * @param \DateTime $createdAt
 *
 * @return Set
 */
public function setCreatedAt($createdAt)
{
    $this->createdAt = $createdAt;

    return $this;
}

/**
 * Get createdAt
 *
 * @return \DateTime
 */
public function getCreatedAt()
{
    return $this->createdAt;
}
}

Data class:

class Navigation 
{
    private $linksArray;

public function __construct() {
    $this->linksArray=array();
}

function getLinksArray() {
    return $this->linksArray;
}

function setLinksArray($linksArray) {
    $this->linksArray = $linksArray;
}

function add($key,$value){
    $this->linksArray[$key]=$value;
}


}

1 Answer 1

1

I am not sure if this will work but you should give it a try.

2)How to parse data into the form that has variable number of fields.

You can send the data as form $options.

in your controller

$oForm = $this->createForm(YourFormType::class, 
$FormObject, [
    'your_options' => [
        'Checkbox' => 'FieldName1',
        'TextArea' => 'FieldName2'
]);

in your form

public function buildForm(FormBuilderInterface $builder, array $options)
{

foreach($options['your_options'] as $key, $option) { //you can name $option as $filedName or whatever you find convenient
    $builder->add($option, $key.Type::class);
}
...}


public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'your_options' => null
    ])
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.