diff --git a/README.md b/README.md index b14e4b1..77af094 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,694 @@ -# CRUD_Dialogs +# Документация библиотеки CRUD Dialogs -Библиотека для абстракции создания диалогов для CRUD операций. Требует bootstrap и dom.js \ No newline at end of file +Библиотека для создания модальных диалоговых окон на основе Bootstrap с поддержкой CRUD операций, вкладок и гибкой настройкой макета. + +## Оглавление + +1. [Подключение](#подключение) +2. [Основные концепции](#основные-концепции) +3. [API Reference](#api-reference) + - [makeDialog](#makedialog) + - [Методы объекта диалога](#методы-объекта-диалога) + - [Глобальные методы](#глобальные-методы) +4. [Типы полей](#типы-полей) +5. [Настройки диалога](#настройки-диалога) +6. [Layout и категории](#layout-и-категории) +7. [Примеры использования](#примеры-использования) + +--- + +## Подключение + +```html + + + + + + + + + +``` + +--- + +## Основные концепции + +Библиотека работает с двумя основными режимами открытия диалога: + +- **create** — создание нового элемента (пустая форма) +- **update** — редактирование существующего элемента (форма с подгруженными данными) + +Каждый диалог имеет: +- Уникальный строковый идентификатор (dialogId) +- Набор полей с типами и настройками +- Эндпоинты для CRUD операций (устанавливаются отдельно) +- Опциональные настройки отображения (ширина, категории) + +Управлять диалогом можно: +- Через объект, полученный при создании +- Через глобальные методы, используя dialogId + +--- + +## API Reference + +### makeDialog + +Создает новый диалог и возвращает объект для работы с ним. + +```javascript +crud_d.makeDialog(dialogId, fields, options) +``` + +**Параметры:** + +| Параметр | Тип | Обязательный | Описание | +|----------|-----|--------------|----------| +| dialogId | string | Да | Уникальный идентификатор диалога (не DOM id) | +| fields | object | Да | Объект с описанием полей | +| options | object | Нет | Дополнительные настройки диалога | + +**Возвращает:** Объект диалога с методами управления. + +**Пример:** +```javascript +const dialog = crud_d.makeDialog('user_form', { + name: ['text', { label: 'Имя', required: true }, ''], + email: ['email', { label: 'Email', required: true }, ''] +}, { + width: 'lg' +}); +``` + +--- + +### Методы объекта диалога + +#### setCreateEndpoint(endpoint) +Устанавливает endpoint для создания элемента (POST). + +```javascript +dialog.setCreateEndpoint('/api/users') +``` + +#### setUpdateEndpoint(endpoint) +Устанавливает endpoint для обновления элемента (PUT). Используйте `{id}` для подстановки ID элемента. + +```javascript +dialog.setUpdateEndpoint('/api/users/{id}') +``` + +#### setGetEndpoint(endpoint) +Устанавливает endpoint для получения данных элемента (GET). Используйте `{id}` для подстановки ID элемента. + +```javascript +dialog.setGetEndpoint('/api/users/{id}') +``` + +#### setDeleteEndpoint(endpoint) +Устанавливает endpoint для удаления элемента (DELETE). Используйте `{id}` для подстановки ID элемента. + +```javascript +dialog.setDeleteEndpoint('/api/users/{id}') +``` + +#### openCreate(title) +Открывает диалог в режиме создания. + +```javascript +dialog.openCreate('Новый пользователь') + .onSuccess(function(result) { + console.log('Создан:', result); + }) + .onError(function(error) { + console.error('Ошибка:', error); + }); +``` + +#### openUpdate(itemId, title) +Открывает диалог в режиме редактирования. Автоматически загружает данные через GET endpoint. + +```javascript +dialog.openUpdate(42, 'Редактирование пользователя') + .onSuccess(function(result) { + console.log('Обновлен:', result); + }) + .onDelete(function(id) { + console.log('Удален:', id); + }) + .onError(function(error) { + console.error('Ошибка:', error); + }); +``` + +#### getConfig() +Возвращает текущую конфигурацию диалога. + +```javascript +const config = dialog.getConfig(); +console.log(config.fields, config.options, config.endpoints); +``` + +#### destroy() +Удаляет регистрацию диалога и закрывает его, если открыт. + +```javascript +dialog.destroy(); +``` + +--- + +### Методы API при открытом диалоге + +При открытии диалога возвращается объект с методами для управления текущим экземпляром: + +```javascript +const api = dialog.openCreate('Заголовок'); +``` + +#### onSuccess(callback) +Устанавливает обработчик успешного выполнения операции. + +```javascript +api.onSuccess(function(result) { + // result - данные, возвращенные сервером + console.log('Успех:', result); +}); +``` + +#### onError(callback) +Устанавливает обработчик ошибок. + +```javascript +api.onError(function(error) { + console.error('Ошибка:', error.message); +}); +``` + +#### onDelete(callback) +Устанавливает обработчик успешного удаления (только для режима update). + +```javascript +api.onDelete(function(itemId) { + console.log('Удален элемент с ID:', itemId); +}); +``` + +#### setValues(values) +Устанавливает значения полей формы. + +```javascript +api.setValues({ + name: 'Иван', + email: 'ivan@example.com' +}); +``` + +#### getFormData() +Возвращает текущие данные формы. + +```javascript +const data = api.getFormData(); +console.log(data); +``` + +#### close() +Закрывает диалог программно. + +```javascript +api.close(); +``` + +#### show() +Показывает диалог (если был скрыт). + +```javascript +api.show(); +``` + +--- + +### Глобальные методы + +Работа с диалогами через глобальный объект `crud_d` по dialogId. + +#### setCreateEndpoint(dialogId, endpoint) +```javascript +crud_d.setCreateEndpoint('user_form', '/api/users'); +``` + +#### setUpdateEndpoint(dialogId, endpoint) +```javascript +crud_d.setUpdateEndpoint('user_form', '/api/users/{id}'); +``` + +#### setGetEndpoint(dialogId, endpoint) +```javascript +crud_d.setGetEndpoint('user_form', '/api/users/{id}'); +``` + +#### setDeleteEndpoint(dialogId, endpoint) +```javascript +crud_d.setDeleteEndpoint('user_form', '/api/users/{id}'); +``` + +#### openCreate(dialogId, title) +```javascript +crud_d.openCreate('user_form', 'Новый пользователь') + .onSuccess(function(result) { + console.log('Создан:', result); + }); +``` + +#### openUpdate(dialogId, itemId, title) +```javascript +crud_d.openUpdate('user_form', 42, 'Редактирование') + .onSuccess(function(result) { + console.log('Обновлен:', result); + }); +``` + +#### closeDialog(dialogId) +```javascript +crud_d.closeDialog('user_form'); +``` + +#### getRegisteredDialogs() +```javascript +const dialogs = crud_d.getRegisteredDialogs(); +console.log(dialogs); // ['user_form', 'product_form'] +``` + +#### closeAll() +```javascript +crud_d.closeAll(); +``` + +--- + +## Типы полей + +### text +Текстовое поле ввода. + +```javascript +fieldName: ['text', { + label: 'Название поля', // string - заголовок + required: true, // boolean - обязательно для заполнения + placeholder: 'Подсказка', // string - текст-подсказка + readonly: false, // boolean - только для чтения + maxlength: 100 // number - максимальная длина +}, 'значение по умолчанию'] +``` + +### number +Числовое поле ввода. + +```javascript +fieldName: ['number', { + label: 'Цена', + required: true, + min: 0, // number - минимальное значение + max: 1000000, // number - максимальное значение + step: 0.01 // number - шаг изменения +}, 0] +``` + +### email +Поле для email с валидацией формата. + +```javascript +fieldName: ['email', { + label: 'Email', + required: true, + placeholder: 'user@example.com' +}, ''] +``` + +### password +Поле для пароля (скрытый ввод). + +```javascript +fieldName: ['password', { + label: 'Пароль', + required: true, + placeholder: 'Введите пароль' +}, ''] +``` + +### textarea +Многострочное текстовое поле. + +```javascript +fieldName: ['textarea', { + label: 'Описание', + required: false, + rows: 5 // number - количество строк +}, ''] +``` + +### select +Выпадающий список. + +```javascript +fieldName: ['select', { + label: 'Категория', + required: true, + options: [ // array - список опций + { value: 'electronics', label: 'Электроника' }, + { value: 'books', label: 'Книги' }, + { value: 'clothing', label: 'Одежда' } + ] +}, 'electronics'] // значение по умолчанию +``` + +### checkbox +Флажок (чекбокс). + +```javascript +fieldName: ['checkbox', { + label: 'Активен', + required: false +}, true] // значение по умолчанию +``` + +### date +Поле выбора даты. + +```javascript +fieldName: ['date', { + label: 'Дата рождения', + required: true +}, '2024-01-01'] +``` + +### custom +Кастомное поле на основе HTMLElement. + +```javascript +const customElement = document.createElement('div'); +customElement.innerHTML = ` + + +`; + +fieldName: ['custom', customElement, '#ff0000'] +``` + +--- + +## Настройки диалога + +Объект options передается третьим параметром в `makeDialog`: + +```javascript +{ + width: 'lg', // Ширина диалога: 'sm', 'lg', 'xl' или CSS значение ('800px', '90%') + categories: { // Категории (вкладки) + 'Основное': [ + ['name', 'price'], + ['description'] + ], + 'Дополнительно': [ + ['sku', 'weight'], + ['dimensions'] + ] + } +} +``` + +--- + +## Layout и категории + +### Без категорий +Если категории не указаны, все поля отображаются последовательно по одному в ряду. + +```javascript +crud_d.makeDialog('simple', { + name: ['text', { label: 'Имя' }, ''], + email: ['email', { label: 'Email' }, ''], + phone: ['text', { label: 'Телефон' }, ''] +}); +``` + +### Layout в категориях +Каждая категория содержит массив рядов. Ряд — это массив имен полей, которые будут в одной строке. + +```javascript +categories: { + 'Основная информация': [ + ['first_name', 'last_name'], // 2 поля в ряд + ['email', 'phone'], // 2 поля в ряд + ['address'] // 1 поле в ряд + ], + 'Дополнительно': [ + ['birth_date', 'gender'], // 2 поля в ряд + ['notes'] // 1 поле в ряд + ] +} +``` + +### Автоматическая категория "Дополнительно" +- Если указана только одна категория, все поля, не попавшие в неё, автоматически добавляются во вкладку "Дополнительно" +- Если категорий несколько, нераспределенные поля также добавляются в "Дополнительно" +- Если категории не указаны вообще, все поля идут подряд без вкладок + +### Ширина колонок в ряду +Автоматически рассчитывается на основе количества полей: +- 1 поле: `col-12` (полная ширина) +- 2 поля: `col-md-6` (по половине) +- 3 поля: `col-md-4` (по трети) +- 4 поля: `col-md-3` (по четверти) + +--- + +## Примеры использования + +### Простой диалог создания + +```javascript +const dialog = crud_d.makeDialog('simple_user', { + name: ['text', { label: 'Имя', required: true }, ''], + email: ['email', { label: 'Email', required: true }, ''] +}); + +dialog.setCreateEndpoint('/api/users'); + +// Открытие +dialog.openCreate('Новый пользователь') + .onSuccess(function(result) { + alert('Пользователь создан!'); + }); +``` + +### Диалог с обновлением и удалением + +```javascript +const dialog = crud_d.makeDialog('user_edit', { + name: ['text', { label: 'Имя', required: true }, ''], + email: ['email', { label: 'Email', required: true }, ''], + role: ['select', { + label: 'Роль', + options: [ + { value: 'user', label: 'Пользователь' }, + { value: 'admin', label: 'Администратор' } + ] + }, 'user'] +}); + +dialog.setGetEndpoint('/api/users/{id}'); +dialog.setUpdateEndpoint('/api/users/{id}'); +dialog.setDeleteEndpoint('/api/users/{id}'); + +// Открытие на редактирование +dialog.openUpdate(42, 'Редактирование пользователя') + .onSuccess(function(result) { + console.log('Обновлен:', result); + // Обновить список пользователей + }) + .onDelete(function(id) { + console.log('Удален:', id); + // Удалить из списка + }) + .onError(function(error) { + alert('Ошибка: ' + error.message); + }); +``` + +### Сложный диалог с категориями + +```javascript +const dialog = crud_d.makeDialog('product_form', { + name: ['text', { label: 'Название', required: true }, ''], + price: ['number', { label: 'Цена', required: true, min: 0 }, 0], + quantity: ['number', { label: 'Количество', min: 0 }, 0], + category: ['select', { + label: 'Категория', + options: [ + { value: 'electronics', label: 'Электроника' }, + { value: 'clothing', label: 'Одежда' } + ] + }, 'electronics'], + description: ['textarea', { label: 'Описание', rows: 5 }, ''], + weight: ['number', { label: 'Вес (кг)', step: 0.1 }, null], + dimensions: ['text', { label: 'Размеры' }, ''], + manufacturer: ['text', { label: 'Производитель' }, ''], + in_stock: ['checkbox', { label: 'В наличии' }, true] +}, { + width: 'lg', + categories: { + 'Основное': [ + ['name'], + ['price', 'quantity', 'in_stock'], + ['category'], + ['description'] + ], + 'Характеристики': [ + ['weight', 'dimensions'], + ['manufacturer'] + ] + } +}); + +dialog.setCreateEndpoint('/api/products'); +dialog.setGetEndpoint('/api/products/{id}'); +dialog.setUpdateEndpoint('/api/products/{id}'); +dialog.setDeleteEndpoint('/api/products/{id}'); +``` + +### Глобальное управление + +```javascript +// Регистрируем диалог +crud_d.makeDialog('quick_user', { + name: ['text', { label: 'Имя' }, ''], + email: ['email', { label: 'Email' }, ''] +}); + +// Устанавливаем эндпоинты глобально +crud_d.setCreateEndpoint('quick_user', '/api/users'); +crud_d.setGetEndpoint('quick_user', '/api/users/{id}'); + +// Открываем из любого места +crud_d.openCreate('quick_user', 'Быстрое создание') + .onSuccess(function(result) { + console.log('Готово!', result); + }); + +// Закрываем при необходимости +crud_d.closeDialog('quick_user'); + +// Закрыть все открытые диалоги +crud_d.closeAll(); +``` + +### Кастомное поле + +```javascript +// Создаем кастомный элемент для выбора цвета +const colorPicker = document.createElement('div'); +colorPicker.className = 'mb-3'; +colorPicker.innerHTML = ` + +
+ + +
+`; + +// Синхронизация инпутов +const [colorInput, textInput] = colorPicker.querySelectorAll('input'); +colorInput.addEventListener('input', () => textInput.value = colorInput.value); +textInput.addEventListener('input', () => colorInput.value = textInput.value); + +const dialog = crud_d.makeDialog('custom_example', { + name: ['text', { label: 'Название' }, ''], + color: ['custom', colorPicker, '#563d7c'] +}); + +dialog.setCreateEndpoint('/api/products'); +dialog.openCreate('Товар с цветом'); +``` + +### Установка значений по умолчанию + +```javascript +dialog.openCreate('Новый заказ') + .setValues({ + priority: 'high', + status: 'pending', + date: new Date().toISOString().split('T')[0] + }) + .onSuccess(function(result) { + console.log('Заказ создан:', result); + }); +``` + +--- + +## События и колбэки + +### Порядок выполнения + +1. **Создание (create):** + - Валидация формы + - POST запрос на create endpoint + - `onSuccess` при успехе + - `onError` при ошибке + - Автоматическое закрытие диалога при успехе + +2. **Обновление (update):** + - GET запрос на get endpoint для загрузки данных + - Заполнение формы полученными данными + - При сохранении: валидация → PUT запрос → `onSuccess`/`onError` + - При удалении: подтверждение → DELETE запрос → `onDelete`/`onError` + - Автоматическое закрытие диалога при успехе + +### Цепочка методов + +Все методы настройки возвращают `this`, что позволяет использовать цепочку вызовов: + +```javascript +dialog + .setCreateEndpoint('/api/users') + .setGetEndpoint('/api/users/{id}') + .openCreate('Пользователь') + .onSuccess(handleSuccess) + .onError(handleError); +``` + +--- + +## Обработка ошибок + +Библиотека автоматически обрабатывает HTTP ошибки и отображает их в диалоге: + +- Ошибки валидации отображаются в alert-блоке внутри диалога +- Ошибки сети/сервера отображаются там же +- Все ошибки также передаются в `onError` колбэк + +```javascript +dialog.openCreate('Форма') + .onError(function(error) { + // error.message содержит текст ошибки + if (error.message.includes('422')) { + // Ошибка валидации на сервере + } else if (error.message.includes('500')) { + // Ошибка сервера + } + }); +``` + +--- + +## Примечания + +- Все эндпоинты поддерживают подстановку `{id}` в URL +- При закрытии диалога через `data-bs-dismiss="modal"` он автоматически очищается +- Одновременно может быть открыт только один экземпляр диалога с одинаковым dialogId +- Библиотека требует Bootstrap 5 и библиотеку dom.js +- Все HTTP запросы отправляются с заголовком `Content-Type: application/json` \ No newline at end of file