Задача:
Создать кастомное пользовательское поле, с помощью которого можно выбрать элемент смарт-процесса. Список элементов, которые можно выбрать, зависит от значения другого поля.
Попытка решить:
1. Создал свое кастомное пользовательское поле и компонент для него, куда был добавлен Диалог выбора сущностей согласно документации . Вкладка "Последние" не отображается. Видимо ее нужно создавать вручную при получении значений обновлять.
Исходники .default.php из main.edit компонента, где объявлен Диалог выбора сущностей:
2. Создал свой кастомный провайдер данных, который должен возвращать элементы смарт-поцесса согласно документации
Исходники кастомного провайдера:
3. Логи в fillDialog кастомного провайдера данных показывают что все нормально и результат должен быть возвращен тот который необходим
4. Однако кастомный провайдер возвращает в массиве items как нужный результат так и последние выбранные элементы(RecentItems)
Запрос к провайдеру данных:

Ответ провайдера данных:

Создать кастомное пользовательское поле, с помощью которого можно выбрать элемент смарт-процесса. Список элементов, которые можно выбрать, зависит от значения другого поля.
Попытка решить:
1. Создал свое кастомное пользовательское поле и компонент для него, куда был добавлен Диалог выбора сущностей согласно документации . Вкладка "Последние" не отображается. Видимо ее нужно создавать вручную при получении значений обновлять.
Исходники .default.php из main.edit компонента, где объявлен Диалог выбора сущностей:
| Код |
|---|
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
use Bitrix\Crm\Service\Container;
use Bitrix\Main\Text\HtmlFilter;
use QBS\Commons\Log\Logger;
use QBS\Fields\Providers\DependentDynamicSelectorProvider;
\Bitrix\Main\UI\Extension::load(['sidepanel']);
/**
* @var DependentDynamicSelectorUfComponent $component
* @var array $arResult
*/
$component = $this->getComponent();
$name = $arResult['userField']['FIELD_NAME'];
$entityId = $arResult['userField']['ENTITY_ID'];
$entityValueId = $arResult['userField']['ENTITY_VALUE_ID'];
$dynamic_id = $arResult['userField']['SETTINGS']['DYNAMIC_ID'];
$dependent_field_id = $arResult['userField']['SETTINGS']['DEPENDENT_FIELD_ID'];
$dependent_dynamic_id = $arResult['userField']['SETTINGS']['DEPENDENT_DYNAMIC_ID'];
$dependent_dynamic_field_id = $arResult['userField']['SETTINGS']['DEPENDENT_DYNAMIC_FIELD_ID'];
$logger = new Logger("qbs:fields.field.dependent_dynamic_selector::.default");
$logger->debug('entityId=' . \CCrmOwnerType::ResolveIDByUFEntityID($entityId));
$factory = Container::getInstance()->getFactory(\CCrmOwnerType::ResolveIDByUFEntityID($entityId));
$logger->debug('entityValueId=' . $entityValueId);
$item = $factory->getItem(intval($entityValueId));
$dependent_dynamic_element_id = $item[$dependent_field_id];
$values = [];
if (is_array($arResult['value']) && !empty($arResult['value'])) {
$factory = \Bitrix\Crm\Service\Container::getInstance()->getFactory(intval($arResult['additionalParameters']['arUserField']['SETTINGS']['DYNAMIC_ID']));
foreach ($arResult['value'] as $value) {
if ($value != "") {
$id = intval($value);
if ($id == 0) {
continue;
} else {
$values[$id] = $factory->getItem($id)->getTitle();
}
}
}
}
$multiple_postfix = '';
if ($arResult['userField']['MULTIPLE'] == 'Y') {
$multiple_postfix = '[]';
}
?>
<div class='field-wrap'>
<div id="<?=$name?>_value_container" name="<?=$name?>_value_container">
<?php foreach ($values as $id => $title): ?>
<input type="hidden" name="<?= $name . $multiple_postfix ?>" value="<?= $id ?>"/>
<?php endforeach; ?>
</div>
<div id="<?=$name?>_selector_container" name="<?=$name?>_selector_container"></div>
<sc ript>
var <?= $name?>_entities = [
{
id: 'dependent_dynamic', // Динамические сущности
options: {
dynamic_id: '<?=$dynamic_id?>',
dependent_field_id: '<?=$dependent_field_id?>',
entityId: '<?=$entityId?>',
entityValueId: '<?=$entityValueId?>',
dependent_dynamic_id: '<?=$dependent_dynamic_id?>',
dependent_dynamic_field_id: '<?=$dependent_dynamic_field_id?>',
dependent_dynamic_element_id: '<?=$dependent_dynamic_element_id?>',
tabs: 'dependent_dynamic'
},
dynamicLoad: true,
dynamicSearch: true,
}
];
BX.ready(function (e) {
let selector = createSelector(<?= $name?>_entities);
selector.renderTo(BX('<?= $name?>_selector_container'));
});
function createSelector(entities) {
let value_container = BX("<?= $name?>_value_container");
const <?= $name?>tagSelector = new BX.UI.EntitySelector.TagSelector({
id: '<?= $name?>_selector',
multiple: ('<?= $arResult['userField']['MULTIPLE']?>' == 'Y'),
height: 300,
dialogOptions: {
enableSearch: true,
multiple: ('<?= $arResult['userField']['MULTIPLE']?>' == 'Y'),
dropdownMode: true,
id: '<?= $name?>_selector_dialog',
context: '<?= $name?>',
items: [
<?php foreach ($values as $id => $title): ?>
{
id: <?= $id?>,
entityId: 'dependent_dynamic',
title: '<?=$title?>',
tabs: 'dependent_dynamic',
},
<?php endforeach;?>
],
selectedItems: [
<?php foreach ($values as $id => $title): ?>
{
id: <?=$id?>,
entityId: 'dependent_dynamic',
title: '<?=$title?>',
tabs: 'dependent_dynamic',
},
<?php endforeach;?>
],
searchFields: [
{
name:'title',
type:'string',
searchable: true
},
],
searchOptions: {
allowCreateItem: true,
dynamicSearch: true,
},
recentTabOptions: {
stub: true,
},
tabs: [
{id: 'dependent_dynamic', title: 'Элементы', itemOrder: {id: 'asc'}},
],
entities: entities,
events: {
'Item:onSelect': function (event) {
console.log('Item:onSelect');
let value = event.getData().item.id;
var selectedItems = <?= $name?>tagSelector.getDialog().getSelectedItems();
var result = [];
BX.cleanNode(value_container);
if (BX.type.isArray(selectedItems) && selectedItems.length > 0) {
selectedItems.forEach(function (item) {
let input = BX.create(
{
tag: 'input',
props: {
value: (item.id).toString(),
type: 'hidden',
name: '<?= $name . $multiple_postfix?>'
}
}
);
BX.append(input, value_container);
result.push(item.id);
});
} else {
let input = BX.create(
{
tag: 'input',
props: {
value: "",
type: 'hidden',
name: '<?= $name . $multiple_postfix?>'
},
}
);
BX.append(input, value_container);
}
event.getTarget().selectTab('dependent_dynamic');
for (var k in BX.Crm.EntityEditor.items) {
let editor = BX.Crm.EntityEditor.get(k)
let field = editor.getModel().getField('<?=$name?>');
if (result.length === 0) {
field.VALUE = "";
field.IS_EMPTY = true;
} else {
field.VALUE = result.join(',');
field.IS_EMPTY = false;
}
editor.getModel().setField('<?=$name?>', field);
let control = editor.getControlById("<?=$name?>");
if (control !== undefined) {
control.markAsChanged();
}
}
// console.log(input);
},
'Item:onDeselect': function (event) {
let selectorId = event.getTarget().context;
let value = event.getData().item.id;
var selectedItems = <?= $name?>tagSelector.getDialog().getSelectedItems();
var result = [];
BX.cleanNode(value_container);
if (BX.type.isArray(selectedItems) && selectedItems.length > 0) {
selectedItems.forEach(function (item) {
let input = BX.create(
{
tag: 'input',
props: {
value: item.id,
type: 'hidden',
name: '<?= $name . $multiple_postfix?>'
}
}
);
BX.append(input, value_container);
result.push(item.id);
});
} else {
let input = BX.create(
{
tag: 'input',
props: {
value: "",
type: 'hidden',
name: '<?= $name . $multiple_postfix?>'
}
}
);
BX.append(input, value_container);
}
event.getTarget().selectTab('dependent_dynamic');
for (var k in BX.Crm.EntityEditor.items) {
let editor = BX.Crm.EntityEditor.get(k)
let field = editor.getModel().getField('<?=$name?>');
if (result.length === 0) {
field.VALUE = "";
field.IS_EMPTY = true;
} else {
field.VALUE = result.join(',');
field.IS_EMPTY = false;
}
editor.getModel().setField('<?=$name?>', field);
let control = editor.getControlById("<?=$name?>");
if (control !== undefined) {
BX.fireEvent(control, "change");
control.markAsChanged();
}
}
},
'onShow': function (event) {
console.log("onShow");
let dialog = event.getTarget();
//dialog.getRecentTab().setVisible(true);
let dependent_dynamic_element_id = Object.values(BX.Crm.EntityEditor.items)[0]._model._data.<?=$dependent_field_id?>.VALUE;
//console.log(dependent_dynamic_element_id);
let val = dialog.entities.get('dependent_dynamic');
if(val.options.dependent_dynamic_element_id === undefined) {
}else{
if (val.options.dependent_dynamic_element_id != dependent_dynamic_element_id) {
val.options.dependent_dynamic_element_id = dependent_dynamic_element_id;
//dialog.entities.set('dependent_dynamic', val);
//entities[0].options.dependent_dynamic_element_id = dependent_dynamic_element_id;
//<?= $name?>_entities.set('dependent_dynamic', val);
dialog.destroy();
BX.cleanNode(BX('<?=$name?>_selector_container'));
BX.cleanNode(BX('<?=$name?>_value_container'));
BX.remove(BX('<?=$name?>_selector'));
let ent = <?= $name?>_entities;
ent[0].options.dependent_dynamic_element_id = dependent_dynamic_element_id;
let selector = createSelector(ent);
selector.renderTo(BX('<?=$name?>_selector_container'));
//
selector.getDialog().show();
//new_dialog.getDialog().load();
//dialog.getDialog().load();
//dialog.load();
}
}
},
'onSearch': function (event) {
console.log("onSearch");
const dialog = event.getTarget();
console.log(event.getData());
}
}
}
});
return <?= $name?>tagSelector;
}
</sc ript>
</div>
|
2. Создал свой кастомный провайдер данных, который должен возвращать элементы смарт-поцесса согласно документации
Исходники кастомного провайдера:
| Код |
|---|
<?php
namespace QBS\Fields\Providers;
use Bitrix\Crm\Controller\Entity;
use Bitrix\Crm\Integration\UI\EntitySelector\EntityProvider;
use Bitrix\Crm\Restriction\RestrictionManager;
use Bitrix\Crm\Security\EntityAuthorization;
use Bitrix\Crm\Service\Container;
use Bitrix\Crm\UI\EntitySelector;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Type\Collection;
use Bitrix\UI\EntitySelector\BaseProvider;
use Bitrix\UI\EntitySelector\Dialog;
use Bitrix\UI\EntitySelector\Item;
use Bitrix\UI\EntitySelector\RecentItem;
use Bitrix\UI\EntitySelector\SearchQuery;
use Bitrix\UI\EntitySelector\Tab;
use QBS\Commons\Log\Logger;
class DependentDynamicSelectorProvider extends BaseProvider
{
protected const ENTITY_ID = 'dependent_dynamic';
protected const ELEMENTS_LIMIT = 20;
const WRITE_LOG = true;
const OPTIONS = [
"DYNAMIC_ID" => "dynamic_id",
"DEPENDENT_DYNAMIC_ID" => 'dependent_dynamic_id',
"DEPENDENT_DYNAMIC_ELEMENT_ID" => 'dependent_dynamic_element_id',
"DEPENDENT_DYNAMIC_FIELD_ID" => 'dependent_dynamic_field_id',
"TABS" => 'tabs'
];
protected static function getEntityId(): string
{
return static::ENTITY_ID;
}
protected function getTabs(): array
{
return explode(',',$this->getOption(self::OPTIONS["TABS"],""));
}
protected function getEntityTypeId(): int
{
return intval($this->getOption(self::OPTIONS["DYNAMIC_ID"],""));
}
protected function getEntityTypeNameForMakeItemMethod()
{
return mb_strtolower(\CCrmOwnerType::ResolveName($this->getEntityTypeId()));
}
public function getEntityTypeName(): string
{
return \CCrmOwnerType::ResolveName($this->getEntityTypeId());
}
protected function getDependentDynamicId(): int
{
return intval($this->getOption(self::OPTIONS["DEPENDENT_DYNAMIC_ID"],""));
}
protected function getDependentDynamicFieldId(): string
{
return $this->getOption(self::OPTIONS["DEPENDENT_DYNAMIC_FIELD_ID"],"");
}
protected function getDependentDynamicElementId(): int
{
return intval($this->getOption(self::OPTIONS["DEPENDENT_DYNAMIC_ELEMENT_ID"],""));
}
public function __construct(array $options = [])
{
parent::__construct($options);
$this->options = $options;
}
public function doSearch(SearchQuery $searchQuery, Dialog $dialog): void
{
$logger = new Logger(DependentDynamicSelectorProvider::class."::doSearch");
$logger->debug("start odSearch");
/*$entity = $dialog->getEntity(static::ENTITY_ID);
$logger->debug("entity");
$logger->debug($entity->jsonSerialize());
if ($entity)
{
$entity->setDynamicSearch(true);
$entity->setDynamicLoad(true);
$entity->setSearchable(true);
}*/
$dialog->addTab(
new Tab(
array(
"id" => static::ENTITY_ID,
"title" => "Элементы"
)
)
);
//$dialog->addEntity($entity);
//$filter_type = $this->getOption(self::OPTIONS["FILTER"],"");
$searchQuery->setCacheable(false);
$filter = array("TITLE" => $searchQuery->getRawQuery() . '%');
/*if($filter_type == DynamicType::FILTER["FIELDS"]) {
$filter = array();
$f = $this->getOption(self::OPTIONS["FIELDS"], "");
if (!is_array($f)) {
$fields = explode(",", $f);
$filter['LOGIC'] = 'OR';
foreach ($fields as $field) {
$filter[$field] = $searchQuery->getRawQuery() . '%';
}
}else{
$filter[$f] = $searchQuery->getRawQuery(). '%';
}
}*/
$items = $this->makeItemsByIds($this->filter($filter));
//$logger->debug("=================items");
//$logger->dump($items);
$dialog->addItems($items);
//$this->fillDialog($dialog);
}
public function filter($filters = array()): array
{
$logger = new Logger(DependentDynamicSelectorProvider::class."::filter", true);
$factory = Container::getInstance()->getFactory($this->getEntityTypeId());
//Container::getInstance()->getRelationManager()->getChildElements()
//$filter_type = $this->getOption(self::OPTIONS["FILTER"],"");
//$select = ['ID', 'TITLE'];
/*if($filter_type == DynamicEntityType::FILTER["FIELDS"]) {
$fields = explode(",", $this->getOption(self::OPTIONS["FIELDS"], ""));
foreach ($fields as $field){
$select[] = $field;
}
}*/
$params = [
'select' => ['ID'],
'order' => ['ID'],
'limit' => self::ELEMENTS_LIMIT,
'offset'=> 0
];
$dependent_factory = Container::getInstance()->getFactory($this->getDependentDynamicId());
$i = $dependent_factory->getItem($this->getDependentDynamicElementId());
//$ids = explode(",", $item[$this->getDependentDynamicFieldId()]);
//$logger->dump($i[$this->getDependentDynamicFieldId()]);
$filters['@ID'] = $i[$this->getDependentDynamicFieldId()];//implode(", ", $i[$this->getDependentDynamicFieldId()]);
if(!empty($filters) && is_array($filters)){
$params['filter'] = $filters;
}
$logger->debug("=================params");
$logger->dump($params);
$items = $factory->getItems($params);
$result = array();
foreach ($items as $item){
$result[] = $item->getId();
}
// $result = [];
$logger->debug("=================result");
$logger->dump($result);
return $result;
}
protected function fetchEntryIds(array $filter): array
{
$factory = Container::getInstance()->getFactory($this->getEntityTypeId());
if ($factory)
{
$items = $factory->getItemsFilteredByPermissions([
'select' => ['ID'],
'filter' => $filter,
]);
$result = [];
foreach ($items as $item)
{
$result[] = $item->getId();
}
return $result;
}
return [];
}
protected function makeItem(int $entityId): ?Item
{
$userPermissions = \CCrmPerms::GetCurrentUserPermissions();
$serviceUserPermissions = Container::getInstance()->getUserPermissions($userPermissions->GetUserID());
$factory = Container::getInstance()->getFactory($this->getEntityTypeId());
$item = $factory->getItem($entityId);
$entityTypeId = $this->getEntityTypeId();
$canReadItem = EntityAuthorization::checkReadPermission($this->getEntityTypeId(), $entityId);
//$serviceUserPermissions->checkReadPermissions($entityTypeId, $entityId);
$bytes = random_bytes(16);
$entityInfo = [
"id" => 1,
"type" => $this->getEntityTypeNameForMakeItemMethod(),
"typeName" => $this->getEntityTypeName(),
"typeNameTitle" => \CCrmOwnerType::GetDescription($entityTypeId),
"place" => $this->getEntityTypeNameForMakeItemMethod(),
"hidden" => !$canReadItem,
"title" => $item->getHeading(),
"url" => Container::getInstance()->getRouter()->getItemDetailUrl($entityTypeId, $entityId),
"desc" => bin2hex($bytes),
"image" => "",
"permissions" => [
"canUpdate" => $serviceUserPermissions->checkUpdatePermissions($entityTypeId, $entityId),
]
];
//Container::getInstance()->getRelationManager()->getChildElements()
//$filter_type = $this->getOption(self::OPTIONS["FILTER"],"");
//$select = ['ID', 'TITLE'];
$itemdata = [
'id' => $entityId,
//'entityId' => $this->getItemEntityId(),
'entityId' => static::ENTITY_ID,
'title' => (string)$entityInfo['title'],
'subtitle' => $entityInfo['desc'],
'link' => $entityInfo['url'],
'linkTitle' => "Ссылка",
'avatar' => $entityInfo['image'],
'searchable' => true,
'hidden' => !$canReadItem,
'tabs' => static::ENTITY_ID
];
$itemdata['customData'] = array();
/*if($filter_type == DynamicAtStageSelector::FILTER["FIELDS"]) {
$fields = explode(",", $this->getOption(self::OPTIONS["FIELDS"], ""));
foreach ($fields as $field){
$entityInfo[$field] = $item[$field];
$itemdata['customData'][$field] = $entityInfo[$field];
}
}*/
$itemdata['customData']['entityInfo'] = $entityInfo;
return new Item($itemdata);
}
protected function getItemEntityId(): string
{
return static::ENTITY_ID; // TODO: Change the autogenerated stub
}
public function fillDialog(Dialog $dialog): void
{
//$itemEntityId = $this->getItemEntityId();
$logger = new Logger(DependentDynamicSelectorProvider::class."::fillDialog",true);
$logger->debug("start fillDialog");
/* $entity = $dialog->getEntity(static::ENTITY_ID);
$logger->debug("entity");
$logger->debug($entity->jsonSerialize());*/
$items = $this->makeItemsByIds($this->filter([]));
$logger->debug("=================items");
$logger->dump($items);
$dialog->addItems($items);
$logger->debug("=================recentitems");
$logger->dump($dialog->getRecentItems());
//$dialog->saveRecentItems([]);
/*if ($entity)
{
$entity->setDynamicSearch(true);
$entity->setDynamicLoad(true);
$entity->setSearchable(true);
}*/
/*
$recentItems = $dialog->getRecentItems();
$recentItemsByEntityId = $recentItems->getEntityItems($itemEntityId);
$remainingItemsCount = static::ELEMENTS_LIMIT - count($recentItemsByEntityId);
if ($remainingItemsCount > 0)
{
foreach ($dialog->getGlobalRecentItems()->getEntityItems($this->getItemEntityId()) as $globalRecentItem)
{
if ($remainingItemsCount === 0)
{
break;
}
if (!$recentItems->has($globalRecentItem))
{
$recentItems->add($globalRecentItem);
$remainingItemsCount--;
}
}
}
if ($remainingItemsCount > 0)
{
$context = $dialog->getContext() ?: EntitySelector::CONTEXT;
$moreItemIds = $this->getRecentItemIds($context);
foreach ($moreItemIds as $itemId)
{
if ($remainingItemsCount === 0)
{
break;
}
$recentItem = new RecentItem([
'id' => $itemId,
'entityId' => $itemEntityId,
]);
if (!$recentItems->has($recentItem))
{
$recentItems->add($recentItem);
$remainingItemsCount--;
}
}
}
*/
}
public function isAvailable(): bool
{
$restriction = RestrictionManager::getSearchLimitRestriction();
return
EntityAuthorization::checkReadPermission($this->getEntityTypeId(), 0)
&& !$restriction->isExceeded($this->getEntityTypeId())
;
}
public function getItems(array $ids): array
{
return $this->makeItemsByIds($ids);
}
public function getSelectedItems(array $ids): array
{
return $this->makeItemsByIds($ids);
}
protected function makeItemsByIds(array $ids): array
{
$items = [];
Collection::normalizeArrayValuesByInt($ids);
if (empty($ids))
{
return $items;
}
$ids = $this->filterOutNonExistentEntryIds($ids);
//todo remove queries in cycle!
foreach ($ids as $entryId)
{
$items[] = $this->makeItem($entryId);
}
return $items;
}
protected function filterOutNonExistentEntryIds(array $ids): array
{
return $this->fetchEntryIds([
'@ID' => $ids,
]);
}
protected function getAdditionalFilter(): array
{
return [];
}
protected function getCategoryId(): int
{
return 0;
}
protected function getRecentItemIds(string $context): array
{
$ids = [];
$recentItems = Entity::getRecentlyUsedItems($context, $this->getItemEntityId(), [
'EXPAND_ENTITY_TYPE_ID' => $this->getEntityTypeId(),
'EXPAND_CATEGORY_ID' => $this->getCategoryId(),
]);
foreach ($recentItems as $item)
{
$ids[] = $item['ENTITY_ID'];
}
return $ids;
}
}
|
3. Логи в fillDialog кастомного провайдера данных показывают что все нормально и результат должен быть возвращен тот который необходим
4. Однако кастомный провайдер возвращает в массиве items как нужный результат так и последние выбранные элементы(RecentItems)
Запрос к провайдеру данных:
Ответ провайдера данных: