Добрый день!
Хочу поделиться реализацией пользовательского поля с типом "Привязка к складу" Битрикса. Тип создавался для HL-блоков
Структура файлов, участвующих в построении поля такая:
Сейчас разберем содержимое каждого файла. Я опущу их расположение в проекте, т.к. это не важно: разместить их можно в любом удобном месте (программисты поймут)
Начнем с главного файла с классом, описывающим новый тип поля - CUserTypeStoreLink.php
Главная особенность - формирование списка значений и фильтра для поиска. Обратите внимание на метод getItemFieldHtml. Именно в нем формируется HTML с фильтром и выводом значений
AJAX запросы обрабатывает файл ajax.php. В нем содержится класс CUserTypeStoreLinkAction по обработке AJAX запросов на формирование списка вариантов складов, удовольтворяющих условиям фильтра. Его код представлен ниже:
Весь JS расположен в файле script.js
Файл в основном шлет запросы на ajax.php поиск складов и формирует список вариантов. А также небольшие правки для удобства пользования
Есть файл style.css со стилями для поля. Содержит минимальный набор стилей для корректной работы
Завершающий этап - подключить наш новый тип поля "привязка к складам" через хендлер OnUserTypeBuildList
И, наконец, немного картинок как это выглядет:
Вид в списке:
Вид в фильтре:
Надеюсь кому-то пригодится!
Хочу поделиться реализацией пользовательского поля с типом "Привязка к складу" Битрикса. Тип создавался для HL-блоков
Структура файлов, участвующих в построении поля такая:
Сейчас разберем содержимое каждого файла. Я опущу их расположение в проекте, т.к. это не важно: разместить их можно в любом удобном месте (программисты поймут)
Начнем с главного файла с классом, описывающим новый тип поля - CUserTypeStoreLink.php
<?php
namespace Project\UserTypes\StoreLink;
use Bitrix\Catalog\StoreTable;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Loader;
use Bitrix\Main\LoaderException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use CJSCore;
/**
* Class CUserTypeStoreLink
*
* Класс описывает пользовательское свойство привязки к складу
*
* @package Project\UserTypes\StoreLink
*/
class CUserTypeStoreLink
{
const USER_TYPE = "mercool_store_link";
/**
* Формируем массив значение
*
* @param $mxValue
* @return array
*/
private static function getValuesArray($mxValue)
{
$arValues = array();
if (!empty($mxValue)) {
if (is_array($mxValue)) {
$arValues = $mxValue;
} else {
$arValues = [$mxValue];
}
}
return $arValues;
}
/**
* Функция регистрируется в качестве обработчика события OnUserTypeBuildList
* @return array
*/
public function GetUserTypeDescription()
{
return [
"USER_TYPE_ID" => self::USER_TYPE,
"CLASS_NAME" => __CLASS__,
"DESCRIPTION" => "Привязка к складу",
"BASE_TYPE" => "int",
];
}
/**
* Функция вызывается при добавлении нового свойства для конструирования SQL запроса
* создания столбца значений свойства
*
* @param $arUserField
* @return string
*/
public function GetDBColumnType($arUserField)
{
$sNum = "";
switch (strtolower($GLOBALS["DB"]->type)) {
case "mysql":
$sNum = "int(18)";
break;
case "oracle":
$sNum = "number(18)";
break;
case "mssql":
$sNum = "int";
break;
}
return $sNum;
}
/**
* Функция вызывается перед сохранением метаданных (настроек) свойства в БД
*
* @param $arUserField
* @return array
*/
public function PrepareSettings($arUserField)
{
return [];
}
/**
* Функция вызывается при выводе формы метаданных (настроек) свойства
*
* @param bool $arUserField
* @param $arHtmlControl
* @param $bVarsFromForm - флаг отправки формы
* @return string - HTML для вывода
*/
public function GetSettingsHTML($arUserField = false, $arHtmlControl, $bVarsFromForm)
{
return "";
}
/**
* Функция валидатор значений свойства вызвается в $GLOBALS["USER_FIELD_MANAGER"]->CheckField()
* при добавлении/изменении
*
* @param $arUserField
* @param $value - начение для проверки на валидность
* @return array - массив массивов ("id","text") ошибок
*/
public function CheckFields($arUserField, $value)
{
return [];
}
/**
* Получение склада по ID
*
* @param int $iStoreId
* @return array
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
private static function getStore(int $iStoreId)
{
Loader::includeModule("sale");
$arFilter["=ACTIVE"] = "Y";
$arFilter["=ID"] = $iStoreId;
$arStore = StoreTable::getList(array(
"filter" => $arFilter,
"select" => array(
"ID",
"TITLE",
"ADDRESS"
),
"limit" => 1,
"cache" => array(
"ttl" => TTL,
"cache_joins" => true
)
))->fetch();
return self::makeStoreArray($arStore);
}
/**
* формируем нужный формат массива Склада
*
* @param array $arStore
* @return array
*/
public static function makeStoreArray(array $arStore)
{
return array(
"ID" => $arStore["ID"],
"TITLE" => $arStore["TITLE"],
"ADDRESS" => $arStore["ADDRESS"]
);
}
/**
* Формы редактирования значения свойства
*
* @param $arUserField
* @param $arHtmlControl
* @return string - HTML для вывода
*/
public function GetEditFormHTML($arUserField, $arHtmlControl)
{
self::registrExt();
return self::getItemFieldHtml($arHtmlControl["NAME"], $arHtmlControl["VALUE"]);
}
/**
* Вывод в форме редактирования множественного поля
*
* @param $arUserField
* @param $arHtmlControl
* @return string
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
public function GetEditFormHTMLMulty($arUserField, $arHtmlControl)
{
self::registrExt();
return self::getItemFieldHtml($arHtmlControl["NAME"], $arHtmlControl["VALUE"], true);
}
/**
* Генерируем HTML код экземпляра поля
*
* @param string $sFieldName
* @param $mxValue
* @param bool $bIsMultiple
* @return string
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
private function getItemFieldHtml(string $sFieldName, $mxValue, bool $bIsMultiple = false)
{
$sFieldName = str_replace('[]', '', $sFieldName);
$sPathFromContentRoot = str_replace($_SERVER["DOCUMENT_ROOT"], "", __DIR__);
$arValues = self::getValuesArray($mxValue);
list($sReturn, $iKey) = self::generateListHtml($arValues, $sFieldName, $bIsMultiple);
$sReturn .= "<div class='".self::USER_TYPE."-filter' data-code='".$sFieldName."' data-ajaxurl='".$sPathFromContentRoot."/ajax.php' >";
$sReturn .= "<input class='".self::USER_TYPE."' type='text' data-code='".$sFieldName."' name='".$sFieldName."_TITLE' value='' placeholder='Название'>";
$sReturn .= "<input class='".self::USER_TYPE."' type='text' data-code='".$sFieldName."' name='".$sFieldName."_ADDRESS' value='' placeholder='Адрес склада'>";
$sReturn .= "</div>";
$sReturn .= "<div class='store-search-result' data-count='".($iKey + 1)."' data-multiple='".($bIsMultiple ? "true" : "false")."' data-code='".$sFieldName."'></div>";
return $sReturn;
}
/**
* Функция вызывается при выводе фильтра на странице списка
*
* @param $arUserField
* @param $arHtmlControl
* @return string - HTML для вывода
*/
public function GetFilterHTML($arUserField, $arHtmlControl)
{
return self::GetEditFormHTML($arUserField, $arHtmlControl);
}
/**
* Функция должна вернуть представление значения поля для поиска
*
* @param $arUserField
* @return string - посковое содержимое
*/
public function OnSearchIndex($arUserField)
{
if (is_array($arUserField["VALUE"])) {
return implode("\r\n", $arUserField["VALUE"]);
} else {
return $arUserField["VALUE"];
}
}
/**
* Функция вызывается перед сохранением значений в БД
*
* @param $arUserField
* @param $iValue
* @return int - значение для вставки в БД
*/
public function OnBeforeSave($arUserField, $iValue)
{
if (intval($iValue) > 0) {
return intval($iValue);
}
}
/**
* Функция вызывается при выводе значения свойства в списке элементов
*
* @param $arUserField
* @param $arHtmlControl
* @return string - HTML для вывода
*/
public function GetAdminListViewHTML($arUserField, $arHtmlControl)
{
$sReturn = "";
if (!empty($arHtmlControl["VALUE"])) {
$arStore = self::getStore($arHtmlControl["VALUE"]);
$sReturn .= "<p>[".$arStore["ID"]."] ".$arStore["TITLE"]." | ".$arStore["ADDRESS"]."</p>";
}
return $sReturn;
}
/**
* Подключаем стили и скрипты к полю
*/
private function registrExt()
{
$sPathFromContentRoot = str_replace($_SERVER["DOCUMENT_ROOT"], "", __DIR__);
CJSCore::RegisterExt("store_link", [
"js" => $sPathFromContentRoot."/js/script.js",
"css" => $sPathFromContentRoot."/css/style.css",
"rel" => ["jquery"]
]);
CJSCore::Init(["jquery", "store_link"]);
}
/**
* Генерируем HTML списка значений
*
* @param array $arValues
* @param $sFieldName
* @param bool $bIsMultiple
* @return array
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
private function generateListHtml(array $arValues, $sFieldName, bool $bIsMultiple): array
{
$iKey = 0;
$sReturn = "<ul class='".self::USER_TYPE."-list".(empty($arValues) ? " hidden" : "")."' data-code='".$sFieldName."'>";
if (!empty($arValues)) {
foreach ($arValues as $iKey => $sVal) {
if ($arStore = self::getStore($sVal)) {
$sReturn .= "<li>[".$arStore["ID"]."] ".$arStore["TITLE"]." | ".
$arStore["ADDRESS"]." <a class=\"delete\" href=\"#\">[удалить]</a>";
if ($bIsMultiple) {
$sReturn .= "<input type='hidden' name='".$sFieldName."[".$iKey."]' value='".(!empty($sVal) ? $sVal : "")."'></li>";
} else {
$sReturn .= "<input type='hidden' name='".$sFieldName."' value='".(!empty($sVal) ? $sVal : "")."'></li>";
}
}
}
}
$sReturn .= "</ul>";
return array($sReturn, $iKey);
}
} |
AJAX запросы обрабатывает файл ajax.php. В нем содержится класс CUserTypeStoreLinkAction по обработке AJAX запросов на формирование списка вариантов складов, удовольтворяющих условиям фильтра. Его код представлен ниже:
<?
namespace Project\UserTypes\StoreLink;
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
use Bitrix\Catalog\StoreTable;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Loader;
use Bitrix\Main\Application;
use Bitrix\Main\LoaderException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use Bitrix\Main\Web\Json;
final class CUserTypeStoreLinkAction
{
private $sTitle;
private $sAddress;
/**
* CUserTypeStoreLinkAction constructor.
* @throws LoaderException
* @throws SystemException
*/
public function __construct()
{
Loader::includeModule("sale");
$obRequest = Application::getInstance()->getContext()->getRequest();
$this->sTitle = $obRequest->get("title");
$this->sAddress = $obRequest->get("address");
}
/**
* Получить список складов
*
* @return array
* @throws SystemException
* @throws ArgumentException
* @throws ObjectPropertyException
*/
public function getStores()
{
$arStores = array();
$arFilter["=ACTIVE"] = "Y";
if (!empty($this->sTitle)) {
$arFilter["%TITLE"] = $this->sTitle;
}
if (!empty($this->sAddress)) {
$arFilter["%ADDRESS"] = $this->sAddress;
}
$rsStores = StoreTable::getList(array(
"filter" => $arFilter,
"select" => array(
"ID",
"TITLE",
"ADDRESS"
),
"cache" => array(
"ttl" => TTL,
"cache_joins" => true
)
));
while ($arStore = $rsStores->fetch()) {
$arStores[] = CUserTypeStoreLink::makeStoreArray($arStore);
}
return $arStores;
}
}
header("Content-Type: application/json");
$obUserTypeStoreLinkAction = new CUserTypeStoreLinkAction;
$arStores = $obUserTypeStoreLinkAction->getStores();
echo Json::encode($arStores);
?> |
Весь JS расположен в файле script.js
$(document).on("click", ".hover-variant", function () {
var sFieldCode = $(this).attr("data-code");
var iStoreId = $(this).attr("data-id");
var obList = $("ul[data-code='" + sFieldCode + "']");
var obSearchResults = $(".store-search-result[data-code='" + sFieldCode + "']");
var bIsMultilple = (obSearchResults.attr("data-multiple") === "true");
var sHiddenValueField = "<input type='hidden' name='" + sFieldCode + "' value='" + iStoreId + "' >";
var iKey = Number.parseInt($(this).parent().attr("data-count"));
var obFilter = $(".mercool_store_link-filter[data-code='" + sFieldCode + "']");
if (bIsMultilple) {
sHiddenValueField = "<input type='hidden' name='" + sFieldCode + "[" + iKey + "]' value='" + iStoreId + "' >";
}
var sValue = "<li>" + $(this).html().replace(/<[^>]+>/g, '') + sHiddenValueField + " <a class='delete' href='#'>[удалить]</a></li>";
if (bIsMultilple) {
obList.append(sValue);
$(this).parent().attr("data-count", iKey + 1);
} else {
obList.html(sValue);
}
obList.removeClass("hidden");
obSearchResults.hide();
obFilter.find("input").val("");
});
$(document).on("click", ".mercool_store_link-list .delete", function () {
var obListItem = $(this).parent();
obListItem.hide();
obListItem.find("input").val("");
return false;
});
/**
* AJAX-поиск на сервере
*
* @param {string} sFieldCode
*/
var searchStores = function (sFieldCode) {
var obSearchResults = $(".store-search-result[data-code='" + sFieldCode + "']");
var sTitle = $('input[name="' + sFieldCode + '_' + 'TITLE' + '"').val();
var sAddress = $('input[name="' + sFieldCode + '_' + 'ADDRESS' + '"').val();
var sAjaxUrl = $(".mercool_store_link-filter[data-code='" + sFieldCode + "']").attr("data-ajaxurl");
obSearchResults.html("Загрузка...");
obSearchResults.show();
BX.ajax.post(
sAjaxUrl,
{
"title": sTitle,
"address": sAddress
},
function (obData) {
obData = JSON.parse(obData);
var sVariants = "";
$.each(obData, function (iIndex, obStore) {
sVariants = sVariants + "<div class='hover-variant' data-code='" + sFieldCode + "' data-id='" + obStore.ID + "'>[" + obStore.ID + "] " + displayMatches(obStore.TITLE, sTitle) + " | " + displayMatches(obStore.ADDRESS, sAddress) + "</div>";
});
if (sVariants.length == 0) {
sVariants = "Ничего не найдено. Попробуйте изменить параметры поиска.";
}
obSearchResults.html(sVariants);
obSearchResults.removeClass("preload");
}
);
}
/**
* Отложенный вызов
*/
var delayExec = (function () {
var iTimer = 0;
return function (callback, ms) {
clearTimeout(iTimer);
iTimer = setTimeout(callback, ms);
};
})();
$(document).on("keyup", ".mercool_store_link", function () {
if ($(this).val().length >= 3) {
var sFieldCode = $(this).attr("data-code");
delayExec(function () {
searchStores(sFieldCode);
}, 600);
}
});
/**
* Выделение подстроки в строке
*
* @param {string} sFullStr
* @param {string} sSearchSubStr
*/
var displayMatches = function (sFullStr, sSearchSubStr) {
var obRegex = new RegExp(sSearchSubStr, 'gi')
var sStr = sFullStr.replace(obRegex, function (sSearchSubStr) {
return "<b>" + sSearchSubStr + "</b>"
})
return sStr;
}
|
Есть файл style.css со стилями для поля. Содержит минимальный набор стилей для корректной работы
.store-search-result {
background: white;
border: 1px solid gray;
padding: 10px;
position: absolute;
z-index: 50;
height: 150px;
overflow-y: scroll;
display: none;
}
.hover-variant {
padding: 5px 0;
cursor: pointer;
}
.hover-variant:hover {
background: lightgray;
}
.mercool_store_link-list.hidden {
display: none;
}
.mercool_store_link-filter {
display: flex;
justify-content: space-between;
}
.mercool_store_link-filter input {
width: 48%;
} |
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler("main", "OnUserTypeBuildList", array(
"\\Project\\UserTypes\\StoreLink\\CUserTypeStoreLink",
"GetUserTypeDescription"
)); |
И, наконец, немного картинок как это выглядет:
Вид в списке:
Вид в фильтре:
Надеюсь кому-то пригодится!
