forked from UKSOURCE/cms.hailearning.edu.vn
662 lines
26 KiB
Plaintext
662 lines
26 KiB
Plaintext
<div class="container">
|
|
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-0" style="color: var(--primary-dark);">Form Management</h1>
|
|
<p class="text-muted mb-0">Edit form content for Academics page</p>
|
|
</div>
|
|
<div>
|
|
<a href="/admin/academics" class="btn btn-outline-primary">
|
|
<i class="fas fa-arrow-left me-1"></i>Back to Academics
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<form id="formManagementForm" action="/admin/form/update" method="POST" class="content-with-fixed-buttons">
|
|
<input type="hidden" name="admissionJson" id="admissionJson">
|
|
<input type="hidden" name="applyJson" id="applyJson">
|
|
<input type="hidden" name="applicationFormJson" id="applicationFormJson">
|
|
|
|
<!-- Navigation Tabs -->
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-header bg-white border-bottom">
|
|
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" data-bs-toggle="tab" href="#admission" role="tab">
|
|
<i class="fas fa-door-open me-2"></i>Admission
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#apply" role="tab">
|
|
<i class="fas fa-check-circle me-2"></i>Apply
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#application-form" role="tab">
|
|
<i class="fas fa-file-alt me-2"></i>Application Form
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<div class="tab-content">
|
|
<!-- Admission Tab -->
|
|
<div class="tab-pane fade show active" id="admission" role="tabpanel">
|
|
<div class="row g-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium">Background Image</label>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="admissionBgImage" name="admissionBgImage" value="<%= form.admission && form.admission.background_image ? form.admission.background_image : '' %>">
|
|
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="admissionBgImage" data-image-type="academics">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="image-preview mt-2">
|
|
<% if (form.admission && form.admission.background_image) { %>
|
|
<img src="<%= form.admission.background_image %>" alt="Background preview" class="img-thumbnail" style="height:200px;width:100%;object-fit:cover;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Title</label>
|
|
<input type="text" class="form-control" id="admissionTitle" name="admissionTitle" value="<%= form.admission && form.admission.title ? form.admission.title : '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Year</label>
|
|
<input type="text" class="form-control" id="admissionYear" name="admissionYear" value="<%= form.admission && form.admission.year ? form.admission.year : '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium">Description</label>
|
|
<textarea class="form-control" id="admissionDescription" name="admissionDescription" rows="3"><%= form.admission && form.admission.description ? form.admission.description : '' %></textarea>
|
|
</div>
|
|
|
|
<!-- Admission Form Fields -->
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Form Fields</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="admissionFormFields">
|
|
<% (form.admission && form.admission.form && form.admission.form.fields || []).forEach((field, index) => { %>
|
|
<div class="mb-3 border p-3">
|
|
<div class="row g-2">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Field Type</label>
|
|
<input type="text" class="form-control" value="<%= field.type %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Placeholder</label>
|
|
<input type="text" class="form-control" value="<%= field.placeholder %>">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Form Button Text</label>
|
|
<input type="text" class="form-control" id="admissionFormButton" name="admissionFormButton" value="<%= form.admission && form.admission.form && form.admission.form.button ? form.admission.form.button.text : '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Form Button URL</label>
|
|
<input type="text" class="form-control" id="admissionFormButtonUrl" name="admissionFormButtonUrl" value="<%= form.admission && form.admission.form && form.admission.form.button ? form.admission.form.button.url : '' %>">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Apply Tab -->
|
|
<div class="tab-pane fade" id="apply" role="tabpanel">
|
|
<div class="row g-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium">Title</label>
|
|
<input type="text" class="form-control" id="applyTitle" name="applyTitle" value="<%= form.apply && form.apply.title ? form.apply.title : '' %>">
|
|
</div>
|
|
|
|
<!-- Apply Steps -->
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Application Steps</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="applySteps">
|
|
<% (form.apply && form.apply.steps || []).forEach((step, index) => { %>
|
|
<div class="mb-3 border p-3">
|
|
<div class="row g-2">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Title</label>
|
|
<input type="text" class="form-control" name="applyStepTitle_<%= index %>" value="<%= step.title %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label">Description</label>
|
|
<textarea class="form-control" name="applyStepDescription_<%= index %>" rows="2"><%= step.description %></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Application Form Tab -->
|
|
<div class="tab-pane fade" id="application-form" role="tabpanel">
|
|
<div class="row g-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium">Title</label>
|
|
<input type="text" class="form-control" id="applicationFormTitle" name="applicationFormTitle" value="<%= form.application_form && form.application_form.title ? form.application_form.title : '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium">Question</label>
|
|
<input type="text" class="form-control" id="applicationFormQuestion" name="applicationFormQuestion" value="<%= form.application_form && form.application_form.question ? form.application_form.question : '' %>">
|
|
</div>
|
|
|
|
<!-- Button Section -->
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Button</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-2">
|
|
<div class="col-md-4">
|
|
<label class="form-label">Text</label>
|
|
<input type="text" class="form-control" id="applicationFormButtonText" name="applicationFormButtonText" value="<%= form.application_form && form.application_form.button ? form.application_form.button.text : '' %>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Icon</label>
|
|
<input type="text" class="form-control" id="applicationFormButtonIcon" name="applicationFormButtonIcon" value="<%= form.application_form && form.application_form.button ? form.application_form.button.icon : '' %>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">URL</label>
|
|
<input type="text" class="form-control" id="applicationFormButtonUrl" name="applicationFormButtonUrl" value="<%= form.application_form && form.application_form.button ? form.application_form.button.url : '' %>">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Links Section -->
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Links</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="applicationFormLinks">
|
|
<% (form.application_form && form.application_form.links || []).forEach((link, index) => { %>
|
|
<div class="mb-3 border p-3">
|
|
<div class="row g-2">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Text</label>
|
|
<input type="text" class="form-control" name="applicationFormLinkText_<%= index %>" value="<%= link.text %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">URL</label>
|
|
<input type="text" class="form-control" name="applicationFormLinkUrl_<%= index %>" value="<%= link.url %>">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fixed Bottom Buttons -->
|
|
<div class="fixed-bottom-buttons">
|
|
<button type="reset" class="btn btn-secondary">
|
|
<i class="fas fa-undo"></i>
|
|
<span>Reset</span>
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i>
|
|
<span>Save Changes</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Thêm input file ẩn cho upload ảnh -->
|
|
<input type="file" id="directImageUpload" style="display: none;">
|
|
<input type="hidden" id="currentImageType" name="imageType">
|
|
<input type="hidden" id="currentTargetInput" name="targetInput">
|
|
|
|
<script>
|
|
let originalFormData = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Lưu trữ dữ liệu gốc để so sánh thay đổi
|
|
originalFormData = <%- JSON.stringify(form) %>;
|
|
|
|
// Khởi tạo form handlers
|
|
initializeFormHandlers();
|
|
});
|
|
|
|
function initializeFormHandlers() {
|
|
// Form submission
|
|
const form = document.querySelector('form');
|
|
form.addEventListener('submit', handleFormSubmit);
|
|
|
|
// Upload ảnh
|
|
document.querySelectorAll('.btn-upload-image').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const targetInput = this.dataset.targetInput;
|
|
const imageType = this.dataset.imageType || 'academics';
|
|
openImageUploader(targetInput, imageType);
|
|
});
|
|
});
|
|
|
|
// Khởi tạo direct image upload
|
|
initializeDirectImageUpload();
|
|
}
|
|
|
|
function initializeDirectImageUpload() {
|
|
const fileInput = document.getElementById('directImageUpload');
|
|
fileInput.addEventListener('change', handleDirectImageUpload);
|
|
}
|
|
|
|
function openImageUploader(targetInput, imageType) {
|
|
// Lưu thông tin upload hiện tại
|
|
document.getElementById('currentImageType').value = imageType;
|
|
document.getElementById('currentTargetInput').value = targetInput;
|
|
|
|
// Kích hoạt input file
|
|
const fileInput = document.getElementById('directImageUpload');
|
|
fileInput.click();
|
|
}
|
|
|
|
async function handleDirectImageUpload(e) {
|
|
if (!this.files || !this.files[0]) return;
|
|
|
|
const file = this.files[0];
|
|
const imageType = document.getElementById('currentImageType').value;
|
|
const targetInput = document.getElementById('currentTargetInput').value;
|
|
|
|
try {
|
|
// Disable nút upload và hiển thị loading
|
|
const uploadButton = document.querySelector(`[data-target-input="${targetInput}"]`);
|
|
if (uploadButton) {
|
|
uploadButton.disabled = true;
|
|
uploadButton.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
|
|
}
|
|
|
|
// Tạo form data
|
|
const formData = new FormData();
|
|
formData.append('image', file);
|
|
|
|
// Upload ảnh
|
|
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.path) {
|
|
// Tìm input cần cập nhật, ưu tiên theo name, nếu không có thì tìm theo id
|
|
const inputElement = document.querySelector(`[name="${targetInput}"]`) || document.getElementById(targetInput);
|
|
if (inputElement) {
|
|
inputElement.value = result.path;
|
|
// Cập nhật preview
|
|
updateImagePreview(inputElement, result.path);
|
|
}
|
|
|
|
showToast('Success', 'Image uploaded successfully', 'success');
|
|
} else {
|
|
throw new Error(result.error || 'Upload failed');
|
|
}
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
showToast('Error', 'Failed to upload image: ' + error.message, 'error');
|
|
} finally {
|
|
// Reset nút upload
|
|
const uploadButton = document.querySelector(`[data-target-input="${targetInput}"]`);
|
|
if (uploadButton) {
|
|
uploadButton.disabled = false;
|
|
uploadButton.innerHTML = '<i class="fas fa-upload me-1"></i>Upload';
|
|
}
|
|
// Reset file input
|
|
this.value = '';
|
|
}
|
|
}
|
|
|
|
function updateImagePreview(inputElement, imagePath) {
|
|
// Tìm hoặc tạo phần tử preview theo cách giống như Partnerships
|
|
let imgPreview = inputElement.parentElement.nextElementSibling;
|
|
while (imgPreview && !imgPreview.classList.contains('mt-2')) {
|
|
imgPreview = imgPreview.nextElementSibling;
|
|
}
|
|
|
|
if (!imgPreview) {
|
|
// Tạo mới phần tử preview nếu chưa có
|
|
imgPreview = document.createElement('div');
|
|
imgPreview.className = 'mt-2';
|
|
const img = document.createElement('img');
|
|
img.className = 'img-thumbnail';
|
|
|
|
// Set style dựa vào loại ảnh
|
|
const targetInput = inputElement.name || inputElement.id;
|
|
if (targetInput.toLowerCase().includes('logo')) {
|
|
img.style.height = '100px';
|
|
img.style.objectFit = 'contain';
|
|
} else {
|
|
img.style.height = '200px';
|
|
img.style.width = '100%';
|
|
img.style.objectFit = 'cover';
|
|
}
|
|
|
|
img.alt = 'Image preview';
|
|
imgPreview.appendChild(img);
|
|
inputElement.parentElement.parentElement.appendChild(imgPreview);
|
|
}
|
|
|
|
// Cập nhật đường dẫn hình ảnh
|
|
const img = imgPreview.querySelector('img');
|
|
if (img) {
|
|
img.src = imagePath;
|
|
}
|
|
}
|
|
|
|
function handleFormSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
try {
|
|
console.log('=== Form Submit Debug ===');
|
|
|
|
// Chuẩn bị dữ liệu form
|
|
const formData = prepareFormData();
|
|
console.log('Complete form data:', formData);
|
|
|
|
// Log từng section
|
|
Object.keys(formData).forEach(key => {
|
|
console.log(`${key} data:`, formData[key]);
|
|
});
|
|
|
|
// Validate dữ liệu
|
|
const errors = validateFormData(formData);
|
|
if (errors.length > 0) {
|
|
console.log('Validation errors:', errors);
|
|
showToast('Error', errors.join('. '), 'error');
|
|
return;
|
|
}
|
|
|
|
// Log hidden inputs trước khi cập nhật
|
|
Object.keys(formData).forEach(key => {
|
|
const input = document.getElementById(`${key}Json`);
|
|
console.log(`Hidden input ${key}Json:`, {
|
|
element: input,
|
|
value: input?.value
|
|
});
|
|
});
|
|
|
|
// Cập nhật hidden inputs (map key -> input id)
|
|
const keyToInputId = {
|
|
admission: 'admissionJson',
|
|
apply: 'applyJson',
|
|
application_form: 'applicationFormJson'
|
|
};
|
|
|
|
Object.keys(formData).forEach(key => {
|
|
const inputId = keyToInputId[key] || `${key}Json`;
|
|
const input = document.getElementById(inputId);
|
|
if (input) {
|
|
const jsonValue = JSON.stringify(formData[key]);
|
|
input.value = jsonValue;
|
|
console.log(`Updated ${inputId} with:`, jsonValue);
|
|
} else {
|
|
console.warn(`Hidden input not found: ${inputId}`);
|
|
}
|
|
});
|
|
|
|
// Thông báo đang lưu
|
|
showToast('Info', 'Saving changes...', 'info');
|
|
|
|
// Log form final state
|
|
console.log('Form final state:', {
|
|
admissionJson: document.getElementById('admissionJson')?.value,
|
|
applyJson: document.getElementById('applyJson')?.value,
|
|
applicationFormJson: document.getElementById('applicationFormJson')?.value
|
|
});
|
|
|
|
// Submit form
|
|
this.submit();
|
|
} catch (error) {
|
|
console.error('Form submission error:', error);
|
|
console.error('Error stack:', error.stack);
|
|
showToast('Error', 'Error processing form: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function prepareFormData() {
|
|
return {
|
|
admission: prepareAdmissionData(),
|
|
apply: prepareApplyData(),
|
|
application_form: prepareApplicationFormData()
|
|
};
|
|
}
|
|
|
|
function prepareAdmissionData() {
|
|
try {
|
|
// Basic info
|
|
const data = {
|
|
background_image: document.getElementById('admissionBgImage')?.value || '',
|
|
title: document.getElementById('admissionTitle')?.value || '',
|
|
year: document.getElementById('admissionYear')?.value || '',
|
|
description: document.getElementById('admissionDescription')?.value || '',
|
|
form: {
|
|
fields: [],
|
|
button: {
|
|
text: document.getElementById('admissionFormButton')?.value || '',
|
|
url: document.getElementById('admissionFormButtonUrl')?.value || '#'
|
|
}
|
|
}
|
|
};
|
|
|
|
// Form fields
|
|
const formFields = document.querySelectorAll('#admissionFormFields > div');
|
|
if (formFields) {
|
|
data.form.fields = Array.from(formFields).map(field => {
|
|
const inputs = field.querySelectorAll('input');
|
|
return {
|
|
type: inputs[0]?.value || '',
|
|
placeholder: inputs[1]?.value || ''
|
|
};
|
|
}).filter(field => field.type || field.placeholder);
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error preparing admission data:', error);
|
|
return {
|
|
background_image: '',
|
|
title: '',
|
|
year: '',
|
|
description: '',
|
|
form: {
|
|
fields: [],
|
|
button: {
|
|
text: '',
|
|
url: '#'
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
function prepareApplyData() {
|
|
try {
|
|
const data = {
|
|
title: document.getElementById('applyTitle')?.value || '',
|
|
steps: []
|
|
};
|
|
|
|
// Steps
|
|
const steps = document.querySelectorAll('#applySteps > div');
|
|
if (steps) {
|
|
data.steps = Array.from(steps).map(step => ({
|
|
title: step.querySelector('input[name^="applyStepTitle_"]')?.value || '',
|
|
description: step.querySelector('textarea[name^="applyStepDescription_"]')?.value || ''
|
|
})).filter(step => step.title || step.description);
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error preparing apply data:', error);
|
|
return {
|
|
title: '',
|
|
steps: []
|
|
};
|
|
}
|
|
}
|
|
|
|
function prepareApplicationFormData() {
|
|
try {
|
|
console.log('=== Debug Application Form Data ===');
|
|
|
|
// Log các elements
|
|
console.log('Title element:', document.getElementById('applicationFormTitle'));
|
|
console.log('Question element:', document.getElementById('applicationFormQuestion'));
|
|
console.log('Button text element:', document.getElementById('applicationFormButtonText'));
|
|
console.log('Button icon element:', document.getElementById('applicationFormButtonIcon'));
|
|
|
|
const data = {
|
|
title: document.getElementById('applicationFormTitle')?.value || '',
|
|
question: document.getElementById('applicationFormQuestion')?.value || '',
|
|
button: {
|
|
text: document.getElementById('applicationFormButtonText')?.value || '',
|
|
icon: document.getElementById('applicationFormButtonIcon')?.value || '',
|
|
url: document.getElementById('applicationFormButtonUrl')?.value || '#'
|
|
},
|
|
links: []
|
|
};
|
|
|
|
// Log giá trị cơ bản
|
|
console.log('Basic data:', {
|
|
title: data.title,
|
|
question: data.question,
|
|
button: data.button
|
|
});
|
|
|
|
// Debug links
|
|
const linksContainer = document.getElementById('applicationFormLinks');
|
|
console.log('Links container:', linksContainer);
|
|
|
|
const linkDivs = document.querySelectorAll('#applicationFormLinks > div');
|
|
console.log('Found link divs:', linkDivs.length);
|
|
|
|
if (linkDivs.length > 0) {
|
|
data.links = Array.from(linkDivs).map((link, index) => {
|
|
const textInput = link.querySelector('input[name^="applicationFormLinkText_"]');
|
|
const urlInput = link.querySelector('input[name^="applicationFormLinkUrl_"]');
|
|
|
|
return {
|
|
text: textInput?.value || '',
|
|
url: urlInput?.value || ''
|
|
};
|
|
}).filter(link => link.text || link.url);
|
|
}
|
|
|
|
console.log('Final application form data:', data);
|
|
return data;
|
|
|
|
} catch (error) {
|
|
console.error('Error preparing application form data:', error);
|
|
console.error('Error stack:', error.stack);
|
|
return {
|
|
title: '',
|
|
question: '',
|
|
button: {
|
|
text: '',
|
|
icon: '',
|
|
url: '#'
|
|
},
|
|
links: []
|
|
};
|
|
}
|
|
}
|
|
|
|
function validateFormData(data) {
|
|
const errors = [];
|
|
|
|
// Validate admission
|
|
if (!data.admission.title) errors.push('Admission title is required');
|
|
if (!data.admission.year) errors.push('Admission year is required');
|
|
|
|
// Validate apply
|
|
if (!data.apply.title) errors.push('Apply title is required');
|
|
if (!Array.isArray(data.apply.steps)) errors.push('Apply steps must be an array');
|
|
|
|
// Validate application form
|
|
if (!data.application_form.title) errors.push('Application form title is required');
|
|
if (!data.application_form.question) errors.push('Application form question is required');
|
|
|
|
return errors;
|
|
}
|
|
|
|
function showToast(title, message, type = 'info') {
|
|
if (window.toastManager) {
|
|
window.toastManager[type](message);
|
|
return;
|
|
}
|
|
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.setAttribute('aria-live', 'assertive');
|
|
toast.setAttribute('aria-atomic', 'true');
|
|
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
<strong>${title}:</strong> ${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
// Add toast to container
|
|
let container = document.querySelector('.toast-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.className = 'toast-container position-fixed top-0 end-0 p-3';
|
|
document.body.appendChild(container);
|
|
}
|
|
container.appendChild(toast);
|
|
|
|
// Show toast
|
|
const bsToast = new bootstrap.Toast(toast, {
|
|
animation: true,
|
|
autohide: true,
|
|
delay: 3000
|
|
});
|
|
bsToast.show();
|
|
|
|
// Remove toast after hidden
|
|
toast.addEventListener('hidden.bs.toast', () => {
|
|
toast.remove();
|
|
});
|
|
}
|
|
</script>
|