[英]How to change options of select dynamically?
我正在尝试使用 Symfony 创建一个应用程序,该应用程序旨在允许用户通过创建、删除和编辑事务来管理他的预算。 我已经创建了我的项目,以及我使用 Doctrine 的实体,现在一切都很好,该项目与 Crud 和数据库完美配合。
但是,我有一个问题,如下图所示,使用表单创建了一个新事务,其中包含以下输入:
一个名字,一个数量,一个类型和一个类别。 一个类型要么是借方要么是贷方,类别输入代表交易的用途(工资、账单、购物等)
我的问题是我想动态调整类别选择的选项,具体取决于类型选择的值(例如,如果选择贷方,则显示工资,如果是借方,则选项将是账单和购物)。
我知道最好的方法是使用 AJAX,但我在实现它时遇到了一些问题。 实际上,我已经根据为类型选择设置的值开发了对类别选项的改编(如我所愿,它运行良好),但仅在网页加载时。
现在,我想在使用 AJAX 更改时触发相同的事件,这就是我挣扎的地方......我尝试了一些代码,但每次都没有发生任何变化,即使console.log
告诉我代码没有遇到任何问题。 这是我的代码:
模板\事务\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);
}
}
尝试 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 的公共函数 new() 中。
您可以通过创建具有不同路由的单独控制器操作来执行此操作,该路由返回所需的选项。 然后,您的 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));
}
将输入添加到 templates\transaction\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)
}
}
});
});
再会。
提交表单的动态生成
可能出现的另一种情况是,您想要自定义特定于用户提交的数据的表单。 例如,假设您有一个体育聚会的注册表。 一些事件将允许您指定您在该领域的首选位置。 例如,这将是一个选择字段。 然而,可能的选择将取决于每项运动。 足球会有进攻、防守、守门员等……棒球会有投手,但不会有守门员。 您需要正确的选项才能通过验证。
聚会作为实体字段传递给表单。 所以我们可以像这样访问每项运动:
// 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,
));
}
);
}
// ...
}
当您构建此表单以首次显示给用户时,此示例可以完美运行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.