Задача:
Создать кастомное пользовательское поле, с помощью которого можно выбрать элемент смарт-процесса. Список элементов, которые можно выбрать, зависит от значения другого поля.
Попытка решить:
1. Создал свое кастомное пользовательское поле и компонент для него, куда был добавлен Диалог выбора сущностей согласно документацииhttps://dev.1c-bitrix.ru/api_d7/bitrix/ui/entity_selector/index.php . Вкладка "Последние" не отображается. Видимо ее нужно создавать вручную при получении значений обновлять.
Исходники .default.php из main.edit компонента, где объявлен Диалог выбора сущностей:
2. Создал свой кастомный провайдер данных, который должен возвращать элементы смарт-поцесса согласно документацииhttps://dev.1c-bitrix.ru/api_d7/bitrix/ui/entity_selector/providers/index.php
Исходники кастомного провайдера:
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)
Запрос к провайдеру данных:
Ответ провайдера данных: