public-repo/select2

为 Symfony 7.0 和 PHP 8.0+ 支持而创建的 select2entity-bundle 的分支。一个将 Select2 集成到 Symfony 表单上标准实体字段中的 Symfony 扩展包。

安装数: 5,584

依赖者: 0

建议者: 0

安全性: 0

星级: 0

分支: 0

类型:symfony-bundle

dev-main 2024-01-09 09:54 UTC

This package is not auto-updated.

Last update: 2024-09-18 11:27:37 UTC


README

这是 tetranz/select2entity-bundle 的分支,旨在添加对 symfony 7.0 和 php8+ 的完整支持。

例如

  • 参数原生类型声明
  • 返回类型声明

select2entity-bundle

简介

这是一个 Symfony 扩展包,它使流行的 Select2 组件能够作为 Symfony 表单上标准实体字段的可替换组件使用。

它与 Symfony 4 和 5 兼容。对于 Symfony 2 和 3,请使用扩展包的 2.x 版本。对于 Select2 4.0 及以上版本。对于旧版本,请使用扩展包的 1.x 版本(与 Symfony 5 不兼容)。

与标准 Symfony 实体字段(通过 html select 渲染)相比,该扩展包提供的主要功能是通过远程 AJAX 调用来检索列表。这意味着列表的大小几乎可以无限扩展。唯一的限制是数据库查询或远程 Web 服务中检索数据的性能。

它支持单选和复选。如果表单正在编辑一个 Symfony 实体,则这些模式对应于一对多和多对多关系。在复选模式下,大多数人发现 Select2 用户界面比带有 multiple=true 的标准 select 标签更容易使用,后者涉及使用 Ctrl 键等尴尬的操作。

该项目受到 lifo/typeahead-bundle 的启发,它使用 Bootstrap 2 中的 Typeahead 组件来提供类似的功能。Select2Entity 可以在任何可以安装 Select2 的地方使用,包括 Bootstrap 3。

感谢 @ismailbaskin,我们现在有了 Select2 版本 4 兼容性。

截图

这是具有展开的单选字段列表的表单。

Single select example

这是具有展开的复选字段列表的表单。

Multiple select example

安装

必须首先安装并运行 Select2。我希望设置一个演示网站,但我的设置基本上是 BraincraftedBootstrapBundle,为 Bootstrap 3 安装了 Select2。一旦 Braincrafted 扩展包运行正常,我需要的唯一文件是

https://github.com/select2/select2/tree/4.0.0 安装 select2.js 和 select2.css

https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3 安装 select2-bootstrap.css。这将使其适用于 Bootstrap 3。

这些文件位于我其中一个扩展包的 Resources/public/js 和 Resources/public/css 文件夹中,然后包含在我的 main/layout.html.twig 主布局文件中。

或者,可以通过以下两行代码从 CloudFlare CDN 加载 select2.js 和 select2.css 的压缩版本:https://select2.github.io。确保脚本标签在 jQuery 加载之后,这可能是在页面页脚中。

  • tetranz/select2entity-bundle 添加到项目 composer.json 的 "requires" 部分
{
    // ...
    "require": {
        // ...
        "tetranz/select2entity-bundle": "2.*"
    }
}

请注意,此功能仅适用于Select2版本4。如果您使用的是Select2版本3.X,请在composer.json中使用"tetranz/select2entity-bundle": "1.*"

  • 在项目根目录中运行php composer.phar update tetranz/select2entity-bundle
  • 更新您的项目config/bundles.php文件,并将此捆绑包添加到$bundles数组中。
$bundles = [
    // ...
    Tetranz\Select2EntityBundle\TetranzSelect2EntityBundle::class => ['all' => true]
];
  • 更新您的项目config/packages/twig.yaml文件,以提供全局twig表单模板。
twig:
    form_themes:
        - '@TetranzSelect2Entity/Form/fields.html.twig'

* Load the Javascript on the page. The simplest way is to add the following to your layout file. Don't forget to run console assets:install. Alternatively, do something more sophisticated with Assetic.
<script src="{{ asset('bundles/tetranzselect2entity/js/select2entity.js') }}"></script>

如何使用

以下内容适用于Symfony 4。有关Symfony 2/3的配置和使用,请参阅https://github.com/tetranz/select2entity-bundle/tree/v2.1

Select2Entity使用简单。在表单类型类的buildForm方法中,指定Select2EntityType::class作为类型,而不是使用entity:class

以下是一个示例

$builder
   ->add('country', Select2EntityType::class, [
            'multiple' => true,
            'remote_route' => 'tetranz_test_default_countryquery',
            'remote_params' => [], // static route parameters for request->query
            'class' => '\Tetranz\TestBundle\Entity\Country',
            'primary_key' => 'id',
            'text_property' => 'name',
            'minimum_input_length' => 2,
            'page_limit' => 10,
            'allow_clear' => true,
            'delay' => 250,
            'cache' => true,
            'cache_timeout' => 60000, // if 'cache' is true
            'language' => 'en',
            'placeholder' => 'Select a country',
            'query_parameters' => [
                'start' => new \DateTime(),
                'end' => (new \DateTime())->modify('+5d'),
                // any other parameters you want your ajax route request->query to get, that you might want to modify dynamically
            ],
            // 'object_manager' => $objectManager, // inject a custom object / entity manager 
        ])

将此放在包含表单类型类的文件顶部

use Tetranz\Select2EntityBundle\Form\Type\Select2EntityType;

选项

如果未设置,将使用默认值。

  • class是您的实体类。必需
  • primary_key是用于唯一标识实体的属性名称。默认为'id'。
  • text_property这是用于检索现有数据的实体属性。如果省略text_property,则实体将被转换为字符串。这需要它具有__toString()方法。
  • multiple对于多选(多对多)为真。对于单选(多对一)为假。
  • minimum_input_length是需要在键入之前按下的键数,以触发搜索。默认为2。
  • page_limit这作为查询参数传递给远程调用。它旨在用于限制返回列表的大小。默认为10。
  • scroll为真将启用无限滚动。默认为false。
  • allow_clear为真将导致Select2显示一个小的x以清除值。默认为false。
  • allow_add是Select2的添加标签设置的选项数组。仅当表单上的'multiple'为真时才可用。
    • enabled启用允许新标签选项。真或假。默认为False。
    • new_tag_text如果allow_add为真,则显示在实体后面的文本。默认为"(新)"。
    • new_tag_prefix新标签的前缀标识符,默认为"__"。您的实际值开头不得包含这些符号。
    • tag_separators一个JavaScript分隔符数组,用于自动拆分标签。
  • delay按键后触发另一个AJAX请求前的延迟(以毫秒为单位)。默认为250毫秒。
  • placeholder占位符文本。
  • language i18n语言代码。默认为en。
  • theme默认为'default'。
  • cache启用AJAX缓存。结果将针对每个查询的'term'进行缓存。
  • cache_timeout缓存查询的时间(以毫秒为单位)。设置为0将导致缓存从不超时(60000 = 60秒)
  • transformer如果您需要以下描述的灵活性,则提供自定义转换器的完全限定类名。
  • autostart确定是否在文档就绪时自动调用select2 jQuery代码。默认为true,提供正常操作。
  • width如果非空,则设置data-width属性。默认为null。
  • class_type可选值,将被添加到ajax请求作为查询字符串参数。
  • render_html这将渲染返回的['html'下]的结果。

远程查询的url可以通过两种方式之一提供:remote_route是Symfony路由。可以可选地指定remote_params以提供参数。或者,可以使用remote_path直接指定url。

您可以使用query_parameters,当远程_params需要动态更改时。您可以使用$('#elem').data('query-parameters', { / new params / });更改它们。

默认值可以在您的 config/packages/tetranzselect2entity.yaml 文件中按以下格式更改。

tetranz_select2_entity:
    minimum_input_length: 2
    page_limit: 8
    allow_clear: true
    delay: 500
    language: 'fr'
    theme: 'default'
    cache: false
    cache_timeout: 0
    scroll: true
    object_manager: 'manager_alias'
    render_html: true

AJAX 响应

控制器应返回一个以下格式的 JSON 数组。属性必须是 idtext

[
  { id: 1, text: 'Displayed Text 1' },
  { id: 2, text: 'Displayed Text 2' }
]

无限滚动

如果您的结果是通过 Select2 的 "无限滚动" 功能分页的,那么您可以选择继续返回上面显示的相同数组 (为了向后兼容,此包将自动尝试确定是否需要更多结果),或者您可以选择返回下面的对象,以便更精细地控制分页结果。

more 字段应为 true,如果还有更多结果需要加载。

{
  results: [
     { id: 1, text: 'Displayed Text 1' },
     { id: 2, text: 'Displayed Text 2' }
  ],
  more: true
}

获取结果的控制器动作将接收一个参数 page,指示应加载哪个结果页。如果您将滚动设置为 true,则必须在查询中处理页面参数。如果不这样做,会发生奇怪的事情。

自定义选项文本

如果您需要更灵活地显示每个选项的文本,例如显示实体中几个字段的值或显示图像,您可以定义自己的自定义转换器。您的转换器必须实现 DataTransformerInterface。最简单的方法可能是扩展 EntityToPropertyTransformer 或 EntitiesToPropertyTransformer 并重新定义 transform() 方法。这样,您可以返回任何作为 text 的内容,而不仅仅是单个实体属性。

以下是一个返回国家名称和大洲(Country 实体中的两个不同属性)的示例

$builder
    ->add('country', Select2EntityType::class, [
        'multiple' => true,
        'remote_route' => 'tetranz_test_default_countryquery',
        'class' => '\Tetranz\TestBundle\Entity\Country',
        'transformer' => '\Tetranz\TestBundle\Form\DataTransformer\CountryEntitiesToPropertyTransformer',
    ]);

在 transform 中设置数据数组如下

$data[] = array(
    'id' => $country->getId(),
    'text' => $country->getName().' ('.$country->getContinent()->getName().')',
);

您的自定义转换器和相应的 Ajax 控制器应返回以下格式的数组

[ 
    { id: 1, text: 'United Kingdom (Europe)' },
    { id: 1, text: 'China (Asia)' }
]

如果您使用的是 allow_add 选项,并且实体需要除了 text_property 字段以外的其他字段才能有效,您可能需要扩展 EntitiesToPropertyTransformer 来添加缺失的字段、创建 doctrine prePersist 监听器或在表单提交后保存之前在表单视图中添加缺失的数据。

添加新标签

如果您想能够通过 Select2 标签创建新实体,可以使用 allow_add 选项集启用它。

例如

$builder
    ->add('tags', Select2EntityType::class, [
        'remote_route' => 'tetranz_test_tags',
        'class' => '\Tetranz\TestBundle\Entity\PostTags',
        'text_property' => 'name',
        'multiple' => true,
        'allow_add' => [
            'enabled' => true,
            'new_tag_text' => ' (NEW)',
            'new_tag_prefix' => '__',
            'tag_separators' => '[",", " "]'
        ],
    ]);

添加标签时请注意以下几点

  • 您的数据不应有任何可能与 new_tag_prefix 的前缀字符匹配的机会。如果有机会,请将其更改为其他类似 '**' 或 '$$' 的东西。
  • tag_separators 与 Select2 选项相同。它应该是 JavaScript 数组。
  • 如果您想要允许添加的实体除了 text_property 中指定的字段外还有其他必填字段,您必须在表单提交中添加它们或在 doctrine 实体的 prePersist 钩子中添加。
  • 如果您使用 "tags" 通过单行输入模式允许创建新实体,请注意您需要删除空格作为分隔符,否则您将无法在此实体中输入空格字符。
    $builder
      ->add('tags', Select2EntityType::class, [
          ...
          'allow_add' => [
              ...
              'tag_separators' => '[",", ""]' // No Space here
          ],
      ]);
    

在请求中包含其他字段值

如果您需要包含其他字段值,因为选择取决于它,您可以在请求中添加 req_params 选项。键是查询字符串中参数的名称,值是 FormView 中的路径(如果您不知道路径,您可以在模板中执行类似 {{ dump(form) }} 的操作)。

property 选项是指用作标签以及搜索词的您的实体字段。

在回调中,您将获得用于修改结果查询和数据对象的查询构建器(数据可以是简单的请求对象或纯数组。有关详细信息,请参阅 AutocompleteService.php)。

$builder
    ->add('firstName', TextType::class)
        ->add('lastName', TextType::class)
        ->add('state', EntityType::class, array('class' => State::class))
        ->add('county', Select2EntityType::class, [
            'required' => true,
            'multiple' => false,
            'remote_route' => 'ajax_autocomplete',
            'class' => County::class,
            'minimum_input_length' => 0,
            'page_limit' => 10,
            'scroll' => true,
            'allow_clear' => false,
            'req_params' => ['state' => 'parent.children[state]'],
            'property' => 'name',
            'callback'    => function (QueryBuilder $qb, $data) {
                $qb->andWhere('e.state = :state');

                if ($data instanceof Request) {
                    $qb->setParameter('state', $data->get('state'));
                } else {
                    $qb->setParameter('state', $data['state']);
                }

            },
        ])
    ->add('city', Select2EntityType::class, [
        'required' => true,
        'multiple' => false,
        'remote_route' => 'ajax_autocomplete',
        'class' => City::class,
        'minimum_input_length' => 0,
        'page_limit' => 10,
        'scroll' => true,
        'allow_clear' => false,
        'req_params' => ['county' => 'parent.children[county]'],
        'property' => 'name',
        'callback'    => function (QueryBuilder $qb, $data) {
            $qb->andWhere('e.county = :county');

            if ($data instanceof Request) {
                $qb->setParameter('county', $data->get('county'));
            } else {
                $qb->setParameter('county', $data['county']);
            }

        },
    ]);

由于请求的处理通常非常相似,因此您可以使用一个帮助您处理结果的服务。要使用它,只需在您的控制器中添加一个路由即可。

    /**
     * @param Request $request
     *
     * @Route("/autocomplete", name="ajax_autocomplete")
     *
     * @return Response
     */
    public function autocompleteAction(Request $request)
    {
        // Check security etc. if needed
    
        $as = $this->get('tetranz_select2entity.autocomplete_service');

        $result = $as->getAutocompleteResults($request, YourFormType::class);

        return new JsonResponse($result);
    }

模板化

现在已将通用模板添加到包中。如果您需要在选择结果中渲染HTML代码,请将render_html选项设置为true,并在控制器中返回如下数据:

[ 
    { id: 1, text: 'United Kingdom (Europe)', html: '<img src="images/flags/en.png" />' },
    { id: 2, text: 'China (Asia)', html: '<img src="images/flags/ch.png">' }
]
如果您需要进一步模板化,则需要覆盖以下方式中的.select2entity()方法。如果您需要在Select2中使用[模板化](https://select2.org/dropdown#templating),可以考虑以下示例,它显示每个选项旁边的国家国旗。您的自定义转换器应返回如下数据:```javascript [ { id: 1, text: '联合王国(欧洲)', img: 'images/flags/en.png' }, { id: 2, text: '中国(亚洲)', img: 'images/flags/ch.png' } ] ```您需要定义自己的JavaScript函数`select2entityAjax`,该函数扩展了原始的`select2entity`函数,并显示带有图像的自定义模板:```javascript $.fn.select2entityAjax = function(action) { var action = action || {}; var template = function (item) { var img = item.img || null; if (!img) { if (item.element && item.element.dataset.img) { img = item.element.dataset.img; } else { return item.text; } } return $( ' ' + item.text + '' ); }; this.select2entity($.extend(action, { templateResult: template, templateSelection: template })); return this; }; $('.select2entity').select2entityAjax(); ```此脚本将为所有具有`select2entity`类的元素全局添加功能,但如果未传递`img`,它将像原始`select2entity`一样工作。您应该在表单中添加`'autostart' => false`以正确运行JS代码。````php ->add('contry', Select2EntityType::class, [ 'remote_route' => 'country_select2_query', 'autostart' => false, ]) ````您还需要覆盖模板中的以下块:```twig {% block tetranz_select2entity_widget_select_option %} {% endblock %} ```此块向JavaScript函数`select2entityAjax`添加所有必要的附加数据,例如数据属性。在这种情况下,我们传递`data-img`。

主题

Select2支持使用theme选项自定义主题,这样您就可以将Select2样式与您的应用程序的其他部分保持一致。有关Bootstrap4主题,请参阅https://github.com/ttskch/select2-bootstrap4-theme

嵌入集合表单

如果您使用嵌入式集合表单data-prototype在表单中添加新元素,则需要以下JavaScript,它将监听添加的元素.select2entity

$('body').on('click', '[data-prototype]', function(e) {
    $(this).prev().find('.select2entity').last().select2entity();
});