Fix: TomSelect для мультивыбора в modal

 <select multiple> вместо <div> для групп/пользователей
 plugins: ['remove_button'] для крестиков
 modalInitialized флаг для однократной инициализации
 Правильное получение getValue() для мультивыбора

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
mirivlad 2026-04-02 09:03:33 +08:00
parent c65da15feb
commit 5187c6f784
2 changed files with 135 additions and 130 deletions

View File

@ -87,12 +87,12 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Группа</label> <label class="form-label">Группа</label>
<div id="element_group_ids"></div> <select id="element_group_ids" multiple></select>
<small class="text-muted">И</small> <small class="text-muted">И</small>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Пользователь</label> <label class="form-label">Пользователь</label>
<div id="element_user_ids"></div> <select id="element_user_ids" multiple></select>
</div> </div>
<!-- Строка 3: Даты --> <!-- Строка 3: Даты -->
@ -124,81 +124,84 @@ let items = [];
let elementCounter = 0; let elementCounter = 0;
// TomSelect экземпляры // TomSelect экземпляры
let courseSelect, orgSelect, groupTags, userTags; let courseSelect = null, orgSelect = null, groupTags = null, userTags = null;
let modalInitialized = false;
// Инициализация TomSelect при открытии modal // Инициализация TomSelect при ПЕРВОМ открытии modal
document.getElementById('addElementModal').addEventListener('show.bs.modal', function() { document.getElementById('addElementModal').addEventListener('show.bs.modal', function() {
if (modalInitialized) return;
// Курсы // Курсы
if (!courseSelect) { courseSelect = new TomSelect('#element_course_id', {
courseSelect = new TomSelect('#element_course_id', { valueField: 'id',
valueField: 'id', labelField: 'text',
labelField: 'text', searchField: 'text',
searchField: 'text', placeholder: 'Выберите курс...',
placeholder: 'Выберите курс...', preload: false,
preload: false, maxOptions: null,
load: function(query, callback) { load: function(query, callback) {
if (query.length < 2) return callback(); if (query.length < 2) return callback();
fetch('/api/courses/search?q=' + encodeURIComponent(query)) fetch('/api/courses/search?q=' + encodeURIComponent(query))
.then(response => response.json()) .then(response => response.json())
.then(json => callback(json)) .then(json => callback(json))
.catch(() => callback()); .catch(() => callback());
} }
}); });
}
// Организации // Организации
if (!orgSelect) { orgSelect = new TomSelect('#element_organization_id', {
orgSelect = new TomSelect('#element_organization_id', { valueField: 'id',
valueField: 'id', labelField: 'text',
labelField: 'text', searchField: 'text',
searchField: 'text', placeholder: 'Не выбрано',
placeholder: 'Не выбрано', preload: false,
preload: false, maxOptions: null,
load: function(query, callback) { load: function(query, callback) {
if (query.length < 2) return callback(); if (query.length < 2) return callback();
fetch('/api/organizations/search?q=' + encodeURIComponent(query)) fetch('/api/organizations/search?q=' + encodeURIComponent(query))
.then(response => response.json()) .then(response => response.json())
.then(json => callback(json)) .then(json => callback(json))
.catch(() => callback()); .catch(() => callback());
} }
}); });
}
// Группы // Группы (мультивыбор)
if (!groupTags) { groupTags = new TomSelect('#element_group_ids', {
groupTags = new TomSelect('#element_group_ids', { valueField: 'id',
valueField: 'id', labelField: 'text',
labelField: 'text', searchField: 'text',
searchField: 'text', placeholder: 'Выберите группы...',
placeholder: 'Выберите группы...', preload: false,
preload: false, maxOptions: null,
load: function(query, callback) { plugins: ['remove_button'],
if (query.length < 2) return callback(); load: function(query, callback) {
fetch('/api/groups/search?q=' + encodeURIComponent(query)) if (query.length < 2) return callback();
.then(response => response.json()) fetch('/api/groups/search?q=' + encodeURIComponent(query))
.then(json => callback(json)) .then(response => response.json())
.catch(() => callback()); .then(json => callback(json))
} .catch(() => callback());
}); }
} });
// Пользователи // Пользователи (мультивыбор)
if (!userTags) { userTags = new TomSelect('#element_user_ids', {
userTags = new TomSelect('#element_user_ids', { valueField: 'id',
valueField: 'id', labelField: 'text',
labelField: 'text', searchField: 'text',
searchField: 'text', placeholder: 'Выберите пользователей...',
placeholder: 'Выберите пользователей...', preload: false,
preload: false, maxOptions: null,
load: function(query, callback) { plugins: ['remove_button'],
if (query.length < 2) return callback(); load: function(query, callback) {
fetch('/api/users/search?q=' + encodeURIComponent(query)) if (query.length < 2) return callback();
.then(response => response.json()) fetch('/api/users/search?q=' + encodeURIComponent(query))
.then(json => callback(json)) .then(response => response.json())
.catch(() => callback()); .then(json => callback(json))
} .catch(() => callback());
}); }
} });
modalInitialized = true;
}); });
// Сброс формы при закрытии modal // Сброс формы при закрытии modal
@ -215,8 +218,8 @@ document.getElementById('addElementModal').addEventListener('hidden.bs.modal', f
document.getElementById('addElementBtn').addEventListener('click', function() { document.getElementById('addElementBtn').addEventListener('click', function() {
const courseId = courseSelect.getValue(); const courseId = courseSelect.getValue();
const organizationId = orgSelect.getValue(); const organizationId = orgSelect.getValue();
const groupIds = groupTags.getValue(); const groupIds = groupTags.getValue() || [];
const userIds = userTags.getValue(); const userIds = userTags.getValue() || [];
const startDate = document.getElementById('element_start_date').value; const startDate = document.getElementById('element_start_date').value;
const endDate = document.getElementById('element_end_date').value; const endDate = document.getElementById('element_end_date').value;
@ -237,10 +240,10 @@ document.getElementById('addElementBtn').addEventListener('click', function() {
course_name: courseSelect.options[courseId]?.text || 'Курс', course_name: courseSelect.options[courseId]?.text || 'Курс',
organization_id: organizationId || null, organization_id: organizationId || null,
organization_name: organizationId ? orgSelect.options[organizationId]?.text : null, organization_name: organizationId ? orgSelect.options[organizationId]?.text : null,
group_ids: Array.isArray(groupIds) ? groupIds : (groupIds ? [groupIds] : []), group_ids: Array.isArray(groupIds) ? groupIds : [groupIds],
group_names: Array.isArray(groupIds) ? groupIds.map(id => groupTags.options[id]?.text) : (groupIds ? [groupTags.options[groupIds]?.text] : []), group_names: (Array.isArray(groupIds) ? groupIds : [groupIds]).map(id => groupTags.options[id]?.text).filter(Boolean),
user_ids: Array.isArray(userIds) ? userIds : (userIds ? [userIds] : []), user_ids: Array.isArray(userIds) ? userIds : [userIds],
user_names: Array.isArray(userIds) ? userIds.map(id => userTags.options[id]?.text) : (userIds ? [userTags.options[userIds]?.text] : []), user_names: (Array.isArray(userIds) ? userIds : [userIds]).map(id => userTags.options[id]?.text).filter(Boolean),
start_date: startDate, start_date: startDate,
end_date: endDate || null end_date: endDate || null
}); });

View File

@ -74,12 +74,12 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Группа</label> <label class="form-label">Группа</label>
<div id="element_group_ids"></div> <select id="element_group_ids" multiple></select>
<small class="text-muted">И</small> <small class="text-muted">И</small>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Пользователь</label> <label class="form-label">Пользователь</label>
<div id="element_user_ids"></div> <select id="element_user_ids" multiple></select>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Дата начала *</label> <label class="form-label">Дата начала *</label>
@ -120,54 +120,56 @@ let items = @json($courseRequest->items->map(fn($item) => [
]) ?? []); ]) ?? []);
let elementCounter = items.length; let elementCounter = items.length;
let courseSelect, orgSelect, groupTags, userTags; let courseSelect = null, orgSelect = null, groupTags = null, userTags = null;
let modalInitialized = false;
// Инициализация TomSelect при открытии modal // Инициализация TomSelect при ПЕРВОМ открытии modal
document.getElementById('addElementModal').addEventListener('show.bs.modal', function() { document.getElementById('addElementModal').addEventListener('show.bs.modal', function() {
if (!courseSelect) { if (modalInitialized) return;
courseSelect = new TomSelect('#element_course_id', {
valueField: 'id', labelField: 'text', searchField: 'text', courseSelect = new TomSelect('#element_course_id', {
placeholder: 'Выберите курс...', preload: false, valueField: 'id', labelField: 'text', searchField: 'text',
load: function(query, callback) { placeholder: 'Выберите курс...', preload: false, maxOptions: null,
if (query.length < 2) return callback(); load: function(query, callback) {
fetch('/api/courses/search?q=' + encodeURIComponent(query)) if (query.length < 2) return callback();
.then(response => response.json()).then(json => callback(json)).catch(() => callback()); fetch('/api/courses/search?q=' + encodeURIComponent(query))
} .then(response => response.json()).then(json => callback(json)).catch(() => callback());
}); }
} });
if (!orgSelect) {
orgSelect = new TomSelect('#element_organization_id', { orgSelect = new TomSelect('#element_organization_id', {
valueField: 'id', labelField: 'text', searchField: 'text', valueField: 'id', labelField: 'text', searchField: 'text',
placeholder: 'Не выбрано', preload: false, placeholder: 'Не выбрано', preload: false, maxOptions: null,
load: function(query, callback) { load: function(query, callback) {
if (query.length < 2) return callback(); if (query.length < 2) return callback();
fetch('/api/organizations/search?q=' + encodeURIComponent(query)) fetch('/api/organizations/search?q=' + encodeURIComponent(query))
.then(response => response.json()).then(json => callback(json)).catch(() => callback()); .then(response => response.json()).then(json => callback(json)).catch(() => callback());
} }
}); });
}
if (!groupTags) { groupTags = new TomSelect('#element_group_ids', {
groupTags = new TomSelect('#element_group_ids', { valueField: 'id', labelField: 'text', searchField: 'text',
valueField: 'id', labelField: 'text', searchField: 'text', placeholder: 'Выберите группы...', preload: false, maxOptions: null,
placeholder: 'Выберите группы...', preload: false, plugins: ['remove_button'],
load: function(query, callback) { load: function(query, callback) {
if (query.length < 2) return callback(); if (query.length < 2) return callback();
fetch('/api/groups/search?q=' + encodeURIComponent(query)) fetch('/api/groups/search?q=' + encodeURIComponent(query))
.then(response => response.json()).then(json => callback(json)).catch(() => callback()); .then(response => response.json()).then(json => callback(json)).catch(() => callback());
} }
}); });
}
if (!userTags) { userTags = new TomSelect('#element_user_ids', {
userTags = new TomSelect('#element_user_ids', { valueField: 'id', labelField: 'text', searchField: 'text',
valueField: 'id', labelField: 'text', searchField: 'text', placeholder: 'Выберите пользователей...', preload: false, maxOptions: null,
placeholder: 'Выберите пользователей...', preload: false, plugins: ['remove_button'],
load: function(query, callback) { load: function(query, callback) {
if (query.length < 2) return callback(); if (query.length < 2) return callback();
fetch('/api/users/search?q=' + encodeURIComponent(query)) fetch('/api/users/search?q=' + encodeURIComponent(query))
.then(response => response.json()).then(json => callback(json)).catch(() => callback()); .then(response => response.json()).then(json => callback(json)).catch(() => callback());
} }
}); });
}
modalInitialized = true;
}); });
document.getElementById('addElementModal').addEventListener('hidden.bs.modal', function() { document.getElementById('addElementModal').addEventListener('hidden.bs.modal', function() {
@ -182,8 +184,8 @@ document.getElementById('addElementModal').addEventListener('hidden.bs.modal', f
document.getElementById('addElementBtn').addEventListener('click', function() { document.getElementById('addElementBtn').addEventListener('click', function() {
const courseId = courseSelect.getValue(); const courseId = courseSelect.getValue();
const organizationId = orgSelect.getValue(); const organizationId = orgSelect.getValue();
const groupIds = groupTags.getValue(); const groupIds = groupTags.getValue() || [];
const userIds = userTags.getValue(); const userIds = userTags.getValue() || [];
const startDate = document.getElementById('element_start_date').value; const startDate = document.getElementById('element_start_date').value;
const endDate = document.getElementById('element_end_date').value; const endDate = document.getElementById('element_end_date').value;
@ -196,10 +198,10 @@ document.getElementById('addElementBtn').addEventListener('click', function() {
course_name: courseSelect.options[courseId]?.text || 'Курс', course_name: courseSelect.options[courseId]?.text || 'Курс',
organization_id: organizationId || null, organization_id: organizationId || null,
organization_name: organizationId ? orgSelect.options[organizationId]?.text : null, organization_name: organizationId ? orgSelect.options[organizationId]?.text : null,
group_ids: Array.isArray(groupIds) ? groupIds : (groupIds ? [groupIds] : []), group_ids: Array.isArray(groupIds) ? groupIds : [groupIds],
group_names: Array.isArray(groupIds) ? groupIds.map(id => groupTags.options[id]?.text) : (groupIds ? [groupTags.options[groupIds]?.text] : []), group_names: (Array.isArray(groupIds) ? groupIds : [groupIds]).map(id => groupTags.options[id]?.text).filter(Boolean),
user_ids: Array.isArray(userIds) ? userIds : (userIds ? [userIds] : []), user_ids: Array.isArray(userIds) ? userIds : [userIds],
user_names: Array.isArray(userIds) ? userIds.map(id => userTags.options[id]?.text) : (userIds ? [userTags.options[userIds]?.text] : []), user_names: (Array.isArray(userIds) ? userIds : [userIds]).map(id => userTags.options[id]?.text).filter(Boolean),
start_date: startDate, start_date: startDate,
end_date: endDate || null end_date: endDate || null
}); });