Nxdom Framework

Nusantara eXtreme Development Object Model — dokumentasi resmi

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']]); } }
Catatan Penting:
  • Saat setAjax(true) aktif, process() akan otomatis return JSON dan exit
  • Tidak perlu manual sendJsonResponse() atau echo 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 benar

3. Auto JSON Response

Saat setAjax(true) aktif:

  • process() otomatis return JSON dan exit
  • 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 request

Lanjut

Sebelumnya: Form Responses. Berikutnya: File Uploads. Indeks Forms · Dokumentasi · Beranda.