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