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中包含AjaxServiceSignalTraitclass 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.neonajaxSelect: 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}()