create-nexaui-electron
Version:
Create Nexa App - Tool untuk membuat aplikasi Nexa Electron
543 lines (472 loc) • 17.7 kB
JavaScript
import { NexaFilePreview } from "./NexaFilePreview.js";
export function createForm(ret, callback) {
const formSelector = ret.formid;
const submitSelector = ret.submitid;
const fileInput = ret.fileInput;
const validasi = ret.validasi || {}; // Get validation rules if they exist
// Helper function to get element by selector
const getElement = (selector) => {
if (!selector) return null;
// Try querySelector first
const element = document.querySelector(selector);
if (element) return element;
// If not found and selector doesn't start with # or ., try as ID
if (!selector.startsWith("#") && !selector.startsWith(".")) {
const elementById = document.getElementById(selector);
if (elementById) return elementById;
}
return null;
};
// Get form element using the new helper
const form = getElement(formSelector);
if (!form) {
console.error(`Form with selector "${formSelector}" not found`);
return Promise.reject(
new Error(`Form with selector "${formSelector}" not found`)
);
}
// Get submit button using the new helper
const submitButton = getElement(submitSelector);
if (!submitButton) {
console.error(`Submit button with selector "${submitSelector}" not found`);
return Promise.reject(
new Error(`Submit button with selector "${submitSelector}" not found`)
);
}
// formInput,submitForm
if (fileInput) {
initFileInput();
}
// Add file preview initialization
const initFilePreview = () => {
const filePreview = new NexaFilePreview();
return filePreview;
};
// Initialize file preview if there are file inputs
const fileInputs = form.querySelectorAll('input[type="file"]');
if (fileInputs.length > 0) {
initFilePreview();
}
// Fungsi untuk menginisialisasi file input
const initFileInput = () => {
const fileInputs = form.querySelectorAll(".form-nexa-file-input");
fileInputs.forEach((input) => {
const container = input.closest(".form-nexa");
const dragDropArea = container.querySelector(".form-nexa-file-dragdrop");
const preview = container.querySelector(".form-nexa-file-preview");
const fileList = container.querySelector(".form-nexa-file-list");
const errorMessage = container.querySelector(".error-message");
// Inisialisasi NexaFilePreview jika preview tersedia
if (preview) {
const filePreview = new NexaFilePreview();
}
// Handle Drag & Drop events
if (dragDropArea) {
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
dragDropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
["dragenter", "dragover"].forEach((eventName) => {
dragDropArea.addEventListener(eventName, () => {
dragDropArea.classList.add("highlight");
});
});
["dragleave", "drop"].forEach((eventName) => {
dragDropArea.addEventListener(eventName, () => {
dragDropArea.classList.remove("highlight");
});
});
dragDropArea.addEventListener("drop", (e) => {
const dt = e.dataTransfer;
const files = dt.files;
input.files = files;
// Trigger change event untuk memicu validasi dan preview
const changeEvent = new Event("change");
input.dispatchEvent(changeEvent);
});
}
// Handle file validation
input.addEventListener("change", () => {
const maxSize = input.dataset.maxSize;
const maxFiles = parseInt(input.dataset.maxFiles);
const files = Array.from(input.files);
// Reset error message
errorMessage.textContent = "";
// Validate number of files
if (input.multiple && files.length > maxFiles) {
errorMessage.textContent = `Maksimal ${maxFiles} file yang dapat diunggah`;
input.value = "";
return;
}
// Validate file size
const maxSizeBytes = parseFileSize(maxSize);
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
if (totalSize > maxSizeBytes) {
errorMessage.textContent = `Total ukuran file tidak boleh lebih dari ${maxSize}`;
input.value = "";
return;
}
// Update file list
if (fileList) {
fileList.innerHTML = files
.map(
(file) => `
<div class="file-item">
<span class="file-name">${file.name}</span>
<span class="file-size">(${formatFileSize(file.size)})</span>
</div>
`
)
.join("");
}
});
});
};
// Helper function untuk parse ukuran file
function parseFileSize(size) {
const units = {
B: 1,
KB: 1024,
MB: 1024 * 1024,
GB: 1024 * 1024 * 1024,
};
const match = size.match(/^(\d+)\s*(B|KB|MB|GB)$/i);
if (match) {
const [, value, unit] = match;
return parseInt(value) * units[unit.toUpperCase()];
}
return parseInt(size);
}
// Helper function untuk format ukuran file
function formatFileSize(bytes) {
const sizes = ["B", "KB", "MB", "GB"];
if (bytes === 0) return "0 B";
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + " " + sizes[i];
}
// Tambahkan fungsi untuk mengisi nilai input
const setFormValues = (values) => {
if (!values || typeof values !== "object") return;
Object.entries(values).forEach(([name, value]) => {
const elements = form.querySelectorAll(`[name="${name}"]`);
elements.forEach((element) => {
switch (element.type) {
case "file":
// File input tidak bisa diisi secara langsung karena alasan keamanan
console.warn("File input values cannot be set programmatically");
break;
case "checkbox":
case "radio":
element.checked = Array.isArray(value)
? value.includes(element.value)
: element.value === value;
break;
case "select-one":
case "select-multiple":
if (Array.isArray(value)) {
Array.from(element.options).forEach((option) => {
option.selected = value.includes(option.value);
});
} else {
element.value = value;
}
break;
default:
element.value = value;
}
// Trigger change event untuk memicu validasi
const event = new Event("change", { bubbles: true });
element.dispatchEvent(event);
});
});
};
// Jika ada nilai awal di ret.value, set nilai form
if (ret.value) {
setFormValues(ret.value);
}
// 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) {
if (type === "file") {
return `${placeholder} minimal ${minLength}MB`;
} else {
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 `${placeholder} tidak boleh kosong`;
}
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 validasi or data attribute
const maxSizeMB = validasi[name]
? validasi[name][0]
: element.dataset.maxSize
? parseInt(element.dataset.maxSize)
: 15;
const maxSizeBytes = maxSizeMB * 1024 * 1024;
if (totalSize > maxSizeBytes) {
return `${placeholder} minimal ${maxSizeMB}MB`;
}
// Check number of files if multiple
if (element.multiple) {
const maxFiles = element.dataset.maxFiles
? parseInt(element.dataset.maxFiles)
: 5;
if (element.files.length > maxFiles) {
return `${placeholder} minimal ${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 `${placeholder} 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();
// Create data object to store form values
const dataObject = {
// Include initial values from ret.value if they exist
...(ret.value || {}),
};
// Get all input elements from the form
const inputs = form.querySelectorAll("[name]");
// Collect form data manually instead of using FormData
for (const input of inputs) {
if (input.type === "file") {
// Handle file inputs separately
if (input.files.length > 0) {
if (input.multiple) {
// Handle multiple files
dataObject[input.name] = await Promise.all(
Array.from(input.files).map(async (file) => ({
name: file.name,
size: file.size,
type: file.type,
content: await convertToBase64(file),
}))
);
} else {
// Handle single file
const file = input.files[0];
dataObject[input.name] = {
name: file.name,
size: file.size,
type: file.type,
content: await convertToBase64(file),
};
}
}
} else {
// Handle regular inputs
// Only override if input has a value
if (input.value) {
dataObject[input.name] = input.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, // Now includes both form values and initial values
},
});
// Reset all form inputs manually
inputs.forEach((input) => {
if (input.type === "file") {
input.value = "";
} else {
input.value = "";
}
});
// Clear file preview if exists
const formResponse = document.querySelector(".form-nexa-file-preview");
if (formResponse) {
formResponse.innerHTML = "";
}
}
});
resolve({
setValues: setFormValues, // Ekspos fungsi setValues
});
});
}
// 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);
};
});
};