Form AJAX
Kirim form tanpa reload halaman menggunakan setAjax() dan fetch API.
NexaForm · setAjax() · Tested
Contoh Lengkap: AJAX Form dengan File Upload
Contoh ini menunjukkan implementasi form AJAX lengkap dengan validasi, dropdown, dan file upload. Form submit tanpa reload halaman dan menampilkan response secara real-time.
1. Controller - Method ajax()
Setup form dengan setAjax(true) untuk enable mode AJAX:
public function ajax(array $params = []): void
{
// Setup form dengan validasi dan AJAX mode
$form = $this->createForm()
->fields([
'title' => 'Name|3|Judul minimal 3 karakter',
'category' => 'Name|1|Kategori wajib dipilih',
'content' => 'Name|10|Konten minimal 10 karakter',
'image' => 'FileOptional|null|Upload file jika diperlukan'
])
->setUpload([
'maxSize' => '2MB',
'allowedExtensions' => ['jpg', 'jpeg', 'png', 'gif']
])
->setAjax(true) // Enable AJAX mode
->setSuccess('Data berhasil disimpan!')
->setError('Mohon perbaiki kesalahan berikut!');
// Process form - akan auto return JSON jika AJAX
$result = $form->process();
// Jika sukses, simpan ke database
if ($result['success']) {
// TODO: Simpan ke database
// $this->useModels('Article', 'create', [$result['data']]);
}
}- Saat
setAjax(true)aktif,process()akan otomatis return JSON dan exit - Tidak perlu manual
sendJsonResponse()atauecho json_encode() - Code setelah
process()hanya dijalankan untuk non-AJAX request
2. Template HTML dengan JavaScript
Template form dengan fetch API untuk submit via AJAX:
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h2>AJAX Form Example</h2>
</div>
<div class="card-body">
<!-- Alert Container -->
<div id="alert-container"></div>
<form id="ajaxForm" enctype="multipart/form-data">
<!-- Field: Title -->
<div class="form-group mb-3">
<label for="title">Judul Artikel *</label>
<input
type="text"
name="title"
id="title"
class="form-control"
placeholder="Masukkan judul artikel"
>
<div class="invalid-feedback"></div>
<small class="form-text text-muted">Minimal 3 karakter</small>
</div>
<!-- Field: Category -->
<div class="form-group mb-3">
<label for="category">Kategori *</label>
<select name="category" id="category" class="form-control">
<option value="">-- Pilih Kategori --</option>
<option value="1">Teknologi</option>
<option value="2">Bisnis</option>
<option value="3">Pendidikan</option>
<option value="4">Kesehatan</option>
</select>
<div class="invalid-feedback"></div>
</div>
<!-- Field: Content -->
<div class="form-group mb-3">
<label for="content">Konten Artikel *</label>
<textarea
name="content"
id="content"
class="form-control"
rows="5"
placeholder="Tulis konten artikel di sini..."
></textarea>
<div class="invalid-feedback"></div>
<small class="form-text text-muted">Minimal 10 karakter</small>
</div>
<!-- Field: Image -->
<div class="form-group mb-3">
<label for="image">Gambar (Opsional)</label>
<input
type="file"
name="image"
id="image"
class="form-control"
accept="image/jpeg,image/jpg,image/png,image/gif"
>
<div class="invalid-feedback"></div>
<small class="form-text text-muted">Format: JPG, JPEG, PNG, GIF. Maksimal 2MB</small>
<!-- Progress Bar -->
<div class="progress mt-2" id="uploadProgress" style="display: none;">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
<span class="btn-text">Simpan Data</span>
<span class="spinner-border spinner-border-sm ms-2" role="status" style="display: none;"></span>
</button>
</form>
</div>
</div>
</div>3. JavaScript - Fetch API
JavaScript untuk handle form submit dengan fetch API:
document.getElementById('ajaxForm').addEventListener('submit', async function(e) {
e.preventDefault();
const form = this;
const submitBtn = document.getElementById('submitBtn');
const btnText = submitBtn.querySelector('.btn-text');
const spinner = submitBtn.querySelector('.spinner-border');
const alertContainer = document.getElementById('alert-container');
const progressBar = document.getElementById('uploadProgress');
// Clear previous errors
form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
form.querySelectorAll('.invalid-feedback').forEach(el => el.textContent = '');
alertContainer.innerHTML = '';
// Disable submit button
submitBtn.disabled = true;
btnText.textContent = 'Mengirim...';
spinner.style.display = 'inline-block';
// Prepare form data
const formData = new FormData(form);
try {
// Show progress bar if file upload
if (formData.get('image') && formData.get('image').size > 0) {
progressBar.style.display = 'block';
}
// Send AJAX request
const response = await fetch('/example/ajax', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest' // PENTING!
},
body: formData
});
const result = await response.json();
// Hide progress bar
progressBar.style.display = 'none';
// Handle response
if (result.success) {
// Show success message
alertContainer.innerHTML = `
<div class="alert alert-success alert-dismissible fade show">
<strong>Sukses!</strong> ${result.message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
// Reset form
form.reset();
// Log uploaded file info
if (result.files && result.files.image) {
console.log('File uploaded:', result.files.image);
}
} else {
// Show error message
alertContainer.innerHTML = `
<div class="alert alert-danger alert-dismissible fade show">
<strong>Error!</strong> ${result.message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
// Display field errors
if (result.errors) {
for (const [field, message] of Object.entries(result.errors)) {
const fieldName = field.replace('errors_', '');
const input = form.querySelector(`[name="${fieldName}"]`);
const feedback = input.parentElement.querySelector('.invalid-feedback');
if (input && feedback) {
input.classList.add('is-invalid');
feedback.textContent = message;
}
}
}
}
} catch (error) {
alertContainer.innerHTML = `
<div class="alert alert-danger alert-dismissible fade show">
<strong>Error!</strong> Terjadi kesalahan: ${error.message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
} finally {
// Re-enable submit button
submitBtn.disabled = false;
btnText.textContent = 'Simpan Data';
spinner.style.display = 'none';
}
});Struktur JSON Response
NexaForm otomatis mengirim JSON response dengan struktur standar:
Response Sukses
{
"success": true,
"processed": true,
"informasi": "file",
"message": "Data berhasil disimpan!",
"data": {
"title": "Test AJAX Form",
"category": "1",
"content": "This is test content for ajax form",
"image": "2026/03/example_1774841850_dfcbe8f0.png",
"sizefor": 1872816,
"size": "1.79 MB",
"library": "Images",
"type": "png",
"uploaded_at": "2026-03-30 10:37:30"
},
"postData": {
"title": "Test AJAX Form",
"category": "1",
"content": "This is test content for ajax form"
},
"errors": [],
"files": {
"image": {
"path": "2026/03/example_1774841850_dfcbe8f0.png",
"sizefor": 1872816,
"size": "1.79 MB",
"library": "Images",
"type": "png",
"uploaded_at": "2026-03-30 10:37:30"
}
}
}Response Error Validasi
{
"success": false,
"processed": true,
"informasi": "data",
"message": "Mohon perbaiki kesalahan berikut!",
"errors": {
"errors_title": "Judul minimal 3 karakter",
"errors_content": "Konten minimal 10 karakter"
},
"data": {
"category": "1"
},
"postData": {
"title": "ab",
"category": "1",
"content": "test"
}
}Konsep Penting
1. Header X-Requested-With
Header X-Requested-With: XMLHttpRequest WAJIB dikirim agar NexaForm mendeteksi request sebagai AJAX:
fetch('/example/ajax', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest' // WAJIB!
},
body: formData
})2. FormData untuk File Upload
Gunakan FormData untuk handle file upload via AJAX:
const formData = new FormData(form);
// Jangan set Content-Type header!
// Browser akan otomatis set dengan boundary yang benar3. Auto JSON Response
Saat setAjax(true) aktif:
process()otomatis return JSON danexit- Tidak perlu manual
echo json_encode() - Tidak perlu manual
header('Content-Type: application/json')
4. Error Handling
Handle error per field dengan loop melalui result.errors:
if (result.errors) {
for (const [field, message] of Object.entries(result.errors)) {
const fieldName = field.replace('errors_', '');
const input = form.querySelector(`[name="${fieldName}"]`);
if (input) {
input.classList.add('is-invalid');
input.parentElement.querySelector('.invalid-feedback').textContent = message;
}
}
}Testing Results
Form AJAX ini sudah di-test dan bekerja dengan baik:
- Validasi error tampil di field yang salah - WORKING
- Success message tampil tanpa reload - WORKING
- Form di-reset setelah sukses - WORKING
- File upload via AJAX - WORKING
- Progress indicator - WORKING
- Loading state pada button - WORKING
Perbandingan: Form Biasa vs AJAX
Form Biasa (Redirect)
$form = $this->createForm()
->fields([...])
->setSuccess('Data berhasil!')
->setError('Ada kesalahan!');
$result = $form->process();
if ($result['success']) {
$this->setState('form_data', $templateVars);
$this->redirect('/example'); // Reload halaman
} else {
$this->setState('form_data', $templateVars);
$this->redirect('/example'); // Reload halaman
}Form AJAX (No Redirect)
$form = $this->createForm()
->fields([...])
->setAjax(true) // Enable AJAX
->setSuccess('Data berhasil!')
->setError('Ada kesalahan!');
$result = $form->process(); // Auto return JSON dan exit
// Code di bawah tidak dijalankan untuk AJAX requestLanjut
Sebelumnya: Form Responses. Berikutnya: File Uploads. Indeks Forms · Dokumentasi · Beranda.