# πŸ“˜ Руководство ΠΏΠΎ JavaScript модулям EUSRR **ВСрсия**: 1.0 **Π”Π°Ρ‚Π°**: 4 ноября 2025 Π³. --- ## πŸ“š Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠ°Π½ΠΈΠ΅ 1. [ΠžΠ±Π·ΠΎΡ€ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹](#ΠΎΠ±Π·ΠΎΡ€-Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹) 2. [Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ (utils)](#ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹) 3. [ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ (components)](#ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹) 4. [ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования](#ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹-использования) 5. [Best Practices](#best-practices) --- ## πŸ—οΈ ΠžΠ±Π·ΠΎΡ€ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹ ### Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Ρ„Π°ΠΉΠ»ΠΎΠ² ``` static/js/ β”œβ”€β”€ utils/ # Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ β”‚ β”œβ”€β”€ stringUtils.js # Π Π°Π±ΠΎΡ‚Π° со строками β”‚ β”œβ”€β”€ timing.js # Debounce, throttle, delay β”‚ └── scroll.js # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ скроллом └── components/ # ΠŸΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ β”œβ”€β”€ calendarWidget.js # Π’ΠΈΠ΄ΠΆΠ΅Ρ‚ калСндаря β”œβ”€β”€ deptRoleEditor.js # Π Π΅Π΄Π°ΠΊΡ‚ΠΎΡ€ Ρ€ΠΎΠ»Π΅ΠΉ ΠΎΡ‚Π΄Π΅Π»ΠΎΠ² β”œβ”€β”€ employeeFormHandler.js # Π€ΠΎΡ€ΠΌΠ° сотрудника β”œβ”€β”€ employeeGroupsManager.js # Π“Ρ€ΡƒΠΏΠΏΡ‹ сотрудника β”œβ”€β”€ groupPickers.js # Π’Ρ‹Π±ΠΎΡ€ Π³Ρ€ΡƒΠΏΠΏ долТностСй β”œβ”€β”€ headPicker.js # Autocomplete руководитСля β”œβ”€β”€ likeHandler.js # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Π»Π°ΠΉΠΊΠΎΠ² β”œβ”€β”€ listFilter.js # Π€ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ списков β”œβ”€β”€ navbarHeight.js # CSS пСрСмСнная navbar β”œβ”€β”€ positionManager.js # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ долТностями β”œβ”€β”€ roleManager.js # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ролями β”œβ”€β”€ skillsManager.js # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Π½Π°Π²Ρ‹ΠΊΠ°ΠΌΠΈ β”œβ”€β”€ teamWheel.js # КолСсо ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ β”œβ”€β”€ textareaAutogrow.js # АвтоувСличСниС textarea └── index.js # Экспорт всСх ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ² ``` --- ## πŸ› οΈ Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ ### `stringUtils.js` Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ со строками ΠΈ HTML. #### `esc(s: string): string` Π­ΠΊΡ€Π°Π½ΠΈΡ€ΡƒΠ΅Ρ‚ HTML-символы для бСзопасного Π²Ρ‹Π²ΠΎΠ΄Π°. ```javascript import { esc } from '{% static "js/utils/stringUtils.js" %}'; const userInput = ''; const safe = esc(userInput); // => '<script>alert("xss")</script>' ``` #### `norm(s: string): string` НормализуСт строку для поиска (lowercase, trim). ```javascript import { norm } from '{% static "js/utils/stringUtils.js" %}'; const search = norm(' Hello World '); // => 'hello world' ``` #### `escAttr(s: string): string` Π­ΠΊΡ€Π°Π½ΠΈΡ€ΡƒΠ΅Ρ‚ строку для использования Π² Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°Ρ…. ```javascript const attr = escAttr('some "quoted" text'); // => 'some "quoted" text' ``` #### `truncate(s: string, maxLen: number): string` ΠžΠ±Ρ€Π΅Π·Π°Π΅Ρ‚ строку Π΄ΠΎ ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠΉ Π΄Π»ΠΈΠ½Ρ‹ с Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ "...". ```javascript const short = truncate('Very long text here', 10); // => 'Very lon...' ``` #### `getInitials(name: string): string` ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»Ρ‹ ΠΈΠ· ΠΈΠΌΠ΅Π½ΠΈ. ```javascript const initials = getInitials('Иван ΠŸΠ΅Ρ‚Ρ€ΠΎΠ²ΠΈΡ‡ Π‘ΠΈΠ΄ΠΎΡ€ΠΎΠ²'); // => 'ИПБ' ``` --- ### `timing.js` Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для управлСния Π²Ρ€Π΅ΠΌΠ΅Π½Π΅ΠΌ выполнСния. #### `debounce(fn: Function, ms: number): Function` ΠžΡ‚ΠΊΠ»Π°Π΄Ρ‹Π²Π°Π΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π΄ΠΎ прСкращСния Π²Ρ‹Π·ΠΎΠ²ΠΎΠ². ```javascript import { debounce } from '{% static "js/utils/timing.js" %}'; const search = debounce((query) => { console.log('Searching for:', query); }, 300); input.addEventListener('input', (e) => search(e.target.value)); ``` #### `throttle(fn: Function, ms: number): Function` ΠžΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ частоту Π²Ρ‹Π·ΠΎΠ²Π° Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ. ```javascript import { throttle } from '{% static "js/utils/timing.js" %}'; const onScroll = throttle(() => { console.log('Scroll position:', window.scrollY); }, 100); window.addEventListener('scroll', onScroll); ``` #### `delay(ms: number): Promise` ΠŸΡ€ΠΎΠΌΠΈΡΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Π½Π½Π°Ρ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°. ```javascript import { delay } from '{% static "js/utils/timing.js" %}'; async function animateSequence() { element.classList.add('step1'); await delay(300); element.classList.add('step2'); await delay(300); element.classList.add('complete'); } ``` --- ### `scroll.js` Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для управлСния скроллом. #### `smoothScrollTo(element: HTMLElement, options?: Object): void` Плавно ΠΏΡ€ΠΎΠΊΡ€ΡƒΡ‡ΠΈΠ²Π°Π΅Ρ‚ ΠΊ элСмСнту. ```javascript import { smoothScrollTo } from '{% static "js/utils/scroll.js" %}'; const target = document.getElementById('section'); smoothScrollTo(target, { offset: 80, // ΠžΡ‚ΡΡ‚ΡƒΠΏ свСрху duration: 500 // Π”Π»ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠΈ }); ``` #### `scrollToTop(duration: number = 300): void` Плавно ΠΏΡ€ΠΎΠΊΡ€ΡƒΡ‡ΠΈΠ²Π°Π΅Ρ‚ страницу Π½Π°Π²Π΅Ρ€Ρ…. ```javascript button.addEventListener('click', () => scrollToTop(500)); ``` #### `isElementInViewport(el: HTMLElement): boolean` ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚, Π²ΠΈΠ΄Π΅Π½ Π»ΠΈ элСмСнт Π² ΠΎΠΊΠ½Π΅ просмотра. ```javascript if (isElementInViewport(element)) { element.classList.add('animate'); } ``` --- ## 🎨 ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ ### `navbarHeight.js` УстанавливаСт CSS-ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ с высотой Π½Π°Π²Π±Π°Ρ€Π°. #### API ```javascript import { initNavbarHeight } from '{% static "js/components/navbarHeight.js" %}'; const navbarHeight = initNavbarHeight(); // АвтоматичСски создаёт CSS ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΡƒΡŽ --navbar-height ``` **ИспользованиС Π² CSS:** ```css .content { margin-top: var(--navbar-height); } ``` --- ### `likeHandler.js` Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Π»Π°ΠΉΠΊΠΎΠ² для постов ΠΈ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠ΅Π². #### API ```javascript import { initLikeHandler } from '{% static "js/components/likeHandler.js" %}'; const likeHandler = initLikeHandler({ postUrl: '/api/v1/feed/posts/{id}/like/', commentUrl: '/api/v1/feed/comments/{id}/like/' }); ``` #### HTML Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠ° ```django ``` #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `refresh()` - пСрСинициализация ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ² --- ### `textareaAutogrow.js` АвтоматичСски ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ высоту textarea ΠΏΡ€ΠΈ Π²Π²ΠΎΠ΄Π΅ тСкста. #### API ```javascript import { initTextareaAutogrow } from '{% static "js/components/textareaAutogrow.js" %}'; const autogrow = initTextareaAutogrow({ selector: '[data-autogrow]', minHeight: 40, maxHeight: 300 }); ``` #### HTML Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠ° ```html ``` #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `refresh()` - пСрСинициализация для Π½ΠΎΠ²Ρ‹Ρ… элСмСнтов - `destroy()` - ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ² --- ### `employeeFormHandler.js` УправляСт Ρ„ΠΎΡ€ΠΌΠΎΠΉ сотрудника (валидация, измСнСния, AJAX). #### API ```javascript import { initEmployeeForm } from '{% static "js/components/employeeFormHandler.js" %}'; const employeeForm = initEmployeeForm(); ``` #### ВозмоТности - βœ… ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ ΠΏΠΎΠ»Π΅ΠΉ - βœ… Валидация ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ - βœ… ΠŸΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ ΠΎ нСсохранённых измСнСниях - βœ… AJAX ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠ° Ρ„ΠΎΡ€ΠΌΡ‹ - βœ… ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `hasChanges()` - ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ - `resetTracking()` - сброс отслСТивания - `validateForm()` - валидация Ρ„ΠΎΡ€ΠΌΡ‹ - `isDirty` - Ρ„Π»Π°Π³ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ --- ### `positionManager.js` УправляСт CRUD опСрациями с долТностями Ρ‡Π΅Ρ€Π΅Π· ΠΌΠΎΠ΄Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΎΠΊΠ½Π°. #### API ```javascript import { initPositionManager } from '{% static "js/components/positionManager.js" %}'; const positionMgr = initPositionManager(); ``` #### HTML трСбования - `#positionBlock[data-update-url-tpl]` - шаблон URL обновлСния - `#positionBlock[data-detail-url-tpl]` - шаблон URL получСния - `#positionBlock[data-create-url]` - URL создания - ΠœΠΎΠ΄Π°Π»Ρ‹: `#posEditModal`, `#posCreateModal` #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `refresh()` - пСрСинициализация - `openCreateModal()` - ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΌΠΎΠ΄Π°Π» создания - `openEditModal(positionId)` - ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΌΠΎΠ΄Π°Π» рСдактирования --- ### `groupPickers.js` ΠŸΠΈΠΊΠ΅Ρ€Ρ‹ для Π²Ρ‹Π±ΠΎΡ€Π° Π³Ρ€ΡƒΠΏΠΏ долТностСй (созданиС ΠΈ Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅). #### API ```javascript import { initPositionGroupPicker, initPositionEditGroupPicker } from '{% static "js/components/groupPickers.js" %}'; // Для Ρ„ΠΎΡ€ΠΌΡ‹ создания долТности const createPicker = initPositionGroupPicker(); // Для Ρ„ΠΎΡ€ΠΌΡ‹ рСдактирования долТности const editPicker = initPositionEditGroupPicker(); ``` #### ВозмоТности - βœ… Π’Ρ‹Π±ΠΎΡ€ Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… Π³Ρ€ΡƒΠΏΠΏ - βœ… ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Ρ‡ΠΈΠΏΠΎΠ² Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹Ρ… Π³Ρ€ΡƒΠΏΠΏ - βœ… Поиск ΠΏΠΎ Π³Ρ€ΡƒΠΏΠΏΠ°ΠΌ - βœ… Бинхронизация с hidden input'Π°ΠΌΠΈ --- ### `employeeGroupsManager.js` УправляСт ΠΏΠ΅Ρ€ΡΠΎΠ½Π°Π»ΡŒΠ½Ρ‹ΠΌΠΈ Π³Ρ€ΡƒΠΏΠΏΠ°ΠΌΠΈ ΠΏΡ€Π°Π² сотрудника. #### API ```javascript import { initEmployeeGroupsManager } from '{% static "js/components/employeeGroupsManager.js" %}'; const groupsMgr = initEmployeeGroupsManager(); ``` #### HTML трСбования - `#empGroupsAssignBtn` - ΠΊΠ½ΠΎΠΏΠΊΠ° назначСния Π³Ρ€ΡƒΠΏΠΏ - `#empGroupsList` - список Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΡ… Π³Ρ€ΡƒΠΏΠΏ - `#empGroupsModal` - ΠΌΠΎΠ΄Π°Π» Π²Ρ‹Π±ΠΎΡ€Π° Π³Ρ€ΡƒΠΏΠΏ #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `refresh()` - ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ списка - `assignGroups(groupIds)` - Π½Π°Π·Π½Π°Ρ‡ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΡ‹ - `removeGroup(groupId)` - ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΡƒ --- ### `skillsManager.js` УправляСт Π½Π°Π²Ρ‹ΠΊΠ°ΠΌΠΈ сотрудника (Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅/ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ Ρ‡Π΅Ρ€Π΅Π· AJAX). #### API ```javascript import { initSkillsManager } from '{% static "js/components/skillsManager.js" %}'; const skillsManager = initSkillsManager({ blockId: 'skillsBlock', formId: 'skillAddForm', collapseId: 'skillsForm', removeUrl: '/employees/1/skills/remove/' }); ``` #### ВозмоТности - βœ… AJAX Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ Π½Π°Π²Ρ‹ΠΊΠΎΠ² - βœ… AJAX ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΠΊΠ»ΠΈΠΊΡƒ Π½Π° Ρ‡ΠΈΠΏ - βœ… Анимация добавлСния/удалСния - βœ… Валидация Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚ΠΎΠ² #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `addSkill(name)` - Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π½Π°Π²Ρ‹ΠΊ (Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Promise) - `refresh()` - пСрСинициализация --- ### `deptRoleEditor.js` Π Π΅Π΄Π°ΠΊΡ‚ΠΎΡ€ Ρ€ΠΎΠ»ΠΈ сотрудника Π² ΠΎΡ‚Π΄Π΅Π»Π΅ с Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠ΅ΠΉ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ. #### API ```javascript import { initDeptRoleEditor } from '{% static "js/components/deptRoleEditor.js" %}'; const deptRoleEditor = initDeptRoleEditor(); ``` #### HTML Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠ° ```html
``` #### ВозмоТности - βœ… Активация ΠΊΠ½ΠΎΠΏΠΊΠΈ OK Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ - βœ… Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ tooltip'ΠΎΠ² - βœ… ΠœΠ½ΠΎΠΆΠ΅ΡΡ‚Π²Π΅Π½Π½Ρ‹Π΅ экзСмпляры Π½Π° страницС #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `instances` - массив всСх Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΎΡ€ΠΎΠ² - `refresh()` - пСрСинициализация --- ### `headPicker.js` Autocomplete для Π²Ρ‹Π±ΠΎΡ€Π° руководитСля ΠΎΡ‚Π΄Π΅Π»Π° ΠΏΠΎ ΠΈΠΌΠ΅Π½ΠΈ ΠΈΠ»ΠΈ email. #### API ```javascript import { initHeadPicker } from '{% static "js/components/headPicker.js" %}'; const headPicker = initHeadPicker({ selector: '[data-head-picker]' }); ``` #### HTML трСбования ```html
``` #### ВозмоТности - βœ… Поиск ΠΏΠΎ ΠΈΠΌΠ΅Π½ΠΈ ΠΈ email - βœ… Валидация Π²Ρ‹Π±ΠΎΡ€Π° ΠΏΡ€ΠΈ submit - βœ… АвтозаполнСниС ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ - βœ… ΠšΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Π½Π°Ρ навигация (Enter, Escape) --- ### `roleManager.js` Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ролями ΠΈ ΠΏΡ€Π°Π²Π°ΠΌΠΈ ΠΎΡ‚Π΄Π΅Π»ΠΎΠ² (созданиС, Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅, ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅). #### API ```javascript import { initRoleManager } from '{% static "js/components/roleManager.js" %}'; const roleManager = initRoleManager(); ``` #### ВозмоТности - βœ… Autocomplete Π²Ρ‹Π±ΠΎΡ€Π° Ρ€ΠΎΠ»ΠΈ - βœ… Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΡ€Π°Π²Π°ΠΌΠΈ Ρ‡Π΅Ρ€Π΅Π· Ρ‡ΠΈΠΏΡ‹ - βœ… Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π½ΠΎΠ²ΠΎΠΉ Ρ€ΠΎΠ»ΠΈ - βœ… Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅ΠΉ Ρ€ΠΎΠ»ΠΈ - βœ… Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ Ρ€ΠΎΠ»ΠΈ с ΠΏΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ΠΌ - βœ… ΠœΠΎΠ΄Π°Π»Ρ‹ с Π²Ρ‹Π±ΠΎΡ€ΠΎΠΌ ΠΏΡ€Π°Π² (permissions) #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `refresh()` - пСрСинициализация - `getSelectedRole()` - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π²Ρ‹Π±Ρ€Π°Π½Π½ΡƒΡŽ Ρ€ΠΎΠ»ΡŒ - `getEditPermissions()` - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹Π΅ ΠΏΡ€Π°Π²Π° рСдактирования - `getCreatePermissions()` - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹Π΅ ΠΏΡ€Π°Π²Π° создания --- ### `calendarWidget.js` ΠŸΠΎΠ»Π½ΠΎΡ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ калСндаря с FullCalendar.js. #### API ```javascript import { initCalendarWidget } from '{% static "js/components/calendarWidget.js" %}'; const widget = initCalendarWidget(); ``` #### ВозмоТности - βœ… ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ событий - βœ… Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π½ΠΎΠ²Ρ‹Ρ… событий - βœ… Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ событий - βœ… Drag & drop событий - βœ… Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ с ΠΌΠΎΠ΄Π°Π»Π°ΠΌΠΈ - βœ… AJAX сохранСниС --- ### `teamWheel.js` АнимированноС колСсо ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΎΡ‚Π΄Π΅Π»Π° с автоскроллом. #### API ```javascript import { initTeamWheel } from '{% static "js/components/teamWheel.js" %}'; const wheel = initTeamWheel({ wheelId: 'teamWheel-1', dataSourceId: 'teamData-1', fallback: { avatar: '/static/img/default-avatar.png', name: 'Π ΡƒΠΊΠΎΠ²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒ', role: 'Π ΡƒΠΊΠΎΠ²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒ ΠΎΡ‚Π΄Π΅Π»Π°' } }); ``` #### Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ API - `start()` - Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ автоскролл - `stop()` - ΠΎΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ автоскролл - `next()` - ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΉ участник - `prev()` - ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΠΉ участник --- ### `listFilter.js` Π£Π½ΠΈΠ²Π΅Ρ€ΡΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΠΈ списков. #### API ```javascript import { ListFilter, createDataAttrMatcher } from '{% static "js/components/listFilter.js" %}'; new ListFilter({ listSelector: '#empList', itemSelector: '.emp-row', searchInputSelector: '#empFilter', clearButtonSelector: '.ios-search-clear', matchFn: createDataAttrMatcher(['name', 'depts']), debounceMs: 300 }); ``` #### ВозмоТности - βœ… Debounced поиск - βœ… ΠšΠ°ΡΡ‚ΠΎΠΌΠ½Π°Ρ функция сопоставлСния - βœ… ΠŸΠΎΠ΄ΡΡ‡Ρ‘Ρ‚ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² - βœ… Кнопка очистки - βœ… ΠšΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Π½Ρ‹Π΅ события --- ## πŸ’‘ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования ### ΠŸΡ€ΠΈΠΌΠ΅Ρ€ 1: Базовая инициализация Π² шаблонС ```django {% load static %} ``` ### ΠŸΡ€ΠΈΠΌΠ΅Ρ€ 2: ΠœΠ½ΠΎΠΆΠ΅ΡΡ‚Π²Π΅Π½Π½Π°Ρ инициализация ```django ``` ### ΠŸΡ€ΠΈΠΌΠ΅Ρ€ 3: Π Π°Π±ΠΎΡ‚Π° с API модуля ```javascript import { initEmployeeForm } from '/static/js/components/employeeFormHandler.js'; const form = initEmployeeForm(); // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ ΠΏΠ΅Ρ€Π΅Π΄ ΡƒΡ…ΠΎΠ΄ΠΎΠΌ со страницы window.addEventListener('beforeunload', (e) => { if (form && form.hasChanges()) { e.preventDefault(); e.returnValue = 'Π£ вас Π΅ΡΡ‚ΡŒ нСсохранённыС измСнСния'; } }); // ΠŸΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½Π°Ρ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠ° Ρ„ΠΎΡ€ΠΌΡ‹ document.getElementById('customSaveBtn').addEventListener('click', async () => { if (form && form.validateForm()) { try { await form.submit(); alert('Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΎ!'); } catch (error) { alert('Ошибка: ' + error.message); } } }); ``` --- ## βœ… Best Practices ### 1. ВсСгда провСряйтС Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ```javascript const component = initSomeComponent(); if (component) { // ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½ component.doSomething(); } else { // НСобходимыС элСмСнты Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹ console.warn('Component not initialized'); } ``` ### 2. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ DOMContentLoaded ```javascript document.addEventListener('DOMContentLoaded', () => { // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ послС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ DOM initAllComponents(); }); ``` ### 3. ЭкспортируйтС Π² window для ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ ```javascript const component = initComponent(); if (component) { window.debugComponent = component; // Доступ ΠΈΠ· консоли } ``` ### 4. Π“Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΠΉΡ‚Π΅ ΠΈΠΌΠΏΠΎΡ€Ρ‚Ρ‹ ```javascript // Utils import { esc, norm } from '{% static "js/utils/stringUtils.js" %}'; import { debounce } from '{% static "js/utils/timing.js" %}'; // Components import { initEmployeeForm } from '{% static "js/components/employeeFormHandler.js" %}'; import { initPositionManager } from '{% static "js/components/positionManager.js" %}'; ``` ### 5. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Π΄Π΅ΡΡ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€ΠΈΠ·Π°Ρ†ΠΈΡŽ ```javascript // βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ import { initComponent } from './component.js'; // ❌ ΠŸΠ»ΠΎΡ…ΠΎ import * as Component from './component.js'; const init = Component.initComponent; ``` ### 6. ΠžΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°ΠΉΡ‚Π΅ ошибки ```javascript try { const result = await component.someAsyncOperation(); handleSuccess(result); } catch (error) { console.error('Operation failed:', error); handleError(error); } ``` ### 7. ΠžΡ‡ΠΈΡ‰Π°ΠΉΡ‚Π΅ рСсурсы ```javascript // Если ΠΌΠΎΠ΄ΡƒΠ»ΡŒ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ destroy() const component = initComponent(); // ΠŸΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ со страницы function cleanup() { if (component && component.destroy) { component.destroy(); } } ``` --- ## πŸ”§ ΠžΡ‚Π»Π°Π΄ΠΊΠ° ### Доступ ΠΊ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°ΠΌ ΠΈΠ· консоли ВсС ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹Π΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ доступны Ρ‡Π΅Ρ€Π΅Π· `window`: ```javascript // Π’ консоли Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π° window.employeeForm.hasChanges() // true/false window.positionManager.refresh() // пСрСинициализация window.likeHandler // ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ API ``` ### Π›ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠœΠΎΠ΄ΡƒΠ»ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ `console.warn()` для ΠΏΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠΉ: ```javascript // Если элСмСнты Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹ console.warn('EmployeeForm: required elements not found'); ``` ### ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ```javascript // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ, Π±Ρ‹Π» Π»ΠΈ ΠΌΠΎΠ΄ΡƒΠ»ΡŒ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½ if (window.__employeeFormInitialized) { console.log('EmployeeForm already initialized'); } ``` --- ## πŸ“ž ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° ΠŸΡ€ΠΈ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΈΠΈ вопросов: 1. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ JSDoc Π² исходном ΠΊΠΎΠ΄Π΅ модуля 2. Π˜Π·ΡƒΡ‡ΠΈΡ‚Π΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования Π² ΡˆΠ°Π±Π»ΠΎΠ½Π°Ρ… 3. ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ консоль Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π° Π½Π° прСдупрСТдСния 4. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ `window.componentName` для ΠΎΡ‚Π»Π°Π΄ΠΊΠΈ --- **Π”Π°Ρ‚Π° Π°ΠΊΡ‚ΡƒΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ**: 4 ноября 2025 Π³. **ВСрсия**: 1.0