добавление новых функций - onOpen и режима открытия createFrom

This commit is contained in:
svsptech 2026-05-19 05:53:16 +05:00
parent 4965c1c6b0
commit 4e314044ac
3 changed files with 879 additions and 809 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea/

View File

@ -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
View File

@ -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();
}
};
})();
};