Допустим, у нас есть:
-
WordPress с плагином TourMaster для управления турами
-
Таксономия партнёров в сайдбаре (отмечается партнёр по умолчанию на весь тур)
-
Для каждого дня тура есть опция "Кворум", куда нужно добавить выбор партнёра
-
Необходимо сохранять выбранного партнёра и дополнительное текстовое поле для каждого дня
-
Все данные должны улетать в Bitrix24 в кастомное поле
UF_CRM_*при публикации тура -
Возможность восстановления данных из JSON-файла при редактировании тура
Архитектура решения
Основная идея: не трогать код плагина, а работать через JavaScript, добавляя свои виджеты в уже существующие DOM-элементы.
Вот как выглядит итоговый виджет для каждого дня:
function buildDateWidget(dateIndex, allPartners, defaultPartner) {
var storageKey = 'pa_date_' + dateIndex;
var widget = document.createElement('div');
widget.className = 'pa-date-widget';
widget.setAttribute('data-storage-key', storageKey);
var select = document.createElement('select');
select.className = 'pa-partner-select';
// Добавляем опции для всех партнёров
allPartners.forEach(function (p) {
var opt = document.createElement('option');
opt.value = p.id;
opt.textContent = p.name;
select.appendChild(opt);
});
var overrideInput = document.createElement('input');
overrideInput.type = 'text';
overrideInput.className = 'pa-override-input';
overrideInput.placeholder = 'Доп. поле';
widget.appendChild(select);
widget.appendChild(overrideInput);
return widget;
}
Ключевые моменты реализации
1. Получение списка партнёров из таксономии
Партнёры хранятся в WordPress-таксономии, которая рендерится через чекбоксы с определённой структурой. Мне нужно было извлечь всех партнёров и определить, кто из них выбран по умолчанию:
function getAllPartners() {
var partners = [];
$('#partnersB24 input[name="tour_partners_json[]"]').each(function () {
var $cb = $(this);
var $label = $cb.closest('label.selectit');
partners.push({
id: $cb.val(),
name: $label.length ? $label.text().trim() : $cb.val(),
checked: $cb.is(':checked')
});
});
return partners;
}
function getDefaultPartner() {
var $cb = $('#partnersB24 input[name="tour_partners_json[]"]:checked').first();
if (!$cb.length) return null;
var $label = $cb.closest('label.selectit');
return {
id: $cb.val(),
name: $label.length ? $label.text().trim() : $cb.val()
};
}
2. Нахождение всех секций с датами
TourMaster генерирует для каждого дня отдельную вкладку с определённым классом. Моя задача — найти все такие секции и внедрить в каждую свой виджет:
function findAllKvorumFields() {
return document.querySelectorAll('.tourmaster-html-option-tabs-field[data-wrapper-slug="kworum"]');
}
3. Локальное сохранение данных
Чтобы при случайном обновлении страницы данные не терялись, я использую localStorage. Каждый виджет получает уникальный ключ:
function saveDateData(key, data) {
try {
data.ts = Date.now();
localStorage.setItem(key, JSON.stringify(data));
} catch (e) {
console.warn('[PA] localStorage error:', e);
}
}
4. Интеграция с Select2
Для улучшения пользовательского опыта я использовал библиотеку Select2, которая добавляет поиск и красивое оформление выпадающих списков:
if ($ && $.fn.select2) {
$(select).select2({
placeholder: 'Выберите партнёра',
allowClear: true,
width: '100%',
language: {
noResults: function () { return 'Партнёр не найден'; },
searching: function () { return 'Поиск...'; }
}
});
}
5. Синхронизация с Bitrix24
При изменении значения в любом виджете я обновляю скрытое поле, которое отправляется в CRM:
function syncWithBitrix(value) {
var field = document.querySelector('input[name="' + FIELD_KEY + '"]');
if (!field) {
field = document.createElement('input');
field.type = 'hidden';
field.name = FIELD_KEY;
var form = document.querySelector('form');
if (form) {
form.appendChild(field);
}
}
field.value = value || '';
field.dispatchEvent(new Event('change', { bubbles: true }));
}
6. Отправка данных при публикации
Перехватываем клик по кнопке «Опубликовать» и отправляем собранные данные через sendBeacon или fetch с keepalive:
function sendToB24In(data) {
var payload = new URLSearchParams({
action: 'sync_dates',
tour_name: data.tour_name,
partner_id: data.partner_id,
partner_name: data.partner_name,
dates: JSON.stringify(data.dates),
modified_dates: JSON.stringify(modifiedDates)
});
var blob = new Blob([payload.toString()], {
type: 'application/x-www-form-urlencoded'
});
navigator.sendBeacon('/assets/partners/b24in.php', blob);
}
7. Восстановление из JSON-файла
Отдельная история — возможность загрузить уже сохранённые данные из JSON-файла при редактировании тура:
function loadFromSyncJson() {
fetch(SYNC_JSON_URL, { cache: 'no-store' })
.then(function (res) { return res.json(); })
.then(function (records) {
// Ищем тур по названию
var tourNorm = normalizeTourName(tourName);
var matchedRecord = records.find(function(rec) {
var recNorm = normalizeTourName(rec.tour_name || '');
return recNorm === tourNorm ||
recNorm.indexOf(tourNorm) !== -1 ||
tourNorm.indexOf(recNorm) !== -1;
});
// Восстанавливаем данные для каждой даты
// ...
});
}
8. Обработка динамического добавления дат
TourMaster динамически добавляет и удаляет даты через JavaScript. Мне нужно было перехватить эти события и пересоздать виджеты:
$(document).on('click', '.tourmaster-html-option-tabs-add', function (e) {
var $parent = $(this).closest('.tourmaster-html-option-item-title');
// Проверяем, что это кнопка добавления даты, а не цены
if ($parent.length && $parent.find('b.add-pr').length) {
setTimeout(function () {
refreshAllWidgets();
}, 500);
}
});
Стилизация
Чтобы виджет выглядел органично, я добавил современные CSS-стили с анимацией при появлении:
@keyframes paFadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.pa-date-widget {
padding: 12px 14px;
border-radius: 10px;
background: linear-gradient(135deg, rgba(255,255,255,0.92), rgba(240,255,244,0.88));
backdrop-filter: blur(6px);
border: 1px solid rgba(76,175,80,0.18);
animation: paFadeIn 0.35s ease-out both;
}
Структура данных для отправки
Финальная структура данных, которая отправляется в b24in.php:
{
tour_name: "Название тура",
partner_id: "123",
partner_name: "Имя партнёра по умолчанию",
dates: [
{
date: "2024-01-15",
partner_id: "456",
partner_name: "Конкретный партнёр для дня",
override_value: "Текст из дополнительного поля",
final_partner: "Что будет показано в итоге",
is_modified: true
},
// ...
]
}
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье