Заполните зависимое поле выбора, используя Symfony & Ajax - PullRequest
0 голосов
/ 12 января 2020

Мне не удалось найти какое-либо решение, соответствующее моему сценарию, поэтому я решил спросить здесь: в основном, мне нужно добиться, чтобы отобразить форму с несколькими полями выбора, а именно Company, ProductsCategory и Products. Поэтому, в зависимости от того, какую категорию выбирает пользователь, я хочу фильтровать и показывать только продукты этой выбранной категории. Я пытался следовать документации Symfony , как указано здесь , но не могу заставить ее работать. поле выбора интерфейсных продуктов остается пустым даже после того, как категория была задана, а также ajax возврат со статусом 500 с ошибкой:

Возвращаемое значение App \ Entity \ ProductsCategory :: getProducts ( ) должен быть экземпляром App \ Entity \ Products или null, возвращен экземпляр Doctrine \ ORM \ PersistentCollection

вот коды: сущность My Exportables

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ExportablesRepository")
 */

class Exportables
{
/**
 * @ORM\Id()
 * @ORM\GeneratedValue()
 * @ORM\Column(type="integer")
 */
private $id;

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\ExportCompany", inversedBy="exportables")
 * @ORM\JoinColumn(nullable=false)
 */
private $company;

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Products", inversedBy="exportables")
 * @ORM\JoinColumn(nullable=false)
 */
private $product;

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

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\ProductsCategory")
 * @ORM\JoinColumn(nullable=false)
 */
private $category;

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

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

public function setCompany(?ExportCompany $company): self
{
    $this->company = $company;

    return $this;
}

public function getProduct(): ?Products
{
    return $this->product;
}

public function setProduct(?Products $product): self
{
    $this->product = $product;

    return $this;
}

public function getIsActive(): ?bool
{
    return $this->isActive;
}

public function setIsActive(bool $isActive): self
{
    $this->isActive = $isActive;

    return $this;
}

public function getCategory(): ?ProductsCategory
{
    return $this->category;
}

public function setCategory(?ProductsCategory $category): self
{
    $this->category = $category;

    return $this;
}
}

сущность ProductsCategory :

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ProductsCategoryRepository")
 */
class ProductsCategory
{
/**
 * @ORM\Id()
 * @ORM\GeneratedValue()
 * @ORM\Column(type="integer")
 */
private $id;

/**
 * @ORM\Column(type="string", length=50)
 */
private $categoryTitle;

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

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

/**
 * @ORM\OneToMany(targetEntity="App\Entity\Products", mappedBy="category", cascade={"persist", "remove"})
 */
private $products;


public function __construct()
{
    $this->product = new ArrayCollection();
}

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

public function getCategoryTitle(): ?string
{
    return $this->categoryTitle;
}

public function setCategoryTitle(string $categoryTitle): self
{
    $this->categoryTitle = $categoryTitle;

    return $this;
}

public function getCategoryDescription(): ?string
{
    return $this->categoryDescription;
}

public function setCategoryDescription(?string $categoryDescription): self
{
    $this->categoryDescription = $categoryDescription;

    return $this;
}

public function getIsActive(): ?bool
{
    return $this->isActive;
}

public function setIsActive(bool $isActive): self
{
    $this->isActive = $isActive;

    return $this;
}

public function getProducts(): ?Products
{
    return $this->products;
}

public function setProducts(Products $products): self
{
    $this->products = $products;

    // set the owning side of the relation if necessary
    if ($products->getCategory() !== $this) {
        $products->setCategory($this);
    }

    return $this;
}

public function __toString()
{
    return $this->categoryTitle;
}
}

Сущность продуктов:

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ProductsRepository")
 */

class Products
{

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

/**
 * @ORM\Column(type="string", length=50)
 */
private $productTitle;

/**
 * @ORM\Column(type="text")
 */
private $productDescription;

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

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\ProductsCategory", inversedBy="products", cascade={"persist", "remove"})
 * @ORM\JoinColumn(nullable=false)
 */
private $category;

/**
 * @ORM\OneToMany(targetEntity="App\Entity\Exportables", mappedBy="product")
 */
private $exportables;

public function __construct()
{
    $this->exportables = new ArrayCollection();
}


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

public function getProductTitle(): ?string
{
    return $this->productTitle;
}

public function setProductTitle(string $productTitle): self
{
    $this->productTitle = $productTitle;

    return $this;
}

public function getProductDescription(): ?string
{
    return $this->productDescription;
}

public function setProductDescription(string $productDescription): self
{
    $this->productDescription = $productDescription;

    return $this;
}

public function getIsActive(): ?bool
{
    return $this->isActive;
}

public function setIsActive(bool $isActive): self
{
    $this->isActive = $isActive;

    return $this;
}

public function getCategory(): ?ProductsCategory
{
    return $this->category;
}

public function setCategory(ProductsCategory $category): self
{
    $this->category = $category;

    return $this;
}

/**
 * @return Collection|Exportables[]
 */
public function getExportables(): Collection
{
    return $this->exportables;
}

public function addExportable(Exportables $exportable): self
{
    if (!$this->exportables->contains($exportable)) {
        $this->exportables[] = $exportable;
        $exportable->setProduct($this);
    }

    return $this;
}

public function removeExportable(Exportables $exportable): self
{
    if ($this->exportables->contains($exportable)) {
        $this->exportables->removeElement($exportable);
        // set the owning side to null (unless already changed)
        if ($exportable->getProduct() === $this) {
            $exportable->setProduct(null);
        }
    }

    return $this;
}

public function __toString(){
    return $this->productTitle;
}
}

Тип экспорта:

namespace App\Form;

use App\Entity\Products;
use App\Entity\Exportables;
use App\Entity\ProductsCategory;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

class ExportablesType extends AbstractType

{

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

    $builder
        ->add('company')
        ->add('category', EntityType::class, array(
            'class' => ProductsCategory::class,
            'placeholder' => 'Select a Category...',
        ))
        ->add('isActive')
    ;

    $formModifier = function (FormInterface $form, ProductsCategory $cat = null) {
        $products = null === $cat ? [] : $cat->getProducts();
        dump($products);

        $form->add('product', EntityType::class, [
            'class' => 'App\Entity\Products',
            'placeholder' => '',
            'choices' => $products,
        ]);
    };

    $builder->addEventListener(
        FormEvents::PRE_SET_DATA,
        function (FormEvent $event) use ($formModifier) {
            // this would be your entity, i.e. SportMeetup
            $data = $event->getData();

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

    $builder->get('category')->addEventListener(
        FormEvents::POST_SUBMIT,
        function (FormEvent $event) use ($formModifier) {
            // It's important here to fetch $event->getForm()->getData(), as
            // $event->getData() will get you the client data (that is, the ID)
            $cat = $event->getForm()->getData();

            // since we've added the listener to the child, we'll have to pass on
            // the parent to the callback functions!
            $formModifier($event->getForm()->getParent(), $cat);
        }
    );

}

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

Экспортер Контроллер:

namespace App\Controller;

use App\Entity\Exportables;
use App\Form\ExportablesType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class ExportablesController extends AbstractController
{

/**
 * @Route("/admin-panel/exportables/new", name="exportables_create")
 * @Route("/admin-panel/exportables/{id}/edit", name="exportables_edit")
 */
public function exportables_create_and_edit(Request $request, EntityManagerInterface $em, Exportables $exportables = null)
{
    if(!$exportables){
        $exportables = new Exportables();
    }
    $form = $this->createForm(ExportablesType::class, $exportables);
    $form->handleRequest($request);

    if($form->isSubmitted() && $form->isValid()){
        $em->persist($exportables);
        $em->flush();
    }
    return $this->render('/admin-panel/exportables_create.html.twig', [
        'exForm' => $form->createView(),
        'editMode' => $exportables->getId() !== null
    ]);
}   
}

Наконец, файл ветки рендеринг формы:

{% extends '/admin-panel/base-admin.html.twig' %}
{%  block body %}
{{ form_start(exForm) }}
<div class="form-group">
    {{ form_row(exForm.company, {'attr':{'class':"form-control"}}) }}
</div>
   <div class="form-group">
       {{ form_row(exForm.category, {'attr':{'class':"form-control"}}) }}
   </div>
{% if exForm.product is defined %}
   <div class="form-group">
       {{ form_row(exForm.product, {'label': "Product..",  'attr':{'class':"form-control"}}) }}
   </div>
{% endif %}

<div class="form-group">
    <div class="custom-control custom-checkbox">
        {{ form_widget(exForm.isActive, {'attr': {'class': "custom-control-input", 'checked': "checked"}}) }}
        <label class="custom-control-label" for="exportables_isActive">Visible on the website?</label>
    </div>
</div>
<button type="submit" class="btn btn-success">Create</button>

 {{ form_end(exForm) }}
{% endblock %}

{% block javascripts %}
<script>
    {# //for some reasons this line doesnt work  $(document).ready(function() { #}
    jQuery(document).ready(function($){
        var $cat = $('#exportables_category');
        // When cat gets selected ...
        $cat.change(function() {
            // ... retrieve the corresponding form.
            var $form = $(this).closest('form');
            // Simulate form data, but only include the selected cat value.
            var data = {};
            data[$cat.attr('name')] = $cat.val();
        console.log("cat val " + $cat.val());
        //console.log($form.attr('method'));
        const url = "{{ path('exportables_create')|escape('js') }}";
        //console.log(data);
        //console.log(url);
        //why undefined?? console.log($form.attr('action'));
            // Submit data via AJAX to the form's action path.
            $.ajax({
                //url : $form.attr('action'),
                url : url,
                type: $form.attr('method'),
                data : data,
                success: function(html) {
                // Replace current position field ...
                $('#exportables_product').replaceWith(
                    // ... with the returned one from the AJAX response.
                    //$(html).find('#exportables_product')
                    array(1,2,3)
                );
                // Position field now displays the appropriate positions.
                }
            });
        });
    });
    </script>
{% endblock %}

Любая помощь с благодарностью.

1 Ответ

1 голос
/ 12 января 2020

Я не потратил время на анализ всего вашего кода, поэтому я не уверен, что это решит всю проблему, но это может помочь.

Полагаю, внутри * 1003 есть что-то странное * класс сущности. Действительно, свойство products аннотировано как отношение OneToMany:

/**
 * @ORM\OneToMany(targetEntity="App\Entity\Products", mappedBy="category", cascade={"persist", "remove"})
 */
private $products;

Это означает, что это свойство относится к коллекции (одна категория может иметь много продуктов). Но методы получения / установки для этого свойства определяются позже, как если бы это было отношение OneToOne:

public function getProducts(): ?Products
{
    return $this->products;
}

public function setProducts(Products $products): self
{
    $this->products = $products;

    // set the owning side of the relation if necessary
    if ($products->getCategory() !== $this) {
        $products->setCategory($this);
    }

    return $this;
}

Похоже, что вы изменили аннотацию без изменения методов получения / установки, которые обычно должны предлагать следующее методы для OneToMany отношения:

public function getProducts(): Collection;
public function addProducts(Product $product): self;
public function removeProducts(Product $product): self;

Последний пункт: вы должны переименовать вашу сущность Products в Product, это значительно улучшит читабельность вашего кода: действительно, сущность Products фактически представляют только один продукт, а не несколько.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...