adt / ajax-select
Nette 扩展,用于 AJAX 选择控件。
Requires
- php: >=7.1
- adt/base-query: ^1.7
- nette/application: *
- nette/di: ^3.0
- nette/forms: ^3.0
- nette/utils: ^3.0 | ^4.0
This package is auto-updated.
Last update: 2024-09-14 20:09:25 UTC
README
安装
-
通过 composer 安装
composer require adt/ajax-select
-
在您的 config.neon 中注册此扩展
extensions: - ADT\Components\AjaxSelect\DI\AjaxSelectExtension
-
在您的
BasePresenter
中包含AjaxServiceSignalTrait
class BasePresenter extends ... { use \ADT\Components\AjaxSelect\Traits\AjaxServiceSignalTrait;
-
将
assets/ajax-select.js
包含到您的前端构建中。<script type="text/javascript" src="vendor/ajax-select.min.js"></script>
-
创建您的第一个 AjaxEntity。
-
将此实体用于您的第一个 AjaxSelect 控件。
-
完成。
它是做什么的?
此扩展向 Nette\Forms\Container
和因此向所有派生类添加以下方法
addDynamicSelect($name, $title, $items, $itemFactory = null, $config = [])
- 单值动态选择
$itemFactory
:function (array $invalidValues, DynamicSelect $input)
addDynamicMultiSelect($name, $title, $items, $itemFactory = null, $config = [])
- 多值动态选择
$itemFactory
: 请参阅addDynamicSelect
addAjaxSelect($name, $title, $entityName = $name, $entitySetupCallback = NULL, $config = [])
- 单值 AJAX 选择
addAjaxMultiSelect($name, $title, $entityName = $name, $entitySetupCallback = NULL, $config = [])
- 多值 AJAX 选择
配置
[ AjaxSelectExtension::CONFIG_INVALID_VALUE_MODE => AjaxSelectExtension::INVALID_VALUE_MODE_*, AjaxSelectExtension::CONFIG_OR_BY_ID_FILTER => TRUE, AjaxSelectExtension::CONFIG_TRANSLATOR => TRUE, ]
AjaxSelectExtension::CONFIG_TRANSLATOR
: 设置选择值的自动翻译开启/关闭。默认为 TRUE
。
动态选择
此控件允许将未知值传递到 $control->value
字段。这样做将调用控件的 $itemFactory
仅带一个参数 - 无效值。
项目工厂可以返回给定值的标题或空值(NULL
,空字符串,零等)。非空值将自动追加到已知的有效值列表中。
DynamicSelect 接受数组或 \Kdyby\Doctrine\QueryObject
,它扩展了 \ADT\BaseQuery\BaseQuery
中的 $items
。
如果传递了 QO,则实现以下功能
- 函数
\ADT\BaseQuery\BaseQuery::callSelectPairsAuto()
定义是否应自动调用\ADT\BaseQuery\BaseQuery::selectPairs()
函数。selectPairs
将实体属性设置为选择键和值。\ADT\BaseQuery\BaseQuery
中的默认值是SELECT_PAIRS_KEY
= 'id' 和SELECT_PAIRS_VALUE
= null,它返回整个对象,因此您应该根据需要覆盖值常量,例如到name
。在调用selectPairs
函数或覆盖常量时,您还可以使用实体获取器名称,它返回更复杂的值。例如nameWithEmail
,然后从实体对象调用函数getNameWithEmail
。- 当在 QO 中定义自定义获取函数时,应扩展
callSelectPairsAuto
函数,并且我们继续从\ADT\BaseQuery\BaseQuery
的默认获取函数调用中获取数据。请参阅下面 DynamicSelect 中的函数示例。
- 在
\Kdyby\DoctrineForms\EntityForm
中的实体可以设置不活动的默认值,这会导致选中项中不允许错误。因此,调用\ADT\BaseQuery\BaseQuery::orById()
函数,将该不活动值设置为项。AjaxSelectExtension::CONFIG_OR_BY_ID_FILTER
可以关闭此默认调用。- 对于未由处理实体映射的属性,必须关闭此函数。例如,如果我们处于
UserForm
中。实体User
有属性id
、name
和role
。在UserForm
中,我们可以为role
属性创建动态选择,其中 orByIdFilter 已开启。但如果我们为自定义项如profession
创建动态选择,或ByIdFilter 必须关闭,因为它是User
实体的非映射属性。
Ajax 选择
此控件需要我们称之为AjaxEntity的东西及其工厂。所有用户AjaxEntity都必须从我们的AbstractEntity
或AggregateEntity
派生。
- 如果使用
AbstractEntity
,则必须实现以下功能createQueryObject
:返回特定实体创建的查询对象,该实体继承自\ADT\BaseQuery\BaseQuery
。filterQueryObject
:在此函数中,您将调用来自您的QO的所有过滤器函数。formatValues
:在此处,您从QO获取过滤后的数据,将其格式化为所需的格式。
此AjaxEntity封装了$itemFactory
的行为,但它可以变得更强大。
AjaxSelect还使用orByIdFilter,请参阅Dynamic Select
。
配置
实现AjaxEntity
首先,创建一个新的类(例如,UserAjaxEntity
),它从我们的\ADT\Components\AjaxSelect\Entities\AbstractEntity
派生。
\ADT\Components\AjaxSelect\Entities\AbstractEntity
需要实现一些函数。下面是示例。
此外,我们还需要其工厂,因此也要创建一个接口(例如,IUserAjaxEntityFactory
)。
示例
namespace App\Model\Ajax; interface IUserAjaxEntityFactory { /** @return UserAjaxEntity */ function create(); } class UserAjaxEntity extends \ADT\Components\AjaxSelect\Entities\AbstractEntity { const OPTION_OR_BY_ID = 'orById'; const OPTION_BY_ID = 'byId'; const OPTION_ACTIVE = 'active'; /** @var \Kdyby\Doctrine\EntityManager */ protected $em; /** @var \App\Queries\IUserFactory This object is defined below in DynamicSelect implementation */ protected $userQueryFactory; public function __construct(\Kdyby\Doctrine\EntityManager $em, \App\Queries\IUserFactory $userQueryFactory) { $this->em = $em; $this->userQueryFactory = $userQueryFactory; } /** * @param int|int[] $id * @return $this */ public function orById($id) { return $this->set(static::OPTION_OR_BY_ID, $id); } /** * @param int|int[] $id * @return $this */ public function byId($id) { return $this->set(static::OPTION_BY_ID, $id); } public function active($bool) { // if $bool is TRUE, only active users are shown, // otherwise, only inactive are shown return $this->set(self::OPTION_ACTIVE, $bool); } /** * This function is required by \ADT\Components\AjaxSelect\Entities\AbstractEntity * @param array $values * @return array */ public function formatValues($value) { // TODO return array of userId => userName } /** * This function is required by \ADT\Components\AjaxSelect\Entities\AbstractEntity * @internal * @return Queries\User */ protected function createQueryObject() { return $this->userQueryFactory->create(); } /** * This function is required by \ADT\Components\AjaxSelect\Entities\AbstractEntity * @internal * @param Queries\User $query */ protected function filterQueryObject(&$query) { if ($value = $this->get(static::OPTION_OR_BY_ID)) { $query->orById($value); } if ($value = $this->get(static::OPTION_BY_ID)) { $query->byId($value); } if ($value = $this->get(static::OPTION_ACTIVE)) { $query->byActive($value); } } }
然后,在您的config.neon
的services
部分注册此实体及其工厂
services: - create: \App\Model\Ajax\UserAjaxEntity implement: \App\Model\Ajax\IUserAjaxEntityFactory tags: [ajax-select.entity-factory]
这告诉Nette自动实现您的实体的工厂,并将其标记为ajax-select.entity-factory
。AjaxSelect现在知道您的实体了。
现在,您可以从Nette表单中的AjaxSelect控件直接使用您的AjaxEntity
$form->addAjaxSelect('user', 'Active users with default user', function (UserAjaxEntity $ajaxEntity) { $ajaxEntity ->active(TRUE); }) ->setRequired(TRUE); $form->addAjaxSelect('inactiveUser', 'Inactive users without default user', 'user', function (UserAjaxEntity $ajaxEntity) { $ajaxEntity ->active(FALSE); }, [ AjaxSelectExtension::CONFIG_OR_BY_ID_FILTER => FALSE ]);
参数$entityName
和/或$entitySetupCallback
可以省略。如果您省略$entityName
,则它等于控件名称(即第一个参数$name
)。
最后,在表单附加到演示者之后,您必须调用finalizing ajaxSelect。例如,您可以在您的BaseForm::attached($presenter)
中这样做。
/** @var \ADT\Components\AjaxSelect\Services\EntityPoolService $ajaxEntityPoolService */ $ajaxEntityPoolService->invokeDone();
AjaxEntity的名称、其选项和查询URL序列化为控件的data-ajax-select
HTML属性。
使用QueryObject实现DynamicSelect
首先,创建一个新的类(例如,User
),它继承自\ADT\BaseQuery\BaseQuery
。
此外,我们还需要其工厂,因此也要创建一个接口(例如,IUserFactory
)。
示例
namespace App\Queries; interface IUserFactory { /** @return User */ function create(); } class User extends \ADT\BaseQuery\BaseQuery { const OPTION_ACTIVE = 'active'; protected $fetchWithDataEmail = FALSE; public function active() { $this->filter[static::OPTION_ACTIVE] = function (\Kdyby\Doctrine\QueryBuilder $qb) { $qb->andWhere('e.active = TRUE'); }; return $this; } public function disableActiveFilter() { unset($this->filter[static::OPTION_ACTIVE]); return $this; } protected function doCreateQuery(\Kdyby\Persistence\Queryable $repository) { $qb = parent::doCreateQuery($repository); $qb->addOrderBy('e.name'); return $qb; } /** * @param Queryable|null $repository * @param int $hydrationMode * @return array|\Kdyby\Doctrine\ResultSet|mixed|object|\stdClass|null */ public function fetch(?Queryable $repository = null, $hydrationMode = AbstractQuery::HYDRATE_OBJECT) { $fetch = parent::fetch($repository, $hydrationMode); if ($this->fetchWithDataEmail) { $array = []; foreach ($fetch as $person) { $array[$person->getId()] = \Nette\Utils\Html::el('option') ->setAttribute('value', $person->getId()) ->setHtml($person->getName()) ->setAttribute('data-email', $person->getEmail()); } $fetch = $array; } return $fetch; } /** * @return $this */ public function fetchOptionsWithEmail() { $this->fetchWithDataEmail = TRUE; return $this; } /** * @return bool */ public function callSelectPairsAuto() { return ! $this->fetchWithDataEmail && parent::callSelectPairsAuto(); } }
现在,您可以在Nette表单上创建DynamicSelect。
// Active users with default user $entityForm->addDynamicSelect('user', 'Active users', $this->userQueryFactory->create()->active()) ->setRequired(TRUE); // All users without default user $entityForm->addDynamicSelect('allUser', 'All users', $this->userQueryFactory->create(), NULL, [ \ADT\Components\AjaxSelect\DI\AjaxSelectExtension::CONFIG_OR_BY_ID_FILTER => FALSE ]); // Active users with email in label $entityForm->addDynamicSelect('userWithEmail', 'All users', $this->userQueryFactory->create()->selectPairs('nameWithEmail')); // Attribute that is not mapped so CONFIG_OR_BY_ID_FILTER must be turned off $entityForm->addDynamicSelect('profession', 'Profession', $this->userQueryFactory->create(), NULL, [ \ADT\Components\AjaxSelect\DI\AjaxSelectExtension::CONFIG_OR_BY_ID_FILTER => FALSE ]);
更改信号名称
如果您需要更改在查询URL中使用的信号,请按以下步骤操作
-
编辑您的
config.neon
ajaxSelect: getItemsSignalName: yourSignalName
-
重命名特质方法
重写
use AjaxServiceSignalTrait;
如下use AjaxServiceSignalTrait { handleGetAjaxItems as handleYourSignalName; }
故障排除
动态表单容器(如addDynamic和toMany)
如果您创建了一个包含AjaxEntity的输入的新表单容器,并在调用$ajaxEntityPoolService->invokeDone();
之后创建它(通常在表单初始化后调用),那么Ajax搜索将无法正常工作。
此类错误的示例
<?php // Form.php public function init($form) { $form->addDynamic('address', function ($container) { $container->addAjaxSelect('city', 'City', function ($ajaxEntity) { $ajaxEntity ->byCountryCode('CZ'); }); }); // No container exists right now }
{* Form.latte *} <div n:foreach="[0, 1, 2] as $addressIndex"> {* When you access $form['address'][$addressIndex], then the container, "city" input and its AjaxEntity are created *} {label $form['address'][$addressIndex]['city'] /} {input $form['address'][$addressIndex]['city']} </div>
正确解决方案
<?php // Form.php public function init($form) { $form->addDynamic('address', function ($container) { $container->addAjaxSelect('city', 'City', function ($ajaxEntity) { $ajaxEntity ->byCountryCode('CZ'); }); }); // This will create 3 new containers, its "city" input and AjaxEntity $form->setDefaults([ 'address' => [ 0 => [], 1 => [], 2 => [], ], ]); }
{* Form.latte *} {* We render only those containers, which already exists. *} <div n:foreach="$form['address']->getContainers() as $container"> {label $container['city'] /} {input $container['city']} </div>
待办事项
orById过滤嵌套select
如果您在toMany或addDynamic内部有一个select,则必须设置AjaxSelectExtension::CONFIG_OR_BY_ID_FILTER => FALSE
,这样库就不会尝试访问主实体中的按名称的属性,这会导致错误。可以实施嵌套select的orById过滤扩展,我们可以通过在select容器中确定select嵌套的位置(可能有多层嵌套)来实现,然后根据这个位置调用所有嵌套级别的元素,而不是$form->getEntity()->get{$attributeName}()
,而是$form->getEntity()->get{$nestedElement}($elementIndex)->...->get{$attributeName}()