如何动态更改选择的选项?

发布于 2025-02-12 10:43:07 字数 8439 浏览 1 评论 0原文

我正在尝试与Symfony创建一个应用程序,该应用程序旨在通过创建,删除和编辑交易来允许用户管理其预算。我已经创建了我的项目,也是我的学说的实体,现在一切都很好,该项目与Crud和Database完美搭配。

但是,我有一个问题,正如您可以看到在以下图片上用形式创建,并具有以下输入:

a 名称,a type 和a 类别。一种类型是借方或信用,类别输入代表交易的使用(工资,账单,购物等)。

我的问题是我想动态地调整类别的选项,取决于选择类型的价值(例如,如果信用额定信用,则显示薪水,如果是借方,则选项将是账单和购物)。

我知道最好的方法是使用Ajax,但是我在实施它时遇到了一些问题。的确,我已经开发了针对为类型选择的值(我希望它运行良好的值)的类别选项的改编,但仅在网页的负载上。

现在,我想触发与Ajax更改的同一事件,这是我挣扎的地方...我尝试了一些代码,但是每次都没有发生任何更改,即使console.log < /代码>向我显示代码不会遇到任何问题。这是我的代码:

模板\ trassaction \ new.html.twig

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

{% block title %}New Transaction{% endblock %}

{% block body %}
    <h1>Create new Transaction</h1>

    {{ form(form)}}

    <button type="submit" class="btn" formonvalidate>Valider</button>

    <a href="{{ path('app_transaction_index') }}">back to list</a>
{% endblock %}

src \ src \ repository \ categoryrepository.php

<?php

namespace App\Repository;

use App\Entity\Category;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use App\Entity\Type;

/**
 * @extends ServiceEntityRepository<Category>
 *
 * @method Category|null find($id, $lockMode = null, $lockVersion = null)
 * @method Category|null findOneBy(array $criteria, array $orderBy = null)
 * @method Category[]    findAll()
 * @method Category[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class CategoryRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Category::class);
    }

    public function add(Category $entity, bool $flush = false): void
    {
        $this->getEntityManager()->persist($entity);

        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }

    public function remove(Category $entity, bool $flush = false): void
    {
        $this->getEntityManager()->remove($entity);

        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }

    public function findByTypeOrderedByAscName(Type $type): array
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.type = :type')
            ->setParameter('type', $type)
            ->orderBy('c.title', 'ASC')
            ->getQuery()
            ->getResult()
        ;
    }
}

src \ form \ form \ formAtionType.php

<?php

namespace App\Form;

use App\Entity\Transaction;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use App\Repository\TypeRepository;
use App\Repository\CategoryRepository;

class TransactionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name')
            ->add('montant')
            ->add('type')
            ->add('category')
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Transaction::class,
        ]);
    }
}

<强> src \ Controller \ TransActionController.php

<?php

namespace App\Controller;

use App\Entity\Transaction;
use App\Form\TransactionType;
use App\Repository\TransactionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use App\Repository\TypeRepository;
use App\Repository\CategoryRepository;
use Symfony\Component\Validator\Constraints\NotBlank;

#[Route('/transaction')]
class TransactionController extends AbstractController
{
    #[Route('/', name: 'app_transaction_index', methods: ['GET'])]
    public function index(TransactionRepository $transactionRepository): Response
    {
        return $this->render('transaction/index.html.twig', [
            'transactions' => $transactionRepository->findAll(),
        ]);
    }

    #[Route('/new', name: 'app_transaction_new', methods: ['GET', 'POST'])]
    public function new(Request $request, TypeRepository $typeRepository, CategoryRepository $categoryRepository): Response
    {
        $form = $this->createFormBuilder(['type' => $typeRepository->find(0)])
            ->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($categoryRepository) {
                $type = $event->getData()['type'] ?? null;

                $categories = $type === null ? [] : $categoryRepository->findByTypeOrderedByAscName($type);

                $event->getForm()->add('category', EntityType::class, [
                    'class' => 'App\Entity\Category',
                    'choice_label' => 'title',
                    'choices' => $categories,
                    'disabled' => $type === null,
                    'placeholder' => "Sélectionnez une catégorie",
                    'constraints' => new NotBlank(['message' => 'Sélectionnez une catégorie'])
                ]);
            })
            ->add('name')
            ->add('montant')
            ->add('type', EntityType::class, [
                'class' => 'App\Entity\Type',
                'choice_label' => 'title',
                'placeholder' => "Sélectionnez un type",
                'constraints' => new NotBlank(['message' => 'Sélectionnez un type'])
            ])
            ->getForm();

        return $this->renderForm('transaction/new.html.twig', compact('form'));
    }

    #[Route('/{id}', name: 'app_transaction_show', methods: ['GET'])]
    public function show(Transaction $transaction): Response
    {
        return $this->render('transaction/show.html.twig', [
            'transaction' => $transaction,
        ]);
    }

    #[Route('/{id}/edit', name: 'app_transaction_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Transaction $transaction, TransactionRepository $transactionRepository): Response
    {
        $form = $this->createForm(TransactionType::class, $transaction);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $transactionRepository->add($transaction, true);

            return $this->redirectToRoute('app_transaction_index', [], Response::HTTP_SEE_OTHER);
        }

        return $this->renderForm('transaction/edit.html.twig', [
            'transaction' => $transaction,
            'form' => $form,
        ]);
    }

    #[Route('/{id}', name: 'app_transaction_delete', methods: ['POST'])]
    public function delete(Request $request, Transaction $transaction, TransactionRepository $transactionRepository): Response
    {
        if ($this->isCsrfTokenValid('delete'.$transaction->getId(), $request->request->get('_token'))) {
            $transactionRepository->remove($transaction, true);
        }

        return $this->redirectToRoute('app_transaction_index', [], Response::HTTP_SEE_OTHER);
    }
}

尝试AJAX

$(document).on('change', '#form_type', function() {
    const $type = $('#form_type');
    const $form = $(this).closest('form');
    $.ajax({
        url: $form.attr('action'),
        type: $form.attr('method'),
        data: $form.serializeArray(),
        success: function (html) {
            $('#form_category').replaceWith($(html).find('#form_category'));
        }
    });
});

PS:选择类型选择的占位符时,我也希望将类别选择被禁用,并且在使用类型选择的类型选择值时,将启用类别选择。

主要代码在transactioncontroller.php。的公共函数中

I'm trying to create an application with Symfony, which aims to allow a user to manage his budget, by creating, removing and editing transactions. I have created my project, and also my entities with Doctrine, everything is well for now, the project perfectly works with Crud and database.

But, I have a problem, as you can see on the following picture, a new transaction is created with a form, with the following inputs:

a name, an amount, a type and a category. A type is either a debit or a credit, and the category input represents the usage of the transaction (salary, bills, shopping, etc.)

My problem is that I would like to adapt the options of the Category select dynamically, depending on the value of the Type select (for example, if credit is chosed, it shows salary, and if it's debit, then the options will be bills and shopping).

I know that the best way to proceed is to use AJAX, but I have some problems implementing it. Indeed, I already developed the adaptation of the Category options depeding on the value setted for the Type select (it works well, as I wish), but only on load of the webpage.

Now, I would like to trigger this same event on change with AJAX, and this is where I struggle... I tried some codes, but every time, there is no change that is happening, even if console.log shows me that the code doesn't encounter any issue. Here is my code:

templates\transaction\new.html.twig

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

{% block title %}New Transaction{% endblock %}

{% block body %}
    <h1>Create new Transaction</h1>

    {{ form(form)}}

    <button type="submit" class="btn" formonvalidate>Valider</button>

    <a href="{{ path('app_transaction_index') }}">back to list</a>
{% endblock %}

src\Repository\CategoryRepository.php

<?php

namespace App\Repository;

use App\Entity\Category;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use App\Entity\Type;

/**
 * @extends ServiceEntityRepository<Category>
 *
 * @method Category|null find($id, $lockMode = null, $lockVersion = null)
 * @method Category|null findOneBy(array $criteria, array $orderBy = null)
 * @method Category[]    findAll()
 * @method Category[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class CategoryRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Category::class);
    }

    public function add(Category $entity, bool $flush = false): void
    {
        $this->getEntityManager()->persist($entity);

        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }

    public function remove(Category $entity, bool $flush = false): void
    {
        $this->getEntityManager()->remove($entity);

        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }

    public function findByTypeOrderedByAscName(Type $type): array
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.type = :type')
            ->setParameter('type', $type)
            ->orderBy('c.title', 'ASC')
            ->getQuery()
            ->getResult()
        ;
    }
}

src\Form\TransactionType.php

<?php

namespace App\Form;

use App\Entity\Transaction;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use App\Repository\TypeRepository;
use App\Repository\CategoryRepository;

class TransactionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name')
            ->add('montant')
            ->add('type')
            ->add('category')
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Transaction::class,
        ]);
    }
}

src\Controller\TransactionController.php

<?php

namespace App\Controller;

use App\Entity\Transaction;
use App\Form\TransactionType;
use App\Repository\TransactionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use App\Repository\TypeRepository;
use App\Repository\CategoryRepository;
use Symfony\Component\Validator\Constraints\NotBlank;

#[Route('/transaction')]
class TransactionController extends AbstractController
{
    #[Route('/', name: 'app_transaction_index', methods: ['GET'])]
    public function index(TransactionRepository $transactionRepository): Response
    {
        return $this->render('transaction/index.html.twig', [
            'transactions' => $transactionRepository->findAll(),
        ]);
    }

    #[Route('/new', name: 'app_transaction_new', methods: ['GET', 'POST'])]
    public function new(Request $request, TypeRepository $typeRepository, CategoryRepository $categoryRepository): Response
    {
        $form = $this->createFormBuilder(['type' => $typeRepository->find(0)])
            ->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($categoryRepository) {
                $type = $event->getData()['type'] ?? null;

                $categories = $type === null ? [] : $categoryRepository->findByTypeOrderedByAscName($type);

                $event->getForm()->add('category', EntityType::class, [
                    'class' => 'App\Entity\Category',
                    'choice_label' => 'title',
                    'choices' => $categories,
                    'disabled' => $type === null,
                    'placeholder' => "Sélectionnez une catégorie",
                    'constraints' => new NotBlank(['message' => 'Sélectionnez une catégorie'])
                ]);
            })
            ->add('name')
            ->add('montant')
            ->add('type', EntityType::class, [
                'class' => 'App\Entity\Type',
                'choice_label' => 'title',
                'placeholder' => "Sélectionnez un type",
                'constraints' => new NotBlank(['message' => 'Sélectionnez un type'])
            ])
            ->getForm();

        return $this->renderForm('transaction/new.html.twig', compact('form'));
    }

    #[Route('/{id}', name: 'app_transaction_show', methods: ['GET'])]
    public function show(Transaction $transaction): Response
    {
        return $this->render('transaction/show.html.twig', [
            'transaction' => $transaction,
        ]);
    }

    #[Route('/{id}/edit', name: 'app_transaction_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Transaction $transaction, TransactionRepository $transactionRepository): Response
    {
        $form = $this->createForm(TransactionType::class, $transaction);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $transactionRepository->add($transaction, true);

            return $this->redirectToRoute('app_transaction_index', [], Response::HTTP_SEE_OTHER);
        }

        return $this->renderForm('transaction/edit.html.twig', [
            'transaction' => $transaction,
            'form' => $form,
        ]);
    }

    #[Route('/{id}', name: 'app_transaction_delete', methods: ['POST'])]
    public function delete(Request $request, Transaction $transaction, TransactionRepository $transactionRepository): Response
    {
        if ($this->isCsrfTokenValid('delete'.$transaction->getId(), $request->request->get('_token'))) {
            $transactionRepository->remove($transaction, true);
        }

        return $this->redirectToRoute('app_transaction_index', [], Response::HTTP_SEE_OTHER);
    }
}

Attempt for AJAX

$(document).on('change', '#form_type', function() {
    const $type = $('#form_type');
    const $form = $(this).closest('form');
    $.ajax({
        url: $form.attr('action'),
        type: $form.attr('method'),
        data: $form.serializeArray(),
        success: function (html) {
            $('#form_category').replaceWith($(html).find('#form_category'));
        }
    });
});

PS: I would also like the Category select to be disabled when the Type select's placeholder is selected, and the Category select to be enabled when a value is selected with the Type select.

The main code is in the public function new() of TransactionController.php.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

阳光的暖冬 2025-02-19 10:43:07

您可以通过使用不同的路线创建单独的控制器操作来做到这一点,从而返回所需的选项。然后,您的JavaScript将使用返回的数据以表格中重新填充选项。

注意:

未测试,需要更多的工作! (可能包含语法错误,参考错误的属性名称等)调整此样本流以满足您的需求。

添加以下内容到src \ controller \ transactioncontroller.php

use Symfony\Component\HttpFoundation\JsonResponse;
#[Route('/type/options', name: 'app_transaction_type_options', methods: ['POST'])]
public function new(Request $request, TypeRepository $typeRepository, CategoryRepository $categoryRepository): Response
{
  // TODO: get the Type entity from post data
  // $type = $typeRepository->findOneBy...(
  return new JsonResponse($categoryRepository->findByTypeOrderedByAscName($type));
}

添加输入\ trasmplates \ trassaction \ new.html.twig从中获得道路。

    <input type="hidden" id="type_options_path" value="{{ path('app_transaction_type_options') }}">

然后,您的JavaScript看起来像这样:

$(document).on('change', '#form_type', function() {
    const $type = $('#form_type');
    $.ajax({
        url: $('#type_options_path').val(),
        type: 'POST',
        data: {type: $type.val()},
        success: function (data) {
            const $category = $('#form_category');
            $category.find('option').detach();
            const opts = JSON.parse(data)
            for(const opt of opts){
              $(`<option value="${opt.id}">${opt.name}</option>`).appendTo($category)
            }
        }
    });
});

You can do this by creating a separate controller action with a different route, which returns the desired options. Your JavaScript would then use the returned data to repopulate the options in the form.

Note:

NOT TESTED, and needs more work! (may contain syntax errors, reference incorrect property names etc.) Adapt this sample flow to fit your needs.

Add the following to src\Controller\TransactionController.php

use Symfony\Component\HttpFoundation\JsonResponse;
#[Route('/type/options', name: 'app_transaction_type_options', methods: ['POST'])]
public function new(Request $request, TypeRepository $typeRepository, CategoryRepository $categoryRepository): Response
{
  // TODO: get the Type entity from post data
  // $type = $typeRepository->findOneBy...(
  return new JsonResponse($categoryRepository->findByTypeOrderedByAscName($type));
}

Add an input to templates\transaction\new.html.twig to get the path from.

    <input type="hidden" id="type_options_path" value="{{ path('app_transaction_type_options') }}">

Then your JavaScript would look something like this:

$(document).on('change', '#form_type', function() {
    const $type = $('#form_type');
    $.ajax({
        url: $('#type_options_path').val(),
        type: 'POST',
        data: {type: $type.val()},
        success: function (data) {
            const $category = $('#form_category');
            $category.find('option').detach();
            const opts = JSON.parse(data)
            for(const opt of opts){
              $(`<option value="${opt.id}">${opt.name}</option>`).appendTo($category)
            }
        }
    });
});
浅黛梨妆こ 2025-02-19 10:43:07

再会。

提交表格的动态生成

另一种可能出现的情况是您要自定义用户提交的数据的特定表格。例如,想象一下您有一个体育聚会的注册表格。一些事件将使您可以在现场指定自己的首选位置。例如,这将是一个选择字段。但是,可能的选择将取决于每项运动。足球将有进攻,防守,门将等...棒球将有投手,但不会有守门员。您将需要正确的选项才能通过验证。

聚会作为实体字段通过表格。因此,我们可以访问这样的每项运动:

// src/AppBundle/Form/Type/SportMeetupType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
// ...

class SportMeetupType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('sport', 'entity', array(
                'class'       => 'AppBundle:Sport',
                'empty_value' => '',
            ))
        ;

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) {
                $form = $event->getForm();

                // this would be your entity, i.e. SportMeetup
                $data = $event->getData();

                $sport = $data->getSport();
                $positions = null === $sport ? array() : $sport->getAvailablePositions();

                $form->add('position', 'entity', array(
                    'class'       => 'AppBundle:Position',
                    'empty_value' => '',
                    'choices'     => $positions,
                ));
            }
        );
    }

    // ...
}

当您构建此表格以首次向用户显示时,此示例可以很好地工作。

详细信息在- https://symfony-docs-zh-cn.readthedocs.io/cookbook/form/dynamic_form_modification.html#cookbookbook-form-event-form-events-subnited-data

Good day.

Dynamic Generation for Submitted Forms

Another case that can appear is that you want to customize the form specific to the data that was submitted by the user. For example, imagine you have a registration form for sports gatherings. Some events will allow you to specify your preferred position on the field. This would be a choice field for example. However the possible choices will depend on each sport. Football will have attack, defense, goalkeeper etc... Baseball will have a pitcher but will not have a goalkeeper. You will need the correct options in order for validation to pass.

The meetup is passed as an entity field to the form. So we can access each sport like this:

// src/AppBundle/Form/Type/SportMeetupType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
// ...

class SportMeetupType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('sport', 'entity', array(
                'class'       => 'AppBundle:Sport',
                'empty_value' => '',
            ))
        ;

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) {
                $form = $event->getForm();

                // this would be your entity, i.e. SportMeetup
                $data = $event->getData();

                $sport = $data->getSport();
                $positions = null === $sport ? array() : $sport->getAvailablePositions();

                $form->add('position', 'entity', array(
                    'class'       => 'AppBundle:Position',
                    'empty_value' => '',
                    'choices'     => $positions,
                ));
            }
        );
    }

    // ...
}

When you’re building this form to display to the user for the first time, then this example works perfectly.

Details are on - https://symfony-docs-zh-cn.readthedocs.io/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文