Когда я сталкиваюсь с такой проблемой, я использую другой подход.
Если опция выбора будет иметь более 20 записей, поэтому я изменяю ее на текст ввода с автозаполнением.
Шаги:
Установите хорошую Javascript lib для автозаполнения, например jquery-typeahead
Мне нравится использовать Wepack Encore в Symfony. С Webpack, Npm и Yarn установка проста, как
yarn add jquery-typeahead --dev
Вам потребуется запустить yarn run encore dev
после установки.
Создайте новый FieldType для вашей формы, чтобы заменить EntityType
Предположим, что нам нужно создать регистрационную форму с полем city . Поведение по умолчанию будет использовать EntityType и покажет вариант выбора со всеми городами.
Чтобы изменить его на автозаполнение, давайте создадим еще один FieldType.
<?php
// src/Form/Type/AutocompleteCityType.php
namespace App\Form\Type;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AutocompleteCityType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'attr' => ['autocomplete' => 'off']
));
}
public function getParent()
{
return SearchType::class;
}
}
ПРИМЕЧАНИЕ. В приведенном выше коде я расширяю SearchType :: class , который является поиском типа ввода (HTML 5).
Наш новый тип поля можно использовать в наших формах, но это просто другое строковое поле. Не будет работать правильно, чтобы заменить EntityType. Нам нужно преобразовать эту строку в сущность. Итак, нам нужен DataTransformer
Создание города для строки DataTransformer
<?php
// src/Form/DataTransformer/CityToStringTransformer.php
namespace App\Form\DataTransformer;
use App\Entity\City; // Pay attention to use your Entities correctly
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class CityToStringTransformer implements DataTransformerInterface
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Transforms an object (City) to a string.
*
* @param City|null $city
* @return string
*/
public function transform($city)
{
if (null === $city) {
return '';
}
return $city->getSomethingUnique();
}
/**
* Transforms a string to an object (city).
*
* @param string $somethingUnique
* @return City|null
* @throws TransformationFailedException if object (city) is not found.
*/
public function reverseTransform($somethingUnique)
{
// empty City? It's optional, so that's ok
if (!$somethingUnique) {
return;
}
$city = $this->entityManager
->getRepository(City::class)
->findByThatSomethingUnique($somethingUnique);
if (null === $city) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf(
'The city "%s" cannot be found!',
$somethingUnique
));
}
return $city;
}
}
Примечание: строка должна быть своего рода уникальным ключом для правильной работы и должна хорошо отображаться при автозаполнении и заполнении поля (например, [CityCode] CityName ). DataTransformation не может возвращать более одного результата для метода findByThatSomethingUnique()
.
Примечание 2: Например, $city->getSomethingUnique()
не может быть $city->getId()."-".$city->getName()
, а ->findByThatSomethingUnique($somethingUnique)
может быть ->findOneById(explode("-", $somethingUnique)[0])
Почти готово. Мы можем использовать оба класса в нашем FormType для замены EntityType.
Использование в FormType
// src/Form/MyFormType.php
// (...) Other declarations(...)
use App\Form\DataTransformer\ContactToStringTransformer;
use App\Form\Type\AutocompleteContactType;
class MyFormType extends AbstractType
{
private $cityTransformer;
public function __construct(CityToStringTransformer $cityTransformer)
{
$this->cityTransformer = $cityTransformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/* @var $myEntity MyEntity */
$myEntity = $builder->getData();
$builder
->add('city', AutocompleteCityType::class, [
'label' => 'Custom City Label',
])
// (...) Other fields (...)
;
$builder->get('city')
->addModelTransformer($this->cityTransformer);
}
С указанным здесь кодом форма будет отображаться правильно, но заголовок должен быть правильно настроен.
Вы можете создать новый тип блока веток для этого нового типа поля. Код ниже должен находиться в вашей пользовательской form_theme
Блок Twig
{% block autocomplete_city_widget %}
{% spaceless %}
<div class="typeahead__container">
<div class="typeahead__field">
<div class="typeahead__query">
{{ form_widget(form) }}
</div>
</div>
</div>
{% endspaceless %}
{% endblock %}
ПРИМЕЧАНИЕ. Приведенный выше код Twig относится к jquery-typeahead и работает только с полем AutocompleteCityType. Если вы устанавливаете другую библиотеку или изменяете имя класса FieldType, измените его правильно. Также обратите внимание на имена форм, которые изменяют имя блока для визуализации.
Последнее, что нужно написать javascript для typeahead, получить записи.
Javascript Typeahead
jQuery.typeahead({
input: "#myForm_city", // Related to FormName and Autocomplete Field Name
minLength: 1,
maxItem: 20,
order: "asc",
dynamic: true,
delay: 500,
backdrop: { "background-color": "#eeeeee" },
template: "<small style='color:#999;'>{{ '[{{citycode}}] {{cityname}}' }}</small>", // remember that this is a Twig template...
emptyTemplate: "No results for typed string",
source: {
city: {
display: ["citycode", "cityname"],
ajax: function (query) {
return {
type: "POST",
url: '{{ path('controller_with_city_list_json_response') }}',
path: "city",
data: {
"q": "{{ '{{query}}' }}",
"length" : "40",
},
callback: {
done: function (res) {
var d = {};
d.city = [];
jQuery(res.data).each(function(index, value) {
d.city.push(value.city);
});
return d;
}
}
}
}
}
},
callback: {
onClickAfter: function (node, a, item, event) {
event.preventDefault();
jQuery(node).val("[" + item.citycode + "] " + item.cityname);
}
},
debug: false
});
ПРИМЕЧАНИЕ. Приведенному выше коду Typeahead требуется ответ json в формате
{"data":
[
{"city":
{"cityname":"City Name X", "citycode": "NXNX"}
},
{"city":
{"cityname":"City Name Y", "citycode": "NYNY"}
}
]
}