Найти в Дзене
korvova

Создание динамически связанных полей в формах бизнес-процессов в Битрикс24 (коробка)

При работе с бизнес-процессами в Битрикс24 (коробочная версия) нередко возникает задача «привязки» одного поля к значению другого, либо динамического отображения полей в зависимости от выбора пользователя. Стандартного функционала для таких связей зачастую не хватает, и тогда на помощь приходит возможность создавать собственные типы полей. В этой статье разберёмся: Стандартные поля для модулей списков (Lists) в Битрикс24 (коробка) вы можете найти по следующему пути: /bitrix/modules/lists/classes/general/listfieldtypes.php Именно там описаны системные классы для работы со стандартными типами полей списков. Для регистрации дополнительных полей (то есть дополнительных типов свойств) в Битрикс24 используется механизм создания и подключения класса со специальными методами (GetUserTypeDescription, GetPublicEditHTML и т.д.). Если говорить про стандартные инфоблоки и их свойства, то мы можем «подключаться» к событию OnIBlockPropertyBuildList через AddEventHandler. При этом наш класс будет объя
Оглавление

При работе с бизнес-процессами в Битрикс24 (коробочная версия) нередко возникает задача «привязки» одного поля к значению другого, либо динамического отображения полей в зависимости от выбора пользователя. Стандартного функционала для таких связей зачастую не хватает, и тогда на помощь приходит возможность создавать собственные типы полей.

В этой статье разберёмся:

  1. Где в коробочной версии Битрикс24 хранятся стандартные поля для списков,
  2. Как зарегистрировать дополнительные поля,
  3. И как самостоятельно добавить свои (кастомные) поля, используя PHP-код в /local/php_interface/.

Где хранятся стандартные поля?

Стандартные поля для модулей списков (Lists) в Битрикс24 (коробка) вы можете найти по следующему пути:

/bitrix/modules/lists/classes/general/listfieldtypes.php

Именно там описаны системные классы для работы со стандартными типами полей списков.

2. Как зарегистрировать дополнительные поля?

Для регистрации дополнительных полей (то есть дополнительных типов свойств) в Битрикс24 используется механизм создания и подключения класса со специальными методами (GetUserTypeDescription, GetPublicEditHTML и т.д.).

Если говорить про стандартные инфоблоки и их свойства, то мы можем «подключаться» к событию OnIBlockPropertyBuildList через AddEventHandler. При этом наш класс будет объявлять новый пользовательский тип свойства, который будет доступен при создании/редактировании свойств в админке.

3. Как создать свои поля?

Чтобы создать собственный тип поля, нужно сделать следующее:

  1. Создать PHP-класс, который описывает все требуемые методы для работы со свойством (отображение, сохранение, вывод на публичной части и т.д.).
  2. Подключить наш класс к событию OnIBlockPropertyBuildList, чтобы Битрикс «узнал» о новом типе.
  3. Разместить код так, чтобы система автоматически его читала — обычно для этого используется файл /local/php_interface/init.php (или иной файл, подключаемый в init.php).

Ниже приведён пример кода, в котором мы регистрируем новое свойство, которое умеет динамически менять свой вид (строка/число/текст/файл/чекбокс/дата/дата+время/список) в зависимости от значения другого свойства (свойство-список с «триггером»).

Пример кода /local/php_interface/init.php

<?php use Bitrix\Main\Loader; Loader::includeModule('iblock'); // Регистрируем обработчик AddEventHandler("iblock", "OnIBlockPropertyBuildList", ["ExtendedConditionalMultiType", "GetUserTypeDescription"]); class ExtendedConditionalMultiType { public static function GetUserTypeDescription() { return [ "PROPERTY_TYPE" => "S", // Всё храним как строку "USER_TYPE" => "extended_cond_multi", "DESCRIPTION" => "Условное (Строка/Число/Текст/Файл/Bool/Date/Datetime/Список) + триггер + AJAX", // Методы "GetSettingsHTML" => [__CLASS__, "GetSettingsHTML"], "PrepareSettings" => [__CLASS__, "PrepareSettings"], "GetPublicEditHTML" => [__CLASS__, "GetPublicEditHTML"], "GetPublicViewHTML" => [__CLASS__, "GetPublicViewHTML"], "ConvertToDB" => [__CLASS__, "ConvertToDB"], "ConvertFromDB" => [__CLASS__, "ConvertFromDB"], ]; } /** * Отвечает за отрисовку настроек свойства в админке: * - выбор инфоблока-триггера, свойства-списка, значения Enum, * - подтип (строка, число, текст, файл, bool, дата, дата+время, список), * - значение по умолчанию. */ public static function GetSettingsHTML($arProperty, $strHTMLControlName, &$arPropertyFields) { $settings = $arProperty["USER_TYPE_SETTINGS"] ?? []; $iblockId = (int)($settings["IBLOCK_ID"] ?? 0); $propId = (int)($settings["LIST_PROPERTY_ID"] ?? 0); $enumId = (int)($settings["LIST_ENUM_ID"] ?? 0); $fieldSubtype = (string)($settings["FIELD_SUBTYPE"] ?? "string"); $defaultVal = (string)($settings["DEFAULT_VALUE"] ?? ""); // Указываем, что будем выводить настройки пользовательского типа $arPropertyFields = ["SHOW" => ["USER_TYPE_SETTINGS"]]; // (A) Выбор инфоблока $iblockOptions = "<option value='0'>- выберите инфоблок -</option>"; $resIblocks = \CIBlock::GetList(["NAME"=>"ASC"], ["CHECK_PERMISSIONS"=>"N"]); while($ib = $resIblocks->Fetch()) { $sel = ($ib["ID"] == $iblockId) ? "selected" : ""; $safeName = htmlspecialcharsbx("[{$ib["ID"]}] {$ib["NAME"]}"); $iblockOptions .= "<option value='{$ib["ID"]}' {$sel}>{$safeName}</option>"; } $html = " <tr> <td>Инфоблок (триггер):</td> <td> <select name='{$strHTMLControlName["NAME"]}[IBLOCK_ID]'>{$iblockOptions}</select> <br/><small>Сохраните, чтобы увидеть след. этап</small> </td> </tr> "; // (B) Свойство-список if($iblockId>0) { $propOptions = "<option value='0'>- выберите свойство-список -</option>"; $pRes = \CIBlockProperty::GetList(["NAME"=>"ASC"], [ "IBLOCK_ID"=>$iblockId, "PROPERTY_TYPE"=>"L", ]); while($p=$pRes->Fetch()) { $s2 = ($p["ID"]==$propId) ? "selected" : ""; $safeP = htmlspecialcharsbx("[{$p["ID"]}] {$p["NAME"]}"); $propOptions .= "<option value='{$p["ID"]}' {$s2}>{$safeP}</option>"; } $html .= " <tr> <td>Свойство-список:</td> <td> <select name='{$strHTMLControlName["NAME"]}[LIST_PROPERTY_ID]'>{$propOptions}</select> <br/><small>Сохраните, чтобы увидеть список значений</small> </td> </tr> "; } // (C) Enum if($iblockId>0 && $propId>0) { $enumOptions = "<option value='0'>- выберите значение (Enum) -</option>"; $enRes = \CIBlockPropertyEnum::GetList(["SORT"=>"ASC"], ["PROPERTY_ID"=>$propId]); while($en=$enRes->Fetch()) { $sel = ($en["ID"]==$enumId) ? "selected" : ""; $safeE = htmlspecialcharsbx("[{$en["ID"]}] {$en["VALUE"]}"); $enumOptions .= "<option value='{$en["ID"]}' {$sel}>{$safeE}</option>"; } $html .= " <tr> <td>Триггер-значение (Enum):</td> <td> <select name='{$strHTMLControlName["NAME"]}[LIST_ENUM_ID]'>{$enumOptions}</select> <br/><small>Если выбрано именно это значение, показываем поле</small> </td> </tr> "; } // (D) Селект подтипов $subtypeOptions = [ 'string' => 'Строка', 'number' => 'Число', 'text' => 'Многострочный текст', 'file' => 'Файл (AJAX->ссылка)', 'bool' => 'Чекбокс (Да/Нет)', 'date' => 'Дата', 'datetime' => 'Дата/Время', 'list' => 'Небольшой список (из defaultVal)' ]; $htmlSubtype = "<option value=''>- не выбрано -</option>"; foreach($subtypeOptions as $k=>$lab) { $sel = ($k===$fieldSubtype)? "selected" : ""; $htmlSubtype.="<option value='{$k}' {$sel}>".htmlspecialcharsbx($lab)."</option>"; } $html .= " <tr> <td>Тип поля:</td> <td> <select name='{$strHTMLControlName["NAME"]}[FIELD_SUBTYPE]'> {$htmlSubtype} </select> </td> </tr> "; // (E) Default Value (для 'list' — варианты через ;) $html .= " <tr> <td>Значение по умолчанию (или варианты для list):</td> <td> <input type='text' name='{$strHTMLControlName["NAME"]}[DEFAULT_VALUE]' value='".htmlspecialcharsbx($defaultVal)."' style='width:300px;'/> <br/><small>Для 'list' - варианты через ; (точку с запятой)</small> </td> </tr> "; return $html; } public static function PrepareSettings($arProperty) { $s = $arProperty["USER_TYPE_SETTINGS"] ?? []; $arProperty["USER_TYPE_SETTINGS"] = [ "IBLOCK_ID" => (int)($s["IBLOCK_ID"] ?? 0), "LIST_PROPERTY_ID" => (int)($s["LIST_PROPERTY_ID"] ?? 0), "LIST_ENUM_ID" => (int)($s["LIST_ENUM_ID"] ?? 0), "FIELD_SUBTYPE" => (string)($s["FIELD_SUBTYPE"] ?? "string"), "DEFAULT_VALUE" => (string)($s["DEFAULT_VALUE"] ?? ""), ]; return $arProperty; } /** * Отрисовка поля при редактировании элемента */ public static function GetPublicEditHTML($property, $value, $strHTMLControlName) { $s = $property["USER_TYPE_SETTINGS"] ?? []; $propId = (int)($s["LIST_PROPERTY_ID"] ?? 0); $enumId = (int)($s["LIST_ENUM_ID"] ?? 0); $type = (string)($s["FIELD_SUBTYPE"] ?? "string"); $defVal = (string)($s["DEFAULT_VALUE"] ?? ""); // Текущее значение $curVal = (string)($value["VALUE"] ?? ""); if($curVal==="" && !in_array($type, ['file','list'], true)) { $curVal = $defVal; } $wrapId = $strHTMLControlName["VALUE"]."_wrap"; $htmlName = htmlspecialcharsbx($strHTMLControlName["VALUE"]); // Определяем, как рендерить поле switch($type) { case 'number': $fieldHtml = "<input type='number' name='{$htmlName}' style='width:200px;' value='".htmlspecialcharsbx($curVal)."' />"; break; case 'text': $fieldHtml = "<textarea name='{$htmlName}' cols='40' rows='5'>".htmlspecialcharsbx($curVal)."</textarea>"; break; case 'bool': $checked = ($curVal==='Y') ? "checked" : ""; $fieldHtml = "<label><input type='checkbox' name='{$htmlName}' value='Y' {$checked}> Да/Нет?</label>"; break; case 'date': $fieldHtml = "<input type='date' name='{$htmlName}' value='".htmlspecialcharsbx($curVal)."' />"; break; case 'datetime': $fieldHtml = "<input type='datetime-local' name='{$htmlName}' value='".htmlspecialcharsbx($curVal)."' />"; break; case 'list': // Список вариантов = defaultVal (A;B;C) $variants = explode(';', $defVal); $variants = array_map('trim',$variants); if(empty($variants[0])){ $variants=['Option1','Option2']; } $fieldHtml = "<select name='{$htmlName}'><option value=''>- не выбрано -</option>"; foreach($variants as $v) { $safeV = htmlspecialcharsbx($v); $sel = ($v===$curVal)?'selected':''; $fieldHtml.="<option value='{$safeV}' {$sel}>{$safeV}</option>"; } $fieldHtml.="</select>"; $fieldHtml.="<br/><small>Варианты из defaultVal</small>"; break; case 'file': // AJAX -> uploadAjax.php, сохраняем ссылку $fileId = $htmlName.'_file'; $textId = $htmlName.'_link'; $uploadAjaxPath = "/local/php_interface/uploadAjax.php"; $fieldHtml = " <input type='text' id='{$textId}' name='{$htmlName}' value='".htmlspecialcharsbx($curVal)."' placeholder='Ссылка на файл...' style='width:300px;' /> <br/> <input type='file' id='{$fileId}' style='margin-top:5px;' /> <script> (function(){ var fileEl = document.getElementById('{$fileId}'); var textEl = document.getElementById('{$textId}'); if(!fileEl || !textEl) return; fileEl.addEventListener('change', function(){ if(!fileEl.files || !fileEl.files[0]) return; var file = fileEl.files[0]; var formData = new FormData(); formData.append('action','upload'); formData.append('uploadFile', file); fetch('{$uploadAjaxPath}', { method: 'POST', body: formData }) .then(resp => resp.json()) .then(data => { if(data.status==='ok'){ textEl.value = data.url; alert('Файл загружен: '+ data.url); } else { alert('Ошибка: '+data.message); } }) .catch(err=>{ console.log(err); alert('Ошибка при отправке файла'); }); }); })(); </script> "; break; case 'string': default: $fieldHtml = "<input type='text' name='{$htmlName}' style='width:300px;' value='".htmlspecialcharsbx($curVal)."' />"; break; } // Обёртка + скрипт для скрытия/показа поля в зависимости от выбранного значения в другом свойстве $html = " <div id='{$wrapId}' style='background:#eef; padding:5px; margin:5px 0;'> {$fieldHtml} </div> "; // JS-триггер (наблюдаем за PROPERTY_{propId} и скрываем/показываем текущее поле) $js = " <script> BX.ready(function(){ var wrapEl = document.getElementById('{$wrapId}'); if(!wrapEl) return; var row = wrapEl.closest('tr'); if(!row) return; function checkTrigger(){ var sel = document.querySelector('select[name=\"PROPERTY_{$propId}\"]'); if(!sel){ row.style.display='none'; return; } var val = sel.value||''; if(parseInt(val)==={$enumId}){ row.style.display=''; } else { row.style.display='none'; } } checkTrigger(); setTimeout(function(){ var sel2 = document.querySelector('select[name=\"PROPERTY_{$propId}\"]'); if(sel2){ sel2.addEventListener('change',checkTrigger); checkTrigger(); } else { row.style.display='none'; } },500); }); </script> "; return $html.$js; } public static function GetPublicViewHTML($property, $value, $strHTMLControlName) { $s = $property["USER_TYPE_SETTINGS"] ?? []; $type = (string)($s["FIELD_SUBTYPE"] ?? "string"); $val = (string)($value["VALUE"] ?? ""); if(trim($val)==="") { return "<span style='color:#aaa;'>(пусто)</span>"; } switch($type) { case 'bool': return ($val==='Y') ? "Да" : "Нет"; case 'date': case 'datetime': return htmlspecialcharsbx($val); case 'list': return htmlspecialcharsbx($val); case 'file': $safeUrl = htmlspecialcharsbx($val); return "<a href='{$safeUrl}' target='_blank'>Скачать/посмотреть</a>"; default: return htmlspecialcharsbx($val); } } public static function ConvertToDB($property, $value) { // При необходимости можно форматировать данные перед записью return $value; } public static function ConvertFromDB($property, $value) { // И при извлечении из БД return $value; } }

Что здесь происходит:

  • PROPERTY_TYPE = "S": мы храним данные в виде строки.
  • USER_TYPE = "extended_cond_multi": машинное название типа.
  • DESCRIPTION: человеко-читаемое название, отображается в админке.
  • GetSettingsHTML / PrepareSettings: формируют интерфейс настройки и сохраняют настройки.
  • GetPublicEditHTML: отвечают за вывод поля в форме редактирования элемента инфоблока (добавляет скрипт, скрывающий/показывающий поле по «триггеру»).
  • GetPublicViewHTML: выводит значение на публичной части.
  • ConvertToDB / ConvertFromDB: можно преобразовывать данные перед сохранением и извлечением.

После того как вставите код в /local/php_interface/init.php

Можете увидеть новое поле

появится новый тип поля в списке
появится новый тип поля в списке
Сможете выбрать, после какого значнеие в списке показывать поле, по умолчанию поле скрыто
Сможете выбрать, после какого значнеие в списке показывать поле, по умолчанию поле скрыто

Файл uploadAjax.php для загрузки файлов

Для подтипа «файл» мы используем AJAX-загрузку по адресу /local/php_interface/uploadAjax.php. Вот пример кода этого файла:

<?php // /local/php_interface/uploadAjax.php header('Content-type: application/json; charset=utf-8'); if($_SERVER['REQUEST_METHOD'] !== 'POST'){ echo json_encode(['status'=>'error','message'=>'Only POST']); exit; } if(!isset($_FILES['uploadFile']) || $_FILES['uploadFile']['error'] !== UPLOAD_ERR_OK) { echo json_encode(['status'=>'error','message'=>'No file or upload error']); exit; } $uploadDir = $_SERVER['DOCUMENT_ROOT'].'/upload/my/'; if(!is_dir($uploadDir)){ mkdir($uploadDir, 0755, true); } $origName = $_FILES['uploadFile']['name']; $tmpName = $_FILES['uploadFile']['tmp_name']; $unique = uniqid().'_'.preg_replace('/[^a-zA-Z0-9_\.-]/','_', $origName); $destPath = $uploadDir.$unique; if(move_uploaded_file($tmpName, $destPath)){ // Генерируем публичную ссылку $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS']!='off')?'https':'http'; $host = $_SERVER['HTTP_HOST']; $fileUrl = $scheme.'://'.$host.'/upload/my/'.$unique; echo json_encode(['status'=>'ok','url'=>$fileUrl]); } else { echo json_encode(['status'=>'error','message'=>'Failed to move_uploaded_file']); }

Основные моменты:

  • Скрипт ожидает POST-запрос с полем uploadFile.
  • Сохраняет полученный файл в /upload/my/.
  • Возвращает JSON с публичной ссылкой на загруженный файл, чтобы сразу подставить её в наше свойство.

Итоги

Таким образом, вы можете расширить возможности бизнес-процессов, форм и списков в Битрикс24 (коробочная версия), создав собственные динамические поля. Для этого:

  1. Проверьте, где хранятся стандартные типы (например, в файле /bitrix/modules/lists/classes/general/listfieldtypes.php).
  2. Создайте класс, описывающий логику нового типа свойства и зарегистрируйте его через AddEventHandler в файле /local/php_interface/init.php.
  3. Реализуйте нужные методы (отрисовка формы, обработка настроек, сохранение/вывод значения и т.д.).
  4. Добавьте при необходимости дополнительные скрипты (например, для AJAX-загрузки) и подключите их так, чтобы они были доступны (как uploadAjax.php).

Теперь, имея собственный тип поля, вы сможете формировать более гибкие бизнес-процессы с условными (динамическими) полями, которые будут показываться пользователю при выполнении определённых условий. Это открывает широкие возможности для кастомизации форм и автоматизации процессов внутри корпоративного портала Битрикс24.

Если всё сделано правильно, новое свойство будет доступно при добавлении/редактировании свойств инфоблока