257 lines
9.4 KiB
JavaScript
257 lines
9.4 KiB
JavaScript
// Shared function for setting up subtabs
|
||
function setup_custom_subtabs(wrapper, currentDoctype) {
|
||
if (!wrapper || !wrapper.length) {
|
||
return;
|
||
}
|
||
|
||
const tabs = wrapper.find("ul.form-tabs > li.nav-item");
|
||
const hierarchy = frappe.boot.tab_hierarchy?.[currentDoctype];
|
||
|
||
if (!hierarchy) {
|
||
return;
|
||
}
|
||
|
||
const subtabs = hierarchy.subtabs || {};
|
||
|
||
if (Object.keys(subtabs).length === 0) {
|
||
return;
|
||
}
|
||
|
||
// Helper function to find tab link element
|
||
function getTabLink(tabElement) {
|
||
const $tab = $(tabElement);
|
||
let link = $tab.find("button.nav-link");
|
||
if (!link.length) {
|
||
link = $tab.find("a.nav-link");
|
||
}
|
||
return link;
|
||
}
|
||
|
||
// Check if already processed
|
||
if (wrapper.attr('data-subtabs-processed') === 'true') {
|
||
return;
|
||
}
|
||
|
||
// Build parent-child map
|
||
const childToParentMap = {};
|
||
for (const [parentTab, children] of Object.entries(subtabs)) {
|
||
children.forEach(child => {
|
||
childToParentMap[child] = parentTab;
|
||
});
|
||
}
|
||
|
||
// Process subtabs - move them into containers
|
||
tabs.each((index, tab) => {
|
||
const tabLabel = $(tab).text().trim();
|
||
|
||
if (subtabs[tabLabel]) {
|
||
const tabLink = getTabLink(tab);
|
||
const tabContentId = tabLink.attr("aria-controls");
|
||
const tabContent = $(`#${tabContentId}`);
|
||
|
||
if (!tabContent.length) {
|
||
return;
|
||
}
|
||
|
||
// Find container
|
||
let sectionContainer = tabContent.find(".form-section.card-section");
|
||
if (!sectionContainer.length) {
|
||
sectionContainer = tabContent.find(".form-section");
|
||
}
|
||
if (!sectionContainer.length) {
|
||
sectionContainer = tabContent.children().first();
|
||
}
|
||
if (!sectionContainer.length) {
|
||
sectionContainer = tabContent;
|
||
}
|
||
|
||
let subtabContainer = sectionContainer.find('.sub-tab-container');
|
||
if (!subtabContainer.length) {
|
||
subtabContainer = $("<div class='sub-tab-container nav' data-parent-id='" + tabContentId + "'></div>");
|
||
sectionContainer.prepend(subtabContainer);
|
||
}
|
||
|
||
subtabs[tabLabel].forEach((subtab) => {
|
||
const subtabElement = tabs.filter((_, el) => $(el).text().trim() === subtab);
|
||
if (subtabElement.length) {
|
||
const subtabClone = subtabElement.clone(true);
|
||
subtabClone.addClass("sub-tab");
|
||
subtabClone.attr("data-subtab-name", subtab);
|
||
const clonedLink = getTabLink(subtabClone);
|
||
clonedLink.removeAttr("href");
|
||
clonedLink.attr("data-target-id", clonedLink.attr("aria-controls"));
|
||
subtabClone.attr("data-parent-id", tabContentId);
|
||
|
||
subtabContainer.append(subtabClone);
|
||
subtabElement.remove();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// Function to show parent chain
|
||
function ensureParentChainVisible(subtabName) {
|
||
let currentChild = subtabName;
|
||
const parentsToShow = [];
|
||
|
||
// Collect parent chain
|
||
while (childToParentMap[currentChild]) {
|
||
const parentName = childToParentMap[currentChild];
|
||
parentsToShow.push(parentName);
|
||
|
||
// Show all siblings - НЕ используем .show(), используем CSS класс
|
||
const siblings = subtabs[parentName] || [];
|
||
siblings.forEach(sibling => {
|
||
const siblingElement = wrapper.find(`.sub-tab[data-subtab-name="${sibling}"]`);
|
||
siblingElement.css('display', ''); // Убираем inline style
|
||
});
|
||
|
||
currentChild = parentName;
|
||
}
|
||
|
||
// Show all parent content
|
||
parentsToShow.forEach(parentName => {
|
||
const parentTabContentId = `${currentDoctype.toLowerCase().replace(/\s+/g, '-')}-${parentName.toLowerCase().replace(/\s+/g, '_')}_tab`;
|
||
const parentContent = $(`#${parentTabContentId}`);
|
||
if (parentContent.length && !parentContent.hasClass('active')) {
|
||
parentContent.addClass('active show').css('display', 'block');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Handle subtab clicks
|
||
wrapper.on("click.customsubtabs", ".sub-tab button.nav-link, .sub-tab a.nav-link", function(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
const clickedButton = $(this);
|
||
const clickedSubtab = clickedButton.closest(".sub-tab");
|
||
const subtabName = clickedSubtab.attr("data-subtab-name");
|
||
const targetId = clickedButton.attr("data-target-id") || clickedButton.attr("aria-controls");
|
||
const parentContainerId = clickedSubtab.attr("data-parent-id");
|
||
|
||
// Temporarily disable shown.bs.tab handler
|
||
wrapper.off('shown.bs.tab.customsubtabs');
|
||
|
||
// Hide all tabs and content
|
||
wrapper.find(".nav-item").removeClass("active");
|
||
wrapper.find("button.nav-link, a.nav-link").removeClass("active").attr("aria-selected", "false");
|
||
wrapper.find(".tab-pane").removeClass("active show");
|
||
|
||
// Скрыть все контейнеры субтабов, кроме родительского
|
||
wrapper.find(".sub-tab-container").each(function() {
|
||
const containerParentId = $(this).attr("data-parent-id");
|
||
if (containerParentId !== parentContainerId) {
|
||
$(this).addClass("subtab-hidden");
|
||
} else {
|
||
$(this).removeClass("subtab-hidden");
|
||
}
|
||
});
|
||
|
||
// Activate subtab
|
||
clickedSubtab.addClass("active");
|
||
clickedButton.addClass("active").attr("aria-selected", "true");
|
||
|
||
// Show subtab content
|
||
const targetPane = $(`#${targetId}`);
|
||
if (targetPane.length) {
|
||
targetPane.addClass("active show");
|
||
}
|
||
|
||
// Show parent chain
|
||
ensureParentChainVisible(subtabName);
|
||
|
||
// Restore shown.bs.tab handler
|
||
setTimeout(() => {
|
||
setupMainTabHandler();
|
||
}, 100);
|
||
});
|
||
|
||
// Function to setup main tab handler
|
||
function setupMainTabHandler() {
|
||
// Удаляем старый обработчик
|
||
wrapper.off("shown.bs.tab.customsubtabs");
|
||
|
||
// Регистрируем новый на wrapper без делегирования
|
||
wrapper.on("shown.bs.tab.customsubtabs", function(event) {
|
||
const button = event.target;
|
||
const $button = $(button);
|
||
const li = $button.closest("li.nav-item");
|
||
|
||
// If this is a subtab - ignore
|
||
if (li.hasClass("sub-tab")) {
|
||
return;
|
||
}
|
||
|
||
const tabId = button.getAttribute("aria-controls");
|
||
|
||
console.log('shown.bs.tab сработал для:', {
|
||
text: $button.text().trim(),
|
||
tabId: tabId,
|
||
isSubtab: li.hasClass("sub-tab")
|
||
});
|
||
|
||
// ИСПРАВЛЕНИЕ: Убираем active классы со всех субтабов и их контента
|
||
wrapper.find(".sub-tab").removeClass("active");
|
||
wrapper.find(".sub-tab button.nav-link, .sub-tab a.nav-link")
|
||
.removeClass("active")
|
||
.attr("aria-selected", "false");
|
||
|
||
// Скрываем контент всех субтабов
|
||
wrapper.find(".sub-tab").each(function() {
|
||
const subtabButton = getTabLink($(this));
|
||
const subtabContentId = subtabButton.attr("data-target-id") || subtabButton.attr("aria-controls");
|
||
if (subtabContentId) {
|
||
$(`#${subtabContentId}`).removeClass("active show");
|
||
}
|
||
});
|
||
|
||
// Show/hide subtabs
|
||
wrapper.find(".sub-tab-container").each(function() {
|
||
const containerParentId = $(this).attr("data-parent-id");
|
||
console.log('Проверка контейнера:', {
|
||
containerParentId: containerParentId,
|
||
tabId: tabId,
|
||
match: containerParentId === tabId
|
||
});
|
||
if (containerParentId === tabId) {
|
||
$(this).removeClass("subtab-hidden");
|
||
} else {
|
||
$(this).addClass("subtab-hidden");
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// Initialize handler
|
||
setupMainTabHandler();
|
||
|
||
// Инициализация: скрыть все контейнеры, кроме активного таба
|
||
const activeMainTab = wrapper.find("ul.form-tabs > li.nav-item.active:not(.sub-tab)").first();
|
||
const activeTabLink = getTabLink(activeMainTab);
|
||
const activeTabId = activeTabLink.attr("aria-controls");
|
||
|
||
wrapper.find(".sub-tab-container").each(function() {
|
||
const containerParentId = $(this).attr("data-parent-id");
|
||
if (containerParentId !== activeTabId) {
|
||
$(this).addClass("subtab-hidden");
|
||
}
|
||
});
|
||
|
||
// Mark as processed
|
||
wrapper.attr('data-subtabs-processed', 'true');
|
||
}
|
||
|
||
// Use form refresh event
|
||
frappe.ui.form.on('*', {
|
||
refresh: function(frm) {
|
||
setTimeout(() => {
|
||
const wrapper = frm.layout?.wrapper || frm.$wrapper;
|
||
setup_custom_subtabs(wrapper, frm.doctype);
|
||
}, 500);
|
||
},
|
||
|
||
onload: function(frm) {
|
||
// Form onload hook
|
||
}
|
||
}); |