UNPKG

create-nexaui-electron

Version:

Create Nexa App - Tool untuk membuat aplikasi Nexa Electron

420 lines (365 loc) 13.9 kB
import { NexaFilePreview } from "./NexaFilePreview.js"; export function createForm(ret, callback) { const formInput = ret.formid; const submitForm = ret.submitid; const fileInput = ret.fileInput; const validasi = ret.validasi || {}; // Ambil aturan validasi jika ada // Periksa apakah form ada const form = document.getElementById(formInput); if (!form) { console.error(`Form dengan ID "${formInput}" tidak ditemukan`); return Promise.reject( new Error(`Form dengan ID "${formInput}" tidak ditemukan`) ); } // Periksa apakah tombol submit ada const submitButton = document.getElementById(submitForm); if (!submitButton) { console.error(`Tombol submit dengan ID "${submitForm}" tidak ditemukan`); return Promise.reject( new Error(`Tombol submit dengan ID "${submitForm}" tidak ditemukan`) ); } // Inisialisasi file input jika ada if (fileInput) { initFileInput(); } // Inisialisasi preview file const initFilePreview = () => { const filePreview = new NexaFilePreview(); return filePreview; }; // Inisialisasi input file dengan fungsi drag & drop const initFileInput = () => { const fileInputs = form.querySelectorAll(".form-nexa-file-input"); fileInputs.forEach((input) => { const dropZone = input.closest(".form-nexa-file-dragdrop"); if (!dropZone) return; const label = dropZone.querySelector(".form-nexa-file-label"); const preview = dropZone .closest(".form-nexa") .querySelector(".form-nexa-file-preview"); const fileList = dropZone .closest(".form-nexa") .querySelector(".form-nexa-file-list"); // Mencegah perilaku drag & drop default ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { dropZone.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false); }); // Sorot area drop saat file di-drag di atasnya ["dragenter", "dragover"].forEach((eventName) => { dropZone.addEventListener(eventName, highlight, false); }); // Hilangkan sorotan saat file meninggalkan area atau di-drop ["dragleave", "drop"].forEach((eventName) => { dropZone.addEventListener(eventName, unhighlight, false); }); // Tangani file yang di-drop dropZone.addEventListener("drop", handleDrop, false); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } function highlight(e) { dropZone.classList.add("form-nexa-file-dragdrop-highlight"); } function unhighlight(e) { dropZone.classList.remove("form-nexa-file-dragdrop-highlight"); } function handleDrop(e) { const dt = e.dataTransfer; const files = dt.files; input.files = files; // Picu event change untuk memperbarui preview const event = new Event("change"); input.dispatchEvent(event); } // Tangani perubahan file yang dipilih input.addEventListener("change", () => { const files = Array.from(input.files); updateFileList(files, fileList); }); }); }; // Fungsi untuk memperbarui daftar file const updateFileList = (files, listElement) => { if (!listElement) return; listElement.innerHTML = ""; files.forEach((file) => { const item = document.createElement("div"); item.className = "form-nexa-file-item"; item.innerHTML = ` <span class="file-name">${file.name}</span> <span class="file-size">(${formatFileSize(file.size)})</span> `; listElement.appendChild(item); }); }; // Fungsi untuk memformat ukuran file const formatFileSize = (bytes) => { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }; // Inisialisasi input file jika ada const fileInputs = form.querySelectorAll(".form-nexa-file-input"); if (fileInputs.length > 0) { initFileInput(); initFilePreview(); } // Mengembalikan Promise untuk menangani data form return new Promise((resolve) => { // Fungsi untuk menghapus class error const removeErrorClass = (element) => { const formGroup = element.closest(".form-nexa"); if (formGroup) { formGroup.classList.remove("form-error"); const errorMessage = formGroup.querySelector(".error-message"); if (errorMessage) { errorMessage.remove(); } } }; // Fungsi validasi berdasarkan tipe input const validateInput = (element) => { const type = element.type; const name = element.name; const placeholder = element.placeholder; // Check custom validation rules first if (validasi[name]) { if (Array.isArray(validasi[name])) { const [minLength, maxLength] = validasi[name]; if (element.value.length < minLength) { return `${placeholder} minimal ${minLength} karakter`; } if (maxLength && element.value.length > maxLength) { return `${placeholder} tidak boleh lebih dari ${maxLength} karakter`; } } else { const minLength = validasi[name]; if (element.value.length < minLength) { return `${name} minimal ${minLength} karakter`; } } } switch (type) { case "email": const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(element.value)) { return `${name} harus berupa email yang valid`; } break; case "tel": const cleanNumber = element.value.replace(/[^\d]/g, ""); const isStartWith08 = /^08\d{8,11}$/.test(cleanNumber); const isStartWith62 = /^62\d{9,12}$/.test(cleanNumber); const isStartWithArea = /^[2-3]\d{8,11}$/.test(cleanNumber); if (!element.value) { return `${name} tidak boleh kosong`; } if (!isStartWith08 && !isStartWith62 && !isStartWithArea) { return `${name} tidak valid. Gunakan format: 08xx, +62xx, 02x, atau 03x`; } if (cleanNumber.length < 8 || cleanNumber.length > 13) { return `${name} harus antara 8-13 digit`; } break; case "radio": const radioGroup = document.querySelectorAll(`input[name="${name}"]`); const isChecked = Array.from(radioGroup).some( (radio) => radio.checked ); if (!isChecked) { return `${name} harus dipilih`; } break; case "checkbox": const checkboxGroup = document.querySelectorAll( `input[name="${name}"]:checked` ); if (checkboxGroup.length === 0) { return `${name} minimal pilih satu`; } break; case "select-one": if (!element.value) { return `${name} harus dipilih`; } break; case "file": { if (element.required && element.files.length === 0) { return `${name} harus diisi`; } if (element.files.length > 0) { // Check total size of all files const totalSize = Array.from(element.files).reduce( (sum, file) => sum + file.size, 0 ); // Get max size from data attribute const maxSizeStr = element.dataset.maxSize || "5MB"; const maxSizeMB = parseInt(maxSizeStr); const maxSizeBytes = maxSizeMB * 1024 * 1024; if (totalSize > maxSizeBytes) { return `Total ukuran file tidak boleh lebih dari ${maxSizeMB}MB`; } // Check number of files if multiple if (element.multiple) { const maxFiles = parseInt(element.dataset.maxFiles || "5"); if (element.files.length > maxFiles) { return `Maksimal ${maxFiles} file yang dapat diunggah`; } } // Check file types if (element.accept) { const allowedTypes = element.accept .split(",") .map((type) => type.trim()); const fileType = element.files[0].type; const fileExtension = "." + element.files[0].name.split(".").pop().toLowerCase(); const isValidType = allowedTypes.some((type) => { if (type.startsWith(".")) { // Check file extension return type.toLowerCase() === fileExtension; } else { // Check mime type return fileType.match(new RegExp(type.replace("*", ".*"))); } }); if (!isValidType) { return `Tipe file ${name} tidak didukung. Gunakan: ${element.accept}`; } } } break; } default: if (!element.value.trim()) { return `${name} tidak boleh kosong`; } } return null; }; const addErrorClass = (element, message) => { const formGroup = element.closest(".form-nexa"); if (formGroup) { formGroup.classList.add("form-error"); const existingError = formGroup.querySelector(".error-message"); if (existingError) { existingError.remove(); } const errorDiv = document.createElement("div"); errorDiv.className = "error-message"; errorDiv.textContent = message; formGroup.appendChild(errorDiv); } }; // Event listeners untuk validasi real-time form.querySelectorAll("[name]").forEach((element) => { const events = ["input", "change", "blur"]; events.forEach((eventType) => { element.addEventListener(eventType, () => { const errorMessage = validateInput(element); if (errorMessage) { addErrorClass(element, errorMessage); } else { removeErrorClass(element); } }); }); }); // Tambahkan event listener untuk input file form.querySelectorAll('input[type="file"]').forEach((element) => { const events = ["input", "change", "blur"]; events.forEach((eventType) => { element.addEventListener(eventType, () => { // Validasi file const errorMessage = validateInput(element); if (errorMessage) { addErrorClass(element, errorMessage); } else { removeErrorClass(element); } // Preview akan ditangani oleh NexaFilePreview secara otomatis }); }); }); // Tangani submit form submitButton.addEventListener("click", async (e) => { e.preventDefault(); // Cegah submit form default const formData = new FormData(form); const dataObject = {}; // Tangani input file terlebih dahulu for (const fileInput of form.querySelectorAll('input[type="file"]')) { const name = fileInput.name; if (!name) continue; // Lewati jika tidak ada atribut name if (fileInput.multiple && fileInput.files.length > 0) { // Tangani multiple files dataObject[name] = await Promise.all( Array.from(fileInput.files).map(async (file) => ({ name: file.name, size: file.size, type: file.type, content: await convertToBase64(file), // Tambah konversi ke base64 })) ); } else if (fileInput.files.length > 0) { // Tangani single file const file = fileInput.files[0]; dataObject[name] = { name: file.name, size: file.size, type: file.type, content: await convertToBase64(file), // Tambah konversi ke base64 }; } } // Tangani field form lainnya formData.forEach((value, key) => { if (!(value instanceof File) && !dataObject[key]) { dataObject[key] = value; } }); // Validasi semua field sebelum submit let isValid = true; form.querySelectorAll("[name]").forEach((element) => { const errorMessage = validateInput(element); if (errorMessage) { addErrorClass(element, errorMessage); isValid = false; } }); if (isValid && callback) { callback({ response: { status: "success", message: "Form berhasil dikirim", data: dataObject, }, }); form.reset(); } }); resolve(); }); } // Tambahkan fungsi untuk konversi file ke base64 const convertToBase64 = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { // Hapus prefix data URL (contoh: "data:image/png;base64,") const base64String = reader.result.split(",")[1]; resolve(base64String); }; reader.onerror = (error) => { console.error("Error converting file to base64:", error); reject(error); }; }); };