Closed xDeSwa closed 4 days ago
Thank you for this suggestion.
Minor comment: how can it be a bug and ... a missing feature ? :)
You are right, I missed it. I changed it, thank you.
There is something i may not follow...
AutoComplete is not able to create items.. expect maybe tags or scalar/serialized stuff like this, or am i wrong ?
Because if so ... i am not sure we would want to expose a feature that cannot be used/implemented easily by users.
There is something i may not follow...
AutoComplete is not able to create items.. expect maybe tags or scalar/serialized stuff like this, or am i wrong ?
Because if so ... i am not sure we would want to expose a feature that cannot be used/implemented easily by users.
You are right, I was currently using it for tags and separating them with commas (delimiter). Below, I have created a system that can create a new entity, but this may be amateurish, since you are more experienced than me, it can be made easier for users to implement with more proper coding, and this can be explained in the documentation.
The new feature works as follows;
screen-recorder-fri-jun-14-2024-20-35-58.webm
Entities
<?php
# src/Entity/News.php
namespace App\Entity;
use App\Repository\NewsRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: NewsRepository::class)]
class News
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
#[ORM\ManyToOne(inversedBy: 'news')]
private ?Group $membergroup = null;
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getMembergroup(): ?Group
{
return $this->membergroup;
}
public function setMembergroup(?Group $membergroup): static
{
$this->membergroup = $membergroup;
return $this;
}
}
<?php
# src/Entity/Group.php
namespace App\Entity;
use App\Repository\GroupRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: GroupRepository::class)]
#[ORM\Table(name: '`group`')]
class Group
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
/**
* @var Collection<int, News>
*/
#[ORM\OneToMany(mappedBy: 'membergroup', targetEntity: News::class)]
private Collection $news;
public function __construct()
{
$this->news = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
/**
* @return Collection<int, News>
*/
public function getNews(): Collection
{
return $this->news;
}
public function addNews(News $news): static
{
if (!$this->news->contains($news)) {
$this->news->add($news);
$news->setMembergroup($this);
}
return $this;
}
public function removeNews(News $news): static
{
if ($this->news->removeElement($news)) {
// set the owning side to null (unless already changed)
if ($news->getMembergroup() === $this) {
$news->setMembergroup(null);
}
}
return $this;
}
public function __toString() {
return $this->getTitle();
}
}
Create UX Live Component
<?php
# src/Twig/components/News/NewGroup.php
namespace App\Twig\Components\News;
use App\Entity\Group;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent]
class NewGroup
{
use DefaultActionTrait;
#[LiveAction]
public function insert(EntityManagerInterface $entityManager, Request $request): void
{
$data = json_decode($request->request->get('data'), true);
if ($data && $data['args']['newItem']) {
$newgroup = new Group();
$newgroup->setTitle($data['args']['newItem']);
$entityManager->persist($newgroup);
$entityManager->flush();
}
}
}
Create UX Live Component template
templates/components/News/NewGroup.html.twig
<div {{ attributes }}></div>
Create AutoComplete Field for Group entity
<?php
# src/Form/GroupAutocompleteField.php
namespace App\Form;
use App\Entity\Group;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;
#[AsEntityAutocompleteField]
class GroupAutocompleteField extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'class' => Group::class,
'placeholder' => 'Choose a Group',
'tom_select_options' => [
'create' => true,
'createOnBlur' => true,
],
'add_new_item_live_component_name' => 'News:NewGroup', // new Feature
]);
}
public function getParent(): string
{
return BaseEntityAutocompleteType::class;
}
}
Create Form Type for News Entity
<?php
# src/Form/NewsType.php
namespace App\Form;
use App\Entity\News;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class NewsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('membergroup', GroupAutocompleteField::class, [
'label' => 'Group',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => News::class,
]);
}
}
Example Template
<div class="col-3">
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{# Call Live Component in how you want use #}
{# this required for trigger from AutoComplete Stimulus Controller #}
<twig:News:NewGroup></twig:News:NewGroup>
</div>
// vendor/symfony/ux-autocomplete/assets/dist/controller.js
const config = {
render,
plugins,
onItemAdd: (item) => {
// Magic Area -> Trigger UX Live Component with item data
if (this.addNewItemLiveComponentNameValue) {
let live_component = document.querySelector(`[data-live-name-value="${this.addNewItemLiveComponentNameValue}"]`);
if (live_component !== undefined && live_component !== null) {
const component = document.getElementById(live_component.id).__component;
component.action('insert', {newItem: item})
}
}
this.tomSelect.setTextboxValue('');
},
closeAfterSelect: true,
};
// vendor/symfony/ux-autocomplete/assets/dist/controller.js
default_1.values = {
url: String,
optionsAsHtml: Boolean,
optionCreateText: String,
loadingMoreText: String,
noResultsFoundText: String,
noMoreResultsText: String,
minCharacters: Number,
tomSelectOptions: Object,
preload: String,
addNewItemLiveComponentName: String, // New Feature
};
Symfony UX Form Type Modification
# vendor/symfony/ux-autocomplete/src/Form/AutocompleteChoiceTypeExtension.php
<?php
# add in finishView function
if ($options['add_new_item_live_component_name']) {
$values['add-new-item-live-component-name'] = $options['add_new_item_live_component_name'];
}
# add setDefaults in configureOptions function
'add_new_item_live_component_name' => null,
Thank you for taking the time to explain and share @xDeSwa
However, I fear this somewhat proves my point: to enable some "add" features, users still need to write some code (not complex, but very use-case dependent).
In this context, adding a translation message won't be the most challenging part. Plus, TomSelect options can be easily updated or overwritten using the Stimulus Controller.
Conversely, some users might not understand why they cannot automatically add a new entity, especially if there are related messages in the translation messages.
Let's wait other opinions :)
Missing 'Add' label Internationalization AutoComplete (Tom Select) when item add allowable