From 76b178b93a180a4d9c9ade155767e3e1c746c9c4 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 9 Dec 2025 15:52:37 +0400 Subject: [PATCH] fixed subtabs for v16 --- ARCHITECTURE.md | 783 +++++++++++++++++++ custom_subtabs/public/css/custom_subtabs.css | 52 +- custom_subtabs/public/js/custom_subtabs.js | 328 ++++---- 3 files changed, 1010 insertions(+), 153 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..0dfca45 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,783 @@ +# Custom Subtabs - Подробная документация по архитектуре + +## Обзор приложения + +**Custom Subtabs** — это приложение для Frappe Framework, которое добавляет возможность создания вложенных вкладок (подвкладок) в формах DocType. Приложение позволяет организовать вкладки иерархически, когда одна вкладка может содержать другие подвкладки. + +### Основные возможности: +- Создание иерархии вкладок (parent-child отношения) +- Автоматическое перемещение подвкладок под родительскую вкладку +- Визуальное отображение подвкладок с отдельным стилем +- Поддержка Frappe v15 и v16 +- Динамическая загрузка конфигурации через boot session + +--- + +## Архитектура системы + +### Общая схема работы + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Frappe Backend │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ boot_session_handler │ │ +│ │ - Сканирует все DocType │ │ +│ │ - Находит Tab Break поля с js_parent_subtab │ │ +│ │ - Создает tab_hierarchy структуру │ │ +│ │ - Передает данные на клиент через bootinfo │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ add_parent_field.py (patch) │ │ +│ │ - Модифицирует docfield.json │ │ +│ │ - Добавляет поле js_parent_subtab в DocField │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + ↓ + frappe.boot.tab_hierarchy + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ Frappe Frontend (Browser) │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ custom_subtabs.js │ │ +│ │ - Переопределяет setup_tab_events() │ │ +│ │ - Обрабатывает form refresh события │ │ +│ │ - Читает tab_hierarchy из frappe.boot │ │ +│ │ - Перемещает подвкладки в контейнеры │ │ +│ │ - Обрабатывает клики по вкладкам │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ custom_subtabs.css │ │ +│ │ - Стилизует sub-tab-container │ │ +│ │ - Определяет внешний вид подвкладок │ │ +│ │ - Активное состояние подвкладок │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Компоненты приложения + +### 1. Backend компоненты + +#### 1.1 `hooks.py` +**Расположение:** `custom_subtabs/hooks.py` + +Основной файл конфигурации приложения: +- Подключает CSS и JS файлы к desk.html +- Регистрирует `boot_session_handler` для передачи данных на клиент +- Регистрирует патчи для выполнения после миграции + +```python +app_include_css = "/assets/custom_subtabs/css/custom_subtabs.css" +app_include_js = "/assets/custom_subtabs/js/custom_subtabs.js" +boot_session = "custom_subtabs.custom_subtabs.boot_session_handler" +after_migrate = ["custom_subtabs.patches.add_parent_field.add_field_to_docfield_json"] +``` + +#### 1.2 `boot_session_handler` +**Расположение:** `custom_subtabs/custom_subtabs/__init__.py` + +**Назначение:** Подготавливает данные о структуре вкладок для всех DocType и передает их на клиент. + +**Алгоритм работы:** +1. Получает список всех DocType в системе +2. Для каждого DocType: + - Получает метаданные через `frappe.get_meta()` + - Ищет поля с `fieldtype == "Tab Break"` + - Для каждой вкладки проверяет наличие `js_parent_subtab` + - Строит структуру `tabs` (основные вкладки) и `subtabs` (подвкладки) +3. Формирует объект `tab_hierarchy_data` вида: + ```javascript + { + "Customer": { + "tabs": ["Details", "Settings"], + "subtabs": { + "Settings": ["Email Settings", "Privacy Settings"] + } + }, + "Item": { + "tabs": ["Basic", "Pricing"], + "subtabs": { + "Pricing": ["Discounts", "Tax"] + } + } + } + ``` +4. Передает через `bootinfo.tab_hierarchy` на клиент + +**Пример структуры:** +```python +tab_hierarchy_data = { + "Customer": { + "tabs": ["Details", "Settings", "Contact"], + "subtabs": { + "Settings": ["Email Settings", "Privacy Settings"] + } + } +} +``` + +#### 1.3 `add_parent_field.py` (Patch) +**Расположение:** `custom_subtabs/patches/add_parent_field.py` + +**Назначение:** Модифицирует основной DocField DocType, добавляя поле `js_parent_subtab`. + +**Что делает:** +1. Находит файл `frappe/core/doctype/docfield/docfield.json` +2. Добавляет новое поле: + ```json + { + "fieldname": "js_parent_subtab", + "fieldtype": "Data", + "label": "JS Parent Subtab", + "description": "Укажите имя родительского таба" + } + ``` +3. Вставляет поле после поля `reqd` в определении DocField +4. Сохраняет изменения в JSON файл + +**Зачем это нужно:** +После выполнения этого патча, в интерфейсе настройки любого DocType, при добавлении поля Tab Break, появится дополнительное поле "JS Parent Subtab", где можно указать имя родительской вкладки. + +#### 1.4 `custom_field.py` (Override) +**Расположение:** `custom_subtabs/overrides/custom_field.py` + +**Статус:** Закомментирован / не используется + +Исходно был создан для автоматической настройки вложенных вкладок через override CustomField, но в текущей реализации не используется. + +#### 1.5 `utils.py` +**Расположение:** `custom_subtabs/utils.py` + +**Статус:** Содержит функцию `process_nested_tabs`, которая не используется в текущей реализации + +#### 1.6 `delete_custom_field.py` +**Расположение:** `custom_subtabs/delete_custom_field.py` + +Утилита для удаления Custom Field `DocField-js_parent_subtab` (если он был создан через Custom Field вместо модификации JSON). + +--- + +### 2. Frontend компоненты + +#### 2.1 `custom_subtabs.js` +**Расположение:** `custom_subtabs/public/js/custom_subtabs.js` + +Основной JavaScript файл, реализующий логику подвкладок на клиенте. + +**Структура кода:** + +##### A. Переопределение `frappe.ui.form.Layout.prototype.setup_tab_events` + +```javascript +const original_setup_tab_events = frappe.ui.form.Layout.prototype.setup_tab_events; + +frappe.ui.form.Layout.prototype.setup_tab_events = function () { + // Вызов оригинальной функции + original_setup_tab_events.call(this); + + setTimeout(() => { + // Логика обработки подвкладок + }, 500); +}; +``` + +**Зачем переопределяется:** Frappe вызывает `setup_tab_events()` при рендеринге формы. Мы перехватываем этот вызов, чтобы добавить свою логику после стандартной обработки вкладок. + +##### B. Альтернативный подход через `frappe.ui.form.on('*')` + +```javascript +frappe.ui.form.on('*', { + refresh: function(frm) { + setTimeout(() => { + setup_custom_subtabs(frm); + }, 500); + } +}); +``` + +**Зачем два подхода:** +- Первый перехватывает встроенную логику Frappe +- Второй использует event-based подход для совместимости с v16 +- Оба могут работать одновременно для максимальной совместимости + +##### C. Функция `setup_custom_subtabs(frm)` + +**Основная логика:** + +1. **Получение данных:** + ```javascript + const currentDoctype = frm.doctype; + const hierarchy = frappe.boot.tab_hierarchy?.[currentDoctype]; + const subtabs = hierarchy.subtabs || {}; + ``` + +2. **Поиск всех вкладок:** + ```javascript + const tabs = wrapper.find("ul.form-tabs > li.nav-item"); + ``` + +3. **Обработка каждой родительской вкладки:** + ```javascript + tabs.each((index, tab) => { + const tabLabel = $(tab).text().trim(); + + if (subtabs[tabLabel]) { + // Это родительская вкладка с подвкладками + } + }); + ``` + +4. **Создание контейнера для подвкладок:** + ```javascript + subtabContainer = $(""); + sectionContainer.append(subtabContainer); + ``` + +5. **Перемещение подвкладок:** + ```javascript + subtabs[tabLabel].forEach((subtab) => { + const subtabElement = tabs.filter((_, el) => $(el).text().trim() === subtab); + + if (subtabElement.length) { + const subtabClone = subtabElement.clone(); + subtabClone.addClass("sub-tab"); + subtabContainer.append(subtabClone); + subtabElement.remove(); // Удаляем из основного nav + } + }); + ``` + +##### D. Helper функции + +**`getTabLink(tabElement)`** +```javascript +function getTabLink(tabElement) { + const $tab = $(tabElement); + let link = $tab.find("button.nav-link"); + if (!link.length) { + link = $tab.find("a.nav-link"); + } + return link; +} +``` +**Назначение:** Поддержка v15 (используются ``) и v16 (используются ` + + +
+ +
+``` + +### Атрибут data-parent-id + +Контейнер подвкладок хранит ID родительской вкладки: +```html +
+ +
+``` + +**Использование:** +Когда пользователь кликает на основную вкладку, скрипт ищет все `.sub-tab-container` с соответствующим `data-parent-id` и показывает их. + +### Таймауты + +```javascript +setTimeout(() => { + setup_custom_subtabs(frm); +}, 500); +``` + +**Зачем:** Frappe рендерит вкладки асинхронно. Таймаут дает время для завершения рендеринга перед обработкой подвкладок. + +### Event delegation + +```javascript +wrapper.off("click", ".sub-tab").on("click", ".sub-tab", function () { ... }); +``` + +**Зачем `off()` перед `on()`:** Предотвращает дублирование обработчиков при повторном вызове функции (например, при refresh формы). + +--- + +## Ограничения и известные проблемы + +### 1. Имена вкладок должны быть уникальными +Скрипт использует `text().trim()` для поиска вкладок. Если две вкладки имеют одинаковое имя, возможны конфликты. + +### 2. Производительность при большом количестве DocType +`boot_session_handler` сканирует все DocType при каждом входе пользователя. При большом количестве DocType (>1000) это может замедлить загрузку. + +**Решение:** Добавить кэширование или фильтрацию только нужных DocType. + +### 3. Язык интерфейса +Имена вкладок должны совпадать точно, включая локализацию. Если вкладка переведена, нужно использовать переведенное имя в `js_parent_subtab`. + +### 4. Динамическое добавление вкладок +Если вкладки добавляются динамически через JS после загрузки формы, подвкладки могут не обработаться. + +**Решение:** Вызвать `setup_custom_subtabs(frm)` вручную после добавления вкладок. + +--- + +## Расширение функциональности + +### Добавление новых стилей + +Отредактировать `custom_subtabs/public/css/custom_subtabs.css`: + +```css +.sub-tab-container .sub-tab.active button { + color: #ff5733 !important; /* Красный цвет для активной подвкладки */ +} +``` + +### Поддержка вертикальных вкладок + +Изменить `.sub-tab-container`: +```css +.sub-tab-container { + flex-direction: column !important; +} +``` + +### Иконки для подвкладок + +Добавить логику в JS: +```javascript +const iconMapping = { + "Email Settings": "fa-envelope", + "Privacy Settings": "fa-lock" +}; + +subtabClone.prepend(``); +``` + +--- + +## Отладка + +### Включение логов + +Скрипт содержит множество `console.log()` для отладки: +```javascript +console.log("Hierarchy for doctype:", hierarchy); +console.log("Processing parent tab:", tabLabel); +console.log("Looking for subtab:", subtab, "found:", subtabElement.length); +``` + +**Открыть консоль браузера (F12)** и проверить логи при загрузке формы. + +### Проверка tab_hierarchy + +В консоли браузера: +```javascript +console.log(frappe.boot.tab_hierarchy); +``` + +**Ожидаемый результат:** +```javascript +{ + "Customer": { + "tabs": ["Details", "Settings"], + "subtabs": { + "Settings": ["Email Settings", "Privacy Settings"] + } + } +} +``` + +### Проверка DOM структуры + +```javascript +// Проверить все контейнеры подвкладок +$(".sub-tab-container").each(function() { + console.log("Parent ID:", $(this).attr("data-parent-id")); + console.log("Subtabs count:", $(this).find(".sub-tab").length); +}); +``` + +--- + +## Заключение + +Custom Subtabs — это полноценное приложение для организации иерархических вкладок в Frappe. Оно использует: +- **Backend:** Python для генерации конфигурации и модификации DocField +- **Frontend:** JavaScript для динамической перестройки DOM и обработки событий +- **CSS:** Для стилизации подвкладок + +Приложение полностью интегрируется с Frappe Framework и поддерживает версии v15 и v16. + +--- + +## Контакты + +- **Автор:** Jey Soft +- **Email:** info@jeyerp.az +- **Лицензия:** MIT diff --git a/custom_subtabs/public/css/custom_subtabs.css b/custom_subtabs/public/css/custom_subtabs.css index d55088f..109b82d 100644 --- a/custom_subtabs/public/css/custom_subtabs.css +++ b/custom_subtabs/public/css/custom_subtabs.css @@ -13,10 +13,12 @@ padding: 0 !important; } -.sub-tab-container .sub-tab a { +/* Support both
and