Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e314044ac |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.idea/
|
||||
43
README.md
43
README.md
@ -35,10 +35,11 @@
|
||||
|
||||
## Основные концепции
|
||||
|
||||
Библиотека работает с двумя основными режимами открытия диалога:
|
||||
Библиотека работает с тремя основными режимами открытия диалога:
|
||||
|
||||
- **create** — создание нового элемента (пустая форма)
|
||||
- **update** — редактирование существующего элемента (форма с подгруженными данными)
|
||||
- **create from** — создание нового элемента из существующего (форма с подгруженными данными)
|
||||
|
||||
Каждый диалог имеет:
|
||||
- Уникальный строковый идентификатор (dialogId)
|
||||
@ -143,6 +144,19 @@ dialog.openUpdate(42, 'Редактирование пользователя')
|
||||
});
|
||||
```
|
||||
|
||||
#### openCreateFrom(itemId, title)
|
||||
Открывает диалог в режиме создания с загруженными данными через GET endpoint.
|
||||
|
||||
```javascript
|
||||
dialog.openUpdate(42, 'Создание копии пользователя')
|
||||
.onSuccess(function(result) {
|
||||
console.log('Создана копия:', result);
|
||||
})
|
||||
.onError(function(error) {
|
||||
console.error('Ошибка:', error);
|
||||
});
|
||||
```
|
||||
|
||||
#### getConfig()
|
||||
Возвращает текущую конфигурацию диалога.
|
||||
|
||||
@ -196,6 +210,16 @@ api.onDelete(function(itemId) {
|
||||
});
|
||||
```
|
||||
|
||||
#### onOpen(callback)
|
||||
Устанавливает обработчик успешного открытия. Если диалог открыт
|
||||
в режиме редактирования, функция будет вызвана после загрузки всех данных.
|
||||
|
||||
```javascript
|
||||
api.onOpen(function() {
|
||||
console.log('Диалог открыт и загружен');
|
||||
});
|
||||
```
|
||||
|
||||
#### setValues(values)
|
||||
Устанавливает значения полей формы.
|
||||
|
||||
@ -270,6 +294,14 @@ crud_d.openUpdate('user_form', 42, 'Редактирование')
|
||||
});
|
||||
```
|
||||
|
||||
#### openCreateFrom(dialogId, itemId, title)
|
||||
```javascript
|
||||
crud_d.openUpdate('user_form', 42, 'Копирование')
|
||||
.onSuccess(function(result) {
|
||||
console.log('Создан из объекта:', result);
|
||||
});
|
||||
```
|
||||
|
||||
#### closeDialog(dialogId)
|
||||
```javascript
|
||||
crud_d.closeDialog('user_form');
|
||||
@ -648,6 +680,15 @@ dialog.openCreate('Новый заказ')
|
||||
- При удалении: подтверждение → DELETE запрос → `onDelete`/`onError`
|
||||
- Автоматическое закрытие диалога при успехе
|
||||
|
||||
3. **Создание из существующего элемента (create from):**
|
||||
- GET запрос на get endpoint для загрузки данных
|
||||
- Заполнение формы полученными данными
|
||||
- Валидация формы
|
||||
- POST запрос на create endpoint
|
||||
- `onSuccess` при успехе
|
||||
- `onError` при ошибке
|
||||
- Автоматическое закрытие диалога при успехе
|
||||
|
||||
### Цепочка методов
|
||||
|
||||
Все методы настройки возвращают `this`, что позволяет использовать цепочку вызовов:
|
||||
|
||||
100
crud_d.js
100
crud_d.js
@ -1,13 +1,12 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
// Хранилище зарегистрированных диалогов
|
||||
const dialogs = new Map();
|
||||
// Хранилище активных экземпляров диалогов
|
||||
const activeDialogs = new Map();
|
||||
// Хранилище зарегистрированных диалогов
|
||||
const dialogs = new Map();
|
||||
// Хранилище активных экземпляров диалогов
|
||||
const activeDialogs = new Map();
|
||||
|
||||
// Типы полей по умолчанию
|
||||
const FIELD_TYPES = {
|
||||
// Типы полей по умолчанию
|
||||
const FIELD_TYPES = {
|
||||
text: (name, params, defaultValue = '') => {
|
||||
const container = dom.div().cls('mb-3');
|
||||
const label = dom.label().cls('form-label').atr('for', `field_${name}`).text(params.label || name);
|
||||
@ -229,10 +228,10 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Фабрика полей
|
||||
function createField(name, fieldConfig) {
|
||||
// Фабрика полей
|
||||
function createField(name, fieldConfig) {
|
||||
const [type, params, defaultValue] = fieldConfig;
|
||||
|
||||
if (FIELD_TYPES[type]) {
|
||||
@ -257,20 +256,20 @@
|
||||
|
||||
console.warn(`Неизвестный тип поля: ${type}`);
|
||||
return FIELD_TYPES.text(name, params, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для расчета Bootstrap col класса
|
||||
function getColClass(fieldsInRow) {
|
||||
// Функция для расчета Bootstrap col класса
|
||||
function getColClass(fieldsInRow) {
|
||||
if (fieldsInRow === 1) return 'col-12';
|
||||
if (fieldsInRow === 2) return 'col-md-6';
|
||||
if (fieldsInRow === 3) return 'col-md-4';
|
||||
if (fieldsInRow === 4) return 'col-md-3';
|
||||
const colSize = Math.floor(12 / fieldsInRow);
|
||||
return `col-md-${colSize}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для рендеринга полей с учетом layout
|
||||
function renderFieldsWithLayout(container, fields, layout, fieldInstances) {
|
||||
// Функция для рендеринга полей с учетом layout
|
||||
function renderFieldsWithLayout(container, fields, layout, fieldInstances) {
|
||||
const fieldNames = Object.keys(fields);
|
||||
const usedFields = new Set();
|
||||
|
||||
@ -316,13 +315,14 @@
|
||||
row.child(col);
|
||||
container.child(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP запросы
|
||||
async function apiRequest(method, url, data = null) {
|
||||
// HTTP запросы
|
||||
async function apiRequest(method, url, data = null) {
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content ?? '',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
};
|
||||
@ -339,10 +339,10 @@
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
// Создание модального окна
|
||||
function createModal(dialogId, title, fields, options = {}, mode = 'create', itemId = null) {
|
||||
// Создание модального окна
|
||||
function createModal(dialogId, title, fields, options = {}, mode = 'create', itemId = null) {
|
||||
const modalId = `modal_${dialogId}_${Date.now()}`;
|
||||
const { width = '', categories = null } = options;
|
||||
const config = dialogs.get(dialogId);
|
||||
@ -365,7 +365,7 @@
|
||||
|
||||
// Заголовок
|
||||
const header = dom.div().cls('modal-header');
|
||||
const titleText = mode === 'create' ? title : `Редактирование: ${title}`;
|
||||
const titleText = mode === 'create' ? title : mode === 'create_from' ? `Скопировать из: ${title}` : `Редактирование: ${title}`;
|
||||
const titleEl = dom.h5().cls('modal-title').text(titleText);
|
||||
const closeBtn = dom.button().cls('btn-close').atr('data-bs-dismiss', 'modal').atr('aria-label', 'Close');
|
||||
header.child(titleEl);
|
||||
@ -413,7 +413,7 @@
|
||||
const categoryNames = Object.keys(finalCategories);
|
||||
|
||||
const tabNav = dom.ul().cls('nav nav-tabs').atr('role', 'tablist');
|
||||
const tabContent = dom.div().cls('tab-content');
|
||||
const tabContent = dom.div().cls('tab-content').cls('mt-3');
|
||||
|
||||
categoryNames.forEach((catName, index) => {
|
||||
const tabId = `tab_${dialogId}_${catName.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
||||
@ -475,7 +475,7 @@
|
||||
const submitSpinner = dom.span().cls('spinner-border spinner-border-sm').atr('role', 'status')
|
||||
.atr('aria-hidden', 'true').css('display: none');
|
||||
|
||||
if (mode === 'create') {
|
||||
if (mode === 'create' || mode === 'create_from') {
|
||||
submitBtn.text('Создать');
|
||||
submitBtn.child(submitSpinner);
|
||||
footer.child(cancelBtn);
|
||||
@ -576,10 +576,10 @@
|
||||
};
|
||||
|
||||
return modalInstance;
|
||||
}
|
||||
}
|
||||
|
||||
// Основная функция API
|
||||
window.crud_d = {
|
||||
// Основная функция API
|
||||
window.crud_d = {
|
||||
// Создание диалога
|
||||
makeDialog: function(dialogId, fields, options = {}) {
|
||||
if (typeof dialogId !== 'string') {
|
||||
@ -630,6 +630,10 @@
|
||||
return this._open(title, 'create');
|
||||
},
|
||||
|
||||
openCreateFrom: function(itemId, title = 'Скопировать из') {
|
||||
return this._open(title, 'create_from', itemId);
|
||||
},
|
||||
|
||||
// Открытие в режиме обновления
|
||||
openUpdate: function(itemId, title = 'Редактирование') {
|
||||
return this._open(title, 'update', itemId);
|
||||
@ -648,6 +652,10 @@
|
||||
activeDialogs.set(dialogId, modal);
|
||||
|
||||
const api = {
|
||||
onOpen: function(callback) {
|
||||
modal.onOpen = callback;
|
||||
return this;
|
||||
},
|
||||
onSuccess: function(callback) {
|
||||
modal.onSuccess = callback;
|
||||
return this;
|
||||
@ -681,7 +689,7 @@
|
||||
};
|
||||
|
||||
// Загрузка данных для режима update
|
||||
if (mode === 'update' && itemId) {
|
||||
if ((mode === 'update' || mode === 'create_from') && itemId) {
|
||||
modal.showLoadingOverlay();
|
||||
|
||||
const getEndpoint = config.endpoints.get;
|
||||
@ -698,6 +706,9 @@
|
||||
.then(data => {
|
||||
modal.setValues(data);
|
||||
modal.hideLoadingOverlay();
|
||||
if (typeof modal.onOpen === 'function') {
|
||||
modal.onOpen(modal);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
modal.hideLoadingOverlay();
|
||||
@ -705,6 +716,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (mode === 'create') {
|
||||
setTimeout(() => {
|
||||
if (typeof modal.onOpen === 'function') {
|
||||
modal.onOpen(modal);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Обработчик сохранения/создания
|
||||
modal.submitBtn.addEventListener('click', async () => {
|
||||
const errors = modal.validate();
|
||||
@ -720,7 +739,7 @@
|
||||
const formData = modal.getFormData();
|
||||
let result;
|
||||
|
||||
if (mode === 'create') {
|
||||
if (mode === 'create' || mode === 'create_from') {
|
||||
const createEndpoint = config.endpoints.create;
|
||||
if (!createEndpoint) {
|
||||
throw new Error('Create endpoint не установлен');
|
||||
@ -823,8 +842,6 @@
|
||||
return dialogApi;
|
||||
},
|
||||
|
||||
// Глобальные методы для работы по имени диалога
|
||||
|
||||
// Установка эндпоинтов по имени диалога
|
||||
setCreateEndpoint: function(dialogId, endpoint) {
|
||||
const dialog = dialogs.get(dialogId);
|
||||
@ -876,6 +893,19 @@
|
||||
}
|
||||
},
|
||||
|
||||
openCreateFrom: function(dialogId, itemId, title = 'Скопировать из') {
|
||||
const dialog = dialogs.get(dialogId);
|
||||
if (dialog) {
|
||||
const api = this.makeDialog(dialogId, dialog.fields, dialog.options);
|
||||
// Копируем эндпоинты
|
||||
api.getConfig().endpoints = dialog.endpoints;
|
||||
return api.openCreateFrom(itemId, title);
|
||||
} else {
|
||||
console.error(`Диалог "${dialogId}" не найден`);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
openUpdate: function(dialogId, itemId, title = 'Редактирование') {
|
||||
const dialog = dialogs.get(dialogId);
|
||||
if (dialog) {
|
||||
@ -906,6 +936,4 @@
|
||||
activeDialogs.forEach(modal => modal.destroy());
|
||||
activeDialogs.clear();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user