Symfony 6 populate Dropdown with Dynamic entities

Viewed 220

I have a CreditCard entity, on form screen there is a dropdown with user's Bank entities and I want to fill BankAccount dropdown according to selected bank dynamically, I have tried couple solutions from SO and added an event listener to my form but when I change the Bank dropdown I get "Integrity constraint violation: kart_adi (credit card name field on entity) cannot be null. I am using Symfony 6 with php 8.1.2

KrediKarti.php:

class KrediKarti
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;

#[ORM\ManyToOne(targetEntity: Banka::class)]
#[ORM\JoinColumn(nullable: false)]
private $banka;

#[ORM\ManyToOne(targetEntity: BankaHesap::class, inversedBy: 'krediKartlari')]
#[ORM\JoinColumn(nullable: false)]
private $hesap;

#[ORM\Column(type: 'string', length: 255)]
private $kart_adi;

#[ORM\Column(type: 'string', length: 255)]
private $kart_no;

#[ORM\Column(type: 'integer')]
private $hesap_kesim_gunu;

#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $aciklama;

#[ORM\Column(type: 'float')]
private $borc;

#[ORM\Column(type: 'float')]
private $alacak;

#[ORM\Column(type: 'datetime_immutable')]
private $created_at;

public function getId(): ?int
{
    return $this->id;
}

public function getBanka(): ?Banka
{
    return $this->banka;
}

public function setBanka(?Banka $banka): self
{
    $this->banka = $banka;

    return $this;
}

public function getHesap(): ?BankaHesap
{
    return $this->hesap;
}

public function setHesap(?BankaHesap $hesap): self
{
    $this->hesap = $hesap;

    return $this;
}

public function getKartAdi(): ?string
{
    return $this->kart_adi;
}

public function setKartAdi(string $kart_adi): self
{
    $this->kart_adi = $kart_adi;

    return $this;
}

public function getKartNo(): ?string
{
    return $this->kart_no;
}

public function setKartNo(string $kart_no): self
{
    $this->kart_no = $kart_no;

    return $this;
}

public function getHesapKesimGunu(): ?int
{
    return $this->hesap_kesim_gunu;
}

public function setHesapKesimGunu(int $hesap_kesim_gunu): self
{
    $this->hesap_kesim_gunu = $hesap_kesim_gunu;

    return $this;
}

public function getAciklama(): ?string
{
    return $this->aciklama;
}

public function setAciklama(?string $aciklama): self
{
    $this->aciklama = $aciklama;

    return $this;
}

public function getBorc(): ?float
{
    return $this->borc;
}

public function setBorc(float $borc): self
{
    $this->borc = $borc;

    return $this;
}

public function getAlacak(): ?float
{
    return $this->alacak;
}

public function setAlacak(float $alacak): self
{
    $this->alacak = $alacak;

    return $this;
}

public function getCreatedAt(): ?\DateTimeImmutable
{
    return $this->created_at;
}

public function setCreatedAt(\DateTimeImmutable $created_at): self
{
    $this->created_at = $created_at;

    return $this;
}
}

KrediKartiType.php:

$builder
        ->add('kart_adi',TextType::class,['label' => 'Kart Adı','required' => false, 'attr' =>['class' => 'form-control', 'placeholder' => 'Kart Adı']])
        ->add('kart_no',TextType::class,['label' => 'Kart No','required' => false, 'attr' =>['class' => 'form-control', 'placeholder' => 'Kart No']])
        ->add('hesap_kesim_gunu', NumberType::class, [
            'label' => 'Hesap Kesim Tarihi', 'attr' =>['class' => 'form-control', 'placeholder' => 'Hesap Kesim Günü']
        ])
        ->add('aciklama',TextType::class,['label' => 'Açıklama','required' => false, 'attr' =>['class' => 'form-control', 'placeholder' => 'Açıklama']])
        ->add('borc', NumberType::class,
            [
                'label' => 'Borç', 'attr' =>['class' => 'form-control', 'placeholder' => "Borç",'value' => '0.00','style' => 'text-align: right']
            ])
        ->add('alacak', NumberType::class,
            [
                'label' => 'Alacak', 'attr' =>['class' => 'form-control', 'placeholder' => "Alacak",'value' => '0.00','style' => 'text-align: right']
            ])
        ->add('banka',  EntityType::class, array(
            'class' => Banka::class,
            'choice_label'=> 'adi',
            'placeholder' => 'Lütfen bankanızı seçiniz',
            'attr' =>['class' => 'form-control']
        ))

    ;

    $formModifier = function (FormInterface $form, Banka $banka = null) {
        $banka_hesap = null === $banka ? [] : $banka->getBankaHesaplari();

        $form->add('hesap', EntityType::class, [
            'class' => BankaHesap::class,
            'placeholder' => '',
            'choices' => $banka_hesap,
        ]);
    };

    $builder->addEventListener(
        FormEvents::PRE_SET_DATA,
        function (FormEvent $event) use ($formModifier) {
            $data = $event->getData();

            $formModifier($event->getForm(), $data->getBanka());
        }
    );

    $builder->get('banka')->addEventListener(
        FormEvents::POST_SUBMIT,
        function (FormEvent $event) use ($formModifier) {
            $banka = $event->getForm()->getData();

            $formModifier($event->getForm()->getParent(), $banka);
        }
    );

new.html.twig:

<script>
var $banka = $('#kredi_karti_banka');
var $token = $('#kredi_karti__token');
$banka.change(function() {
    var $form = $(this).closest('form');
    var data = {};
    data[$banka.attr('name')] = $banka.val();
    data[$token.attr('name')] = $token.val();
    $.ajax({
    url : $form.attr('action'),
    type: $form.attr('method'),
    data : data,
    complete: function(html) {
    // Replace current position field ...
    $('#kredi_karti_hesap').replaceWith(
    // ... with the returned one from the AJAX response.
    $(html.responseText).find('#meetup_position')
    );
    // Position field now displays the appropriate positions.
    }
    });
});
</script>

I simply want to fetch bank accounts based on the selected bank on bank change.

1 Answers

You could add options to the form when you create it. So you can create an endpoint where you can create the form with the extra options which could be the bank id and then render only the select element you wish, in your case the bank_accounts select. You can call this endpoint on change event via JS to the banks select. Also you need to modify the KrediKartiType so that the bank_accounts select should take the values from a query builder. For Example:

class BankAccountsController extends AbstractController
{
    #[Route('/bank-accounts-select', name: 'bank_accounts_select', methods: ['GET'])]
    public function getBankAccountsSelect(Request $request): Response
    {
        $form = $this->createForm(
            KrediKartiType::class,
            null,
            ['bankId' => $request->query->get('bankId')]
        );

        return $this->render('_bank-accounts-select.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

The form type for the bank_accounts key can look like this:

->add('bank_accounts', EntityType::class, [
      'class' => BankaHesap::class,
      'query_builder' => function (BankaHesapRepository $bankaHesapRepository) use ($options) {
           return $bankaHesapRepository->bankAccountsByBankId((int) $options['bankId']);
       },
       'label_attr' => ['class' => 'mb-0 d-block'],
])

The Repository method to get the bank accounts from bankId:

public function bankAccountsByBankId(?int $bankId): QueryBuilder
{
   return $this->createQueryBuilder('bh')
     ->join('bh.bank', 'b')
     ->where('b.id = :bankId')
     ->setParameter('bankId', $bankId)
     ->orderBy('bh.name', 'ASC') // this is optional :)
   ;
}

You would also need the twig file which renders the select element _bank-accounts-select.html.twig:

{{ form_row(form.bank_accounts, {
    attr: {
        'class': 'mb-3 form-control'
    }
}) }}

At last the Js part, where you need to call the endpoint bank-accounts-select, where you replace the select with the one you get from the endpoint:

$bankSelect.on('change', (event) => {
  const bankId = event.target.value;
  $.ajax({
      url: BASE_URL+'/bank-accounts-select',
      data: {
        bankId: bankId,
      },
      success: function (html) {
        const $bankAccountsSelect = REGION_SELECT_FROM_CLASS_NAME //replace this with your jQuery/Javascript selector;
        $bankAccountsSelect.html($(html).find("option"));
        $bankAccountsSelect.val("").trigger("change");
      },
    });
});

This should do it, And I suggest you use English language on your code ;)

Related