custom_subtabs/custom_subtabs/public/js/custom_subtabs.js

257 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}
});