Структура компонента:
local/components/vendor/example/
файлы: 1.class.php -одного этого файла достаточно, чтобы компонент работал.
Вызов BX.ajax.runComponentAction
BX.ajax.runComponentAction('vendor:example', 'greet', {
mode: 'class', //это означает, что мы хотим вызывать действие из class.php, а конкретно метод greet. Может принимать ajax.php - в нем автоСвязывание идет сразу под капотом.
data: {
person: 'Hero!' //данные будут автоматически замаплены на параметры метода
},
analyticsLabel: {
viewMode: 'grid',
filterState: 'closed'
}
}).then(function (response) {
console.log(response);
/**
{
"status": "success",
"data": "Hi Hero!",
"errors": []
}
**/
}, function (response) {
//сюда будут приходить все ответы, у которых status !== 'success'
console.log(response);
/**
{
"status": "error",
"errors": [...]
}
**/
});
Получить ответ BX.ajax.runComponentAction в методе PHP
async function basketIsEmpty(productId = null) {
try {
return await BX.ajax.runComponentAction('bannikon:warehouse', 'checkBasketForEmpty', {
mode: 'class',
data: {
productId: +productId
}
});
} catch (error) {
console.error(error);
return null;
}
}
async function openModalMapHandler(button) {
// Проверяем пустая ли корзина?
let basketIsEmptyResponse = await basketIsEmpty()
// Если корзина пуста.
if (basketIsEmptyResponse.data.isEmpty === true) {
await getDataAndShowMapModal(button)
} else {
// ...
}
}
Работа с файлами и FormData
BX.ajax.runComponentAction('componentName', "actionName", {
mode: 'ajax',
data: new FormData(form)
})
// Вариант с class
BX.ajax.runComponentAction('bannikon:order.make', 'createOrder', {
mode: 'class',
data: {
formData: Object.fromEntries(new FormData(form)), // Могут быть проблемы, если есть квадратные скобки (напр. property[CITY]: "Москва"). Тогда можно использовать [...new FormData(form)]
deliveryData: this.deliveryData
}
}).then((response) => {
console.log(response)
}, function (response) {
// сюда будут приходить все ответы, у которых status !== 'success'.
console.log(response);
});
Если в форме нужно собрать несколько значений в массив, можно воспользоваться методом getAll у FormData
< form class="reg" action="" data-modal-invite>
< input type="hidden" name="actorId[]" value="1">
< input type="hidden" name="actorId[]" value="2">
< /form> form.addEventListener('submit', event => {
event.preventDefault()
let formData = new FormData(form);
let data = {
...Object.fromEntries(formData),
'actorId': formData.getAll('actorId[]') // получаем массив значений и заменяем ключ, убрав скобки [].
};
delete data['actorId[]'];
BX.ajax.runComponentAction('kochnev:invite.form', 'inviteActors', {
mode: 'class',
data: {
formData: Object.fromEntries(new FormData(form))
}
}).then((response) => {
console.log(response)
}, function (response) {
// сюда будут приходить все ответы, у которых status !== 'success'.
console.log(response);
});
})
На сервере принимаем
use \Bitrix\Main\Application;
$request = Application::getInstance()->getContext()->getRequest();
$files = $request->getFileList()->toArray();
/* или через $_FILES */
// Получить поле из $request
$element->get('NAME')
// Вариант с class
public function createOrderAction(array $formData, array $deliveryData)
{
dd($formData, $deliveryData);
}
Пример
Файл class.php
use Bitrix\Main\Engine\Contract\Controllerable; use CBitrixComponent;
<?php
namespace Vendor;
use Bitrix\Main\Diag\Debug;
use \Bitrix\Main\Engine\ActionFilter\Authentication;
use CIBlockElement;
use CModule;
use CSaleBasket;
use Bitrix\Main\Application;
use Bitrix\Main\Web\Cookie;
use Bitrix\Main\Loader;
use Bitrix\Highloadblock\HighloadBlockTable;
class RecipeFind extends \CBitrixComponent implements \Bitrix\Main\Engine\Contract\Controllerable
{
public function configureActions()
{
return [
'addToBasket' => [
'prefilters' => [],
'postfilters' => [],
],
'addOrder' => [
'prefilters' => [],
'postfilters' => [],
],
];
}
public function addToBasketAction($id, $quan, $recipe):array
{
// ...
return ['status' => 'success', 'id' => $id, $res];
}
public function addOrderAction($name, $phone, $product):array
{
// ...
return [];
}
}
BX.ajax.runComponentAction
BX.ajax.runComponentAction('vendor:component.name', 'checkRecipeProductsInBasket', {
mode: 'class',
data: {
list: list,
arProductIdInBasket: arBasketItemsId
}
}).then(function (response) {
console.log(response);
if (response.data['can_order']) {
loader(false)
switch (response.data['can_order']) {
case 0: {
disableSaveButton()
if (statusContainer.classList.contains('_confirmed')) {
statusContainer.classList.remove('_confirmed')
}
statusContainer.classList.add('_refused')
statusContainer.textContent = 'Рецепт не найден.'
checkHiddenInput.value = ''
break;
}
case 1: {
if (statusContainer.classList.contains('_refused')) {
statusContainer.classList.remove('_refused')
}
statusContainer.classList.add('_confirmed')
statusContainer.textContent = 'Ваш рецепт принят.'
checkHiddenInput.value = checkInput.value
disableSaveButton(false)
break;
}
case 2: {
disableSaveButton()
if (statusContainer.classList.contains('_confirmed')) {
statusContainer.classList.remove('_confirmed')
}
statusContainer.classList.add('_refused')
statusContainer.textContent = 'Не все товары из корзины есть в рецепте. Либо недопустимая дозировка.'
checkHiddenInput.value = ''
break;
}
}
}
}, function (response) {
// сюда будут приходить все ответы, у которых status !== 'success'
loader(false)
statusContainer.classList.add('_refused')
statusContainer.textContent = 'Ошибка запроса.'
console.log(response);
});
Как скачать файл runComponentAction
PHP
/**
* Экспортирует данные в файл
* @param int $id
* @param string $type
* @param null|array $params
* @return null|BFile
*/
public function exportAction(int $id, string $type, ?array $params = []): ?BFile
{
try {
$file = \Bitrix\Main\Engine\Response\BFile::createByFileId(985);
} catch (\Throwable $th) {
$this->addError(new Error($th->getMessage(), $th->getCode()));
}
return $file;
}
JS
BX.ajax.runComponentAction('company:sale.basket.detail', 'export', {
mode: 'ajax',
data: {id, type},
method: 'POST',
})
?.then(response => {
if (response.status === 'success' && response.data?.url) {
window.location.href = response.data.url;
}
})
.catch(response => {...})
Как скачать сгенерированный файл runComponentAction
PHP
/**
* Экспортирует данные в файл
* @param int $id
* @param string $type
* @param null|array $params
* @return null|BFile
*/
public function exportAction(int $id, string $type, ?array $params = []): ?BFile
{
try {
$pFilename = \TEMP_DIR . '/' . 'item.xls';
\file_put_contents($pFilename, $data);
$arFile = \CFile::MakeFileArray($pFilename, false, true);
$file = new BFile($arFile);
} catch (\Throwable $th) {
$this->addError(new Error($th->getMessage(), $th->getCode()));
}
return $file;
}
JS
BX.ajax.runComponentAction('company:sale.basket.detail', 'export', {
mode: 'ajax',
data: {id, type},
method: 'POST',
})
?.then(response => {
if (response.status === 'success' && response.data?.url) {
window.location.href = response.data.url;
}
})
.catch(response => {...})
Как загрузить файл runComponentAction
PHP
/**
* Импортируем файл
* @param array $data
* @param null|array $params
* @return array
*/
public function importAction(array $data, ?array $params = []): array
{
try {
...
} catch (\Throwable $th) {
$this->addError(new Error($th->getMessage(), $th->getCode()));
}
return $file;
}
JS
const data = document.getElementById();
// data =
BX.ajax.runComponentAction('company:sale.basket.detail', 'import', {
mode: 'ajax',
data: Default,
})
?.then(response => {...})
.catch(response => {...})
Пример с лоадером и валидацией
JS
document.addEventListener('DOMContentLoaded', () => {
let formFaq = document.querySelector('form[name="form_faq"]')
console.log(formFaq)
if (formFaq) {
formFaq.addEventListener('submit', sendForm)
function sendForm(e) {
e.preventDefault()
toggleLoader(formFaq, true)
BX.ajax.runComponentAction('bannikon:form', 'addFaq', {
mode: 'class',
data: new FormData(formFaq)
}).then(function (response) {
setTimeout(() => {
toggleLoader(formFaq)
if (response.data.success === true) {
toggleAlert(true)
formFaq.reset()
} else {
toggleAlert(false, response.data.error)
}
}, 2000)
}, function (response) {
//сюда будут приходить все ответы, у которых status !== 'success'
console.log(response);
setTimeout(() => {
toggleLoader(formFaq)
toggleAlert(false, response.data.error)
}, 2000)
});
}
function toggleAlert(success, error = null) {
const alertSuccess = formFaq.querySelector('.alert-success');
const alertDanger = formFaq.querySelector('.alert-danger');
if (success) {
if (!alertDanger.classList.contains('d-none')) {
alertDanger.classList.add('d-none');
}
alertSuccess.classList.remove('d-none');
formFaq.querySelector('[data-btn-submit]').disabled = true;
} else {
if (!alertSuccess.classList.contains('d-none')) {
alertSuccess.classList.add('d-none');
}
if (error !== null) {
alertDanger.textContent = error
} else {
alertDanger.textContent = 'Сервис временно недоступен. Попробуйте отправить Ваше сообщение немного позже.'
}
alertDanger.classList.remove('d-none');
}
}
function toggleLoader(elem, toggle = false) {
if (!toggle) {
elem.classList.remove('_loader')
return;
}
elem.classList.add('_loader')
}
}
});
Html
< form action="#" name="form_faq">
< div class="row">
< div class="col">
< label for="name" class="form-label">Имя*< /label>
< input type="text" class="form-control" id="name" name="name"
placeholder="Укажите Ваше имя"/>
< /div>
< div class="col">
< label for="patronymic" class="form-label">Отчество (при
наличии)< /label>
< input type="text" class="form-control" id="patronymic" name="second_name"
placeholder="Введите номер дела"/>
< /div>
< div class="col">
< label for="city" class="form-label">Город< /label>
< div class="select__box">
< select class="form-select" aria-label="" id="city" name="city">
< option selected>
---
< /option>
< option value="1">
Арзамас
< /option>
< option value="2">
Москва
< /option>
< option value="3">
Комсомольск-на-Амуре
< /option>
< /select>
< /div>
< /div>
< /div>
< div class="row">
< div class="col">
< label for="message" class="form-label">Ваш Вопрос*< /label>
< textarea class="form-control" id="message" rows="4"
placeholder="Введите текст сообщения" name="message">< /textarea>
< /div>
< /div>
< button class="col-4 btn blue" data-btn-submit
style="border: 1px solid teal;">Отправить
< /button>
< div class="alert alert-success col col-5 mt-3 d-none" role="alert">
Спасибо! Ваше сообщение отправлено.
< /div>
< div class="alert alert-danger col col-5 mt-3 d-none" role="alert">< /div>
< /form>
CSS
/*=== Loader ===*/
._loader {
position: relative;
}
._loader:before {
content: '';
background: rgba(255, 255, 255, .8);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 10;
}
._loader:after {
content: '';
width: 48px;
height: 48px;
border: 5px solid var(--blue);
border-bottom-color: var(--sand-color);
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 20;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}