Пример приложения с дополнительными контентными блоками
<?php header('Content-Type: text/html; charset=UTF-8'); $placementOptions = json_decode($_REQUEST['PLACEMENT_OPTIONS'] ?? '', true); $forceMode = ($placementOptions['force_mode'] ?? null) === 'Y'; ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.9.2/dist/jsoneditor.min.js"></script> <link href="https://cdn.jsdelivr.net/npm/jsoneditor@9.9.2/dist/jsoneditor.min.css" rel="stylesheet"> <script src="//api.bitrix24.com/api/v1/"></script> </head> <body> <div class="container-fluid"> <form id="form" class="mt-3 md-3"> <div class="row"> <div class="col-8"> <div class="mb-3"> <div class="d-flex flex-row gap-3"> <label class="form-label h3">Layout JSON</label> <div id="content_block_presets" class="d-flex flex-row gap-2"></div> </div> <div id="json_editor" style="height: 510px"></div> <input type="hidden" id="layout" value="{}"> </div> </div> <div class="col-4" id="parameters"> <label class="form-label h3">Параметры</label> <hr class="mt-0"> <div class="vstack gap-3"> <div class="form-group"> <label for="entity_type_id">Родительская сущность</label> <select id="entity_type_id" name="entityTypeId" class="form-select"> <option value="2" selected>[2] Сделка</option> <option value="1">[1] Лид</option> <option value="3">[3] Контакт</option> <option value="4">[4] Компания</option> <option value="7">[7] Предложение</option> <option value="31">[31] Счёт </option> </select> </div> <div class="form-group"> <label for="entity_id">ID родительской сущности</label> <input id="entity_id" name="entityId" type="text" class="form-control"> </div> <div class="form-group"> <label for="item_type_id" class="text-truncate">Добавляем конфигурируемые блоки в:</label> <select name="itemTypeId" id="item_type_id" class="form-select" required> <option value="1" selected>Дело</option> <option value="2">Запись таймлайна</option> </select> </div> <?php if (!$forceMode): ?> <button id="get_items_button" type="button" class="btn btn-outline-dark btn-sm">Найти</button> <?php endif; ?> <div class="form-group"> <label for="item_id">Дело:</label> <?php if ($forceMode): ?> <input id="item_id" name="itemId" type="text" class="form-control"> <?php else: ?> <select name="itemId" id="item_id" class="form-select"></select> <?php endif; ?> </div> <button id="get_button" type="button" class="btn btn-outline-dark btn-sm">Получить</button> <button id="set_button" type="button" class="btn btn-outline-dark btn-sm">Установить</button> <button id="delete_button" type="button" class="btn btn-outline-danger btn-sm">Удалить</button> </div> </div> </div> </form> </div> <div class="container-fluid" id="alert_container"></div> </body> </html> <script> const ITEM_ACTIVITY = 1; const ITEM_TIMELINE = 2; const ALLOWED_ITEM_TYPES = [ ITEM_ACTIVITY, ITEM_TIMELINE, ]; const METHODS_MAP = { [ITEM_ACTIVITY]: { get: 'crm.activity.layout.blocks.get', set: 'crm.activity.layout.blocks.set', delete: 'crm.activity.layout.blocks.delete', itemField: 'activityId', }, [ITEM_TIMELINE]: { get: 'crm.timeline.layout.blocks.get', set: 'crm.timeline.layout.blocks.set', delete: 'crm.timeline.layout.blocks.delete', itemField: 'timelineId', }, }; class ConfigurableTimelineBlocks { #jsonEditor; #statusContainer; #contentBlockPresets; #isForceMode = false; // fields #entityTypeIdNode; #entityIdNode; #itemTypeIdNode; #itemIdNode; // buttons #getButton; #setButton; #deleteButton; #getItemsButton constructor( jsonEditor, statusContainer, contentBlockPresets, ) { this.#jsonEditor = jsonEditor; this.#statusContainer = statusContainer; this.#contentBlockPresets = contentBlockPresets; this.renderJSONLayoutActions(); this.fetchProperties(); this.bindEvents(); this.loadDynamicTypes(); } fetchProperties() { this.#entityTypeIdNode = document.getElementById('entity_type_id'); this.#entityIdNode = document.getElementById('entity_id'); this.#itemTypeIdNode = document.getElementById('item_type_id'); this.#itemIdNode = document.getElementById('item_id'); this.#getButton = document.getElementById('get_button'); this.#setButton = document.getElementById('set_button'); this.#deleteButton = document.getElementById('delete_button'); this.#getItemsButton = !this.#isForceMode ? document.getElementById('get_items_button') : null; } bindEvents() { this.#getButton.onclick = this.getAction.bind(this); this.#setButton.onclick = this.setAction.bind(this); this.#deleteButton.onclick = this.deleteAction.bind(this); if (this.#getItemsButton) { this.#getItemsButton.onclick = this.getItemsAction.bind(this); this.#itemTypeIdNode.onchange = () => { this.#itemIdNode.innerHTML = ''; const label = document.querySelector(`[for="item_id"]`); label.textContent = this.getItemTypeId() === ITEM_ACTIVITY ? 'Дело' : 'Запись таймлайна'; }; } } renderJSONLayoutActions() { const contentBlockPresetsContainer = document.getElementById('content_block_presets'); if (!contentBlockPresetsContainer) { return; } contentBlockPresetsContainer.innerHTML = ''; this.#contentBlockPresets.forEach((contentBlockPreset) => { const button = document.createElement('button'); button.classList = 'btn btn-link btn-sm text-secondary'; button.innerText = contentBlockPreset.getTitle(); button.type = 'button'; button.onclick = () => { let json = this.#jsonEditor.get(); if (!json.blocks) { json.blocks = {}; } const length = Object.keys(json?.blocks).length; json.blocks[`${length + 1}`] = contentBlockPreset.getValue(); this.#jsonEditor.set(json); return false; }; contentBlockPresetsContainer.append(button); }); const clearButton = document.createElement('button'); clearButton.innerText = 'Clear'; clearButton.classList = 'btn btn-link btn-sm text-danger'; clearButton.type = 'button'; clearButton.onclick = () => { this.#jsonEditor.set({}); }; contentBlockPresetsContainer.append(clearButton); } loadDynamicTypes() { BX24.callMethod('crm.type.list', {}, (result) => { const types = result?.answer?.result?.types || []; types.forEach((item) => { const option = document.createElement("option"); option.value = item.entityTypeId; option.innerText = `[${item.id}] ${item.title}`; this.#entityTypeIdNode.append(option); }) }); } loading() { this.#entityTypeIdNode.disabled = true; this.#entityIdNode.disabled = true; this.#itemTypeIdNode.disabled = true; this.#itemIdNode.disabled = true; this.#getButton.disabled = true; this.#setButton.disabled = true; this.#deleteButton.disabled = true; } stopLoading() { this.#entityTypeIdNode.disabled = false; this.#entityIdNode.disabled = false; this.#itemTypeIdNode.disabled = false; this.#itemIdNode.disabled = false; this.#getButton.disabled = false; this.#setButton.disabled = false; this.#deleteButton.disabled = false; } getItemsAction() { if (this.#isForceMode) { return; } if (!this.validateEntityTypeIdAndEntityId()) { return; } if (this.getItemTypeId() === ITEM_ACTIVITY) { const data = { select: ['*'], filter: { 'OWNER_TYPE_ID': this.getEntityTypeId(), 'OWNER_ID': this.getEntityId(), }, }; const callback = (result) => { if (result.error()) { this.renderDangerAlert(result.error()); return; } const activities = result.data(); this.#itemIdNode.innerHTML = ''; activities.forEach((activity) => { const option = document.createElement('option'); option.innerText = `[${activity.ID}] ${activity.SUBJECT} | ${activity.PROVIDER_ID}`; option.value = activity.ID; this.#itemIdNode.append(option); }); }; BX24.callMethod('crm.activity.list', data, callback); return; } const data = { select: ['*'], filter: { 'bindings': { 'entityTypeId': this.getEntityTypeId(), 'entityId': this.getEntityId(), }, }, }; const callback = (result) => { if (result.error()) { this.renderDangerAlert(result.error()); return; } const items = result.data().items; items.forEach((item) => { let option = document.createElement('option'); const title = item?.layout?.header?.title ?? 'Undefined'; const id = item.id; option.value = id; option.innerText = `[${id}] ${title}`; this.#itemIdNode.append(option); }); }; BX24.callMethod('crm.timeline.historyitem.list', data, callback); } getAction() { const isValid = this.validateFieldsWithAlerts(); if (!isValid) { return; } const method = this.getMethod('get'); const data = this.getData(); const callback = (result) => { this.stopLoading(); if (result.error()) { this.renderDangerAlert(result.error()); return; } this.#jsonEditor.set(result.data().layout ?? {}); this.renderSuccessAlert("Готово, результат чуть выше ;)"); }; this.loading(); BX24.callMethod(method, data, callback); } setAction() { const isValid = this.validateFieldsWithAlerts(); if (!isValid) { return; } const method = this.getMethod('set'); const data = { ...this.getData(), layout: this.#jsonEditor.get(), }; const callback = (result) => { this.stopLoading(); if (result.error()) { this.renderDangerAlert(result.error()); return; } this.renderSuccessAlert("Дополнительные контентные блоки успешно установлены ;)"); }; this.loading(); BX24.callMethod(method, data, callback); } deleteAction() { const isValid = this.validateFieldsWithAlerts(); if (!isValid) { return; } const method = this.getMethod('delete'); const data = this.getData(); const callback = (result) => { this.stopLoading(); if (result.error()) { this.renderDangerAlert(result.error()); return; } this.renderSuccessAlert("Дополнительные контентные блоки успешно удалены ;)"); this.#jsonEditor.set({}); }; this.loading(); BX24.callMethod(method, data, callback); } validateFieldsWithAlerts() { if (!this.validateEntityTypeIdAndEntityId()) { return false; } const itemId = this.getItemId(); if (!itemId) { alert('Введите ID Дела/Записи таймлайна'); this.#itemIdNode.focus(); return false; } return true; } validateEntityTypeIdAndEntityId() { const entityId = this.getEntityId(); if (!entityId) { alert('Введите ID Родительской сущности'); this.#entityIdNode.focus(); return false; } if (!ALLOWED_ITEM_TYPES.includes(this.getItemTypeId())) { alert('Выберите корректное значение для типа сущности к которой будут добавлены конфигурационные блоки'); this.#itemTypeIdNode.focus(); return false; } return true; } getData() { return { entityTypeId: this.getEntityTypeId(), entityId: this.getEntityId(), [this.getItemFieldName()]: this.getItemId(), }; } getMethod(method) { return METHODS_MAP[this.getItemTypeId()][method]; } getEntityTypeId() { return Number.parseInt(this.#entityTypeIdNode.value, 10); } getEntityId() { return Number.parseInt(this.#entityIdNode.value, 10); } getItemTypeId() { return Number.parseInt(this.#itemTypeIdNode.value, 10); } getItemId() { return Number.parseInt(this.#itemIdNode.value, 10); } getItemFieldName() { return METHODS_MAP[this.getItemTypeId()].itemField; } renderAlert(message, classList) { const alert = document.createElement('div'); alert.className = classList; alert.setAttribute('role', 'alert'); const time = (new Date()).toLocaleTimeString(); alert.innerText = `[${time}] ${message}`; this.#statusContainer.innerHTML = ''; this.#statusContainer.append(alert); } renderDangerAlert(message) { this.renderAlert(message, 'alert alert-danger'); } renderSuccessAlert(message) { this.renderAlert(message, 'alert alert-success') } } class ContentBlockPreset { #title; #value; constructor(title, value) { this.#title = title; this.#value = value; } getTitle(){ return this.#title; } getValue(){ return this.#value; } } const presets = [ new ContentBlockPreset('Text', { type: "text", properties: { value: "Здравствуйте!\nМы начинаем.", multiline: true, bold: true, color: "base_90" } }), new ContentBlockPreset('Large text', { type: "largeText", properties: { value: "Здравствуйте!\nМы начинаем.\nМы продолжаем.\nМы все еще работаем над этим.\nМы продолжаем.\nМы близки к результату.\nДо свидания." } }), new ContentBlockPreset('Link', { type: "link", properties: { text: "Открыть сделку", action: { type: "redirect", uri: "/crm/deal/details/123/" }, bold: true } }), new ContentBlockPreset('With title', { type: "withTitle", properties: { title: "Заголовок", block: { type: "text", properties: { value: "Какое-то значение" } } } }), new ContentBlockPreset('With title (inline)', { type: "withTitle", properties: { title: "Заголовок 2", block: { type: "link", properties: { text: "Открыть сделку", action: { type: "redirect", uri: "/crm/deal/details/123/" } } }, inline: true } }), new ContentBlockPreset('Line of blocks', { type: "lineOfBlocks", properties: { blocks: { text: { type: "text", properties: { value: "Какой-то текст" } }, link: { type: "link", properties: { text: "ссылка", action: { type: "redirect", uri: "/crm/deal/details/123/" } } }, boldText: { type: "text", properties: { value: "жирный текст", bold: true } } } } }), new ContentBlockPreset('Deadline', { type: "deadline", properties: { readonly: false } }), ]; document.addEventListener("DOMContentLoaded", () => { const alertContainer = document.getElementById('alert_container'); const jsonEditor = new JSONEditor(document.getElementById('json_editor'), { mode: 'code', }); new ConfigurableTimelineBlocks( jsonEditor, alertContainer, presets, ); }); </script>
© «Битрикс», 2001-2024, «1С-Битрикс», 2024
Пользовательские комментарии
Мы будем рады, если разработчики добавят свои комментарии по практическому использованию методов системы.Для этого нужно всего лишь авторизоваться на сайте
Но помните, что Пользовательские комментарии, несмотря на модерацию, не являются официальной документацией. Ответственность за их использование несет сам пользователь.
Также Пользовательские комментарии не являются местом для обсуждения функционала. По подобным вопросам обращайтесь на форумы.