htmly/system/resources/js/form.builder.js
danpros 6684d027e8 Simplify the select option
Make the select options value similar to future theme settings.
2025-10-21 07:32:16 +07:00

283 lines
No EOL
10 KiB
JavaScript

/*!
* Simple Form Builder for HTMLy - Author @danpros
*
* const fields = [];
*/
let editingIndex = -1; // Track index of the field being edited (-1 means not editing)
// Elements
const typeEl = document.getElementById('type');
const nameEl = document.getElementById('name');
const labelEl = document.getElementById('label');
const valueEl = document.getElementById('value');
const infoEl = document.getElementById('info'); // New element for 'info'
const optionsContainerEl = document.getElementById('options-container');
const optionListEl = document.getElementById('option-list');
const addOptionBtn = document.getElementById('add-option');
const addFieldBtn = document.getElementById('add-field');
const formPreviewEl = document.getElementById('form-preview');
const jsonOutputEl = document.getElementById('json-output');
// Show or hide the options container and reset fields
typeEl.addEventListener('change', () => {
nameEl.value = '';
labelEl.value = '';
valueEl.value = '';
infoEl.value = ''; // Reset 'info' field
optionsContainerEl.style.display = typeEl.value === 'select' ? 'block' : 'none';
optionListEl.innerHTML = '';
addFieldBtn.textContent = "Add Field";
});
// Add new option for select field
addOptionBtn.addEventListener('click', () => {
const optionDiv = document.createElement('div');
optionDiv.setAttribute('class', 'option-item row mt-1 mb-1');
const col1Div = document.createElement('div');
col1Div.classList.add('col');
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.placeholder = 'Value (required)';
valueInput.setAttribute('class', 'option-value form-control');
valueInput.addEventListener('input', () => {
valueInput.value = valueInput.value.replace(/\s+/g, ''); // Remove spaces in real-time
});
const removeBtn = document.createElement('button');
removeBtn.textContent = 'Remove';
removeBtn.addEventListener('click', () => optionDiv.remove());
removeBtn.setAttribute('class', 'btn btn-danger');
col1Div.appendChild(valueInput);
optionDiv.appendChild(col1Div);
optionDiv.appendChild(removeBtn);
optionListEl.appendChild(optionDiv);
});
// Add field button logic (works for both adding and updating fields)
addFieldBtn.addEventListener('click', () => {
let field = {
type: typeEl.value,
name: nameEl.value.trim().replace(/\s+/g, ''), // Remove spaces
label: labelEl.value.trim(),
value: valueEl.value.trim(),
info: infoEl.value.trim() // Add the 'info' property
};
if (field.type === 'select') {
const options = Array.from(document.querySelectorAll('.option-item')).map(item => {
return item.querySelector('.option-value').value.trim().replace(/\s+/g, '');
});
if (options.some(opt => !opt)) {
alert("All options for a select must have a value!");
return;
}
field.options = options; // now an array of strings
}
if (!field.name || !field.label || !field.type) {
alert("Please fill in all required fields: Type, Name, and Label.");
return;
}
if (editingIndex === -1) {
const existingNames = fields.map(f => f.name);
if (existingNames.includes(field.name)) {
const timestamp = Date.now();
field.name = `${field.name}_${timestamp}`;
}
fields.push(field);
} else {
fields[editingIndex] = field;
editingIndex = -1;
addFieldBtn.textContent = "Add Field";
}
updatePreviewAndOutput();
const target = document.getElementById(field.name + '-form-preview');
if (target) {
target.scrollIntoView({
behavior: 'smooth'
});
}
});
// Delete field logic
function deleteField(index) {
if (confirm("Are you sure you want to delete this field?")) {
fields.splice(index, 1);
updatePreviewAndOutput();
}
}
// Edit field logic
function editField(index) {
const field = fields[index];
typeEl.value = field.type;
nameEl.value = field.name.replace(/\s+/g, ''); // Remove spaces
labelEl.value = field.label;
valueEl.value = field.value || '';
infoEl.value = field.info || ''; // Populate 'info' input field
if (field.type === 'select') {
optionsContainerEl.style.display = 'block';
optionListEl.innerHTML = '';
// Accept both string array and legacy object array ({value: 'x'})
field.options.forEach(opt => {
const valueText = (typeof opt === 'string') ? opt : (opt && opt.value ? opt.value : '');
const optionDiv = document.createElement('div');
optionDiv.setAttribute('class', 'option-item row mt-1 mb-1');
const col1Div = document.createElement('div');
col1Div.classList.add('col');
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.placeholder = 'Value (required)';
valueInput.value = valueText.replace(/\s+/g, ''); // Remove spaces
valueInput.setAttribute('class', 'option-value form-control');
valueInput.addEventListener('input', () => {
valueInput.value = valueInput.value.replace(/\s+/g, ''); // Remove spaces in real-time
});
const removeBtn = document.createElement('button');
removeBtn.textContent = 'Remove';
removeBtn.setAttribute('class', 'btn btn-danger');
removeBtn.addEventListener('click', () => {
if (confirm("Are you sure you want to remove this option?")) {
optionDiv.remove();
}
});
col1Div.appendChild(valueInput);
optionDiv.appendChild(col1Div);
optionDiv.appendChild(removeBtn);
optionListEl.appendChild(optionDiv);
});
} else {
optionsContainerEl.style.display = 'none';
optionListEl.innerHTML = '';
}
editingIndex = index;
addFieldBtn.textContent = "Update Field";
const statusEl = document.getElementById('input-status');
if (statusEl) {
statusEl.innerHTML = `<div class="callout callout-warning">Editing:</small> <code>${field.label}</code></div>`;
}
const formInput = document.getElementById('form-input');
if (formInput) {
formInput.scrollIntoView({
behavior: 'smooth'
});
}
}
// Update preview with Edit and Delete buttons
function updatePreviewAndOutput() {
formPreviewEl.innerHTML = '';
fields.forEach((f, index) => {
const wrapper = document.createElement('div');
wrapper.setAttribute('class', 'field-preview callout callout-info');
wrapper.setAttribute('id', f.name + '-form-preview');
const label = document.createElement('label');
label.innerHTML = `${f.label} <br><small>Field ID:</small> <code>${f.name}</code>`;
wrapper.appendChild(label);
let el;
if (f.type === 'textarea') {
el = document.createElement('textarea');
el.placeholder = f.info || '';
el.setAttribute('class', 'form-control');
el.value = f.value || '';
} else if (f.type === 'checkbox') {
const spacer = document.createElement('br');
el = document.createElement('input');
el.type = 'checkbox';
el.checked = (f.value === 'true' || f.value === true);
const checkboxLabel = document.createElement('span');
checkboxLabel.textContent = ` ${f.label} `;
wrapper.appendChild(spacer);
wrapper.appendChild(el);
wrapper.appendChild(checkboxLabel);
el = null;
} else if (f.type === 'select') {
el = document.createElement('select');
el.setAttribute('class', 'form-control');
// f.options is an array of strings
(f.options || []).forEach(opt => {
const option = document.createElement('option');
option.value = opt;
option.textContent = opt;
el.appendChild(option);
});
} else {
el = document.createElement('input');
el.type = f.type;
el.value = f.value || '';
el.placeholder = f.info || '';
el.setAttribute('class', 'form-control');
}
if (el) {
wrapper.appendChild(el);
}
if (f.info) {
const tip = document.createElement('span');
tip.innerHTML = `<small class="d-block mt-1"><em>${f.info}</em></small>`;
wrapper.appendChild(tip);
}
const editBtn = document.createElement('button');
editBtn.textContent = 'Edit';
editBtn.addEventListener('click', () => editField(index));
editBtn.setAttribute('class', 'btn btn-primary btn-xs m-1');
wrapper.appendChild(editBtn);
const deleteBtn = document.createElement('button');
deleteBtn.textContent = 'Delete';
deleteBtn.addEventListener('click', () => deleteField(index));
deleteBtn.setAttribute('class', 'btn btn-danger btn-xs m-1');
wrapper.appendChild(deleteBtn);
formPreviewEl.appendChild(wrapper);
});
jsonOutputEl.value = JSON.stringify(fields, null, 2);
// Reset form inputs
nameEl.value = '';
labelEl.value = '';
valueEl.value = '';
infoEl.value = ''; // Reset 'info' field
optionsContainerEl.style.display = 'none';
optionListEl.innerHTML = '';
typeEl.value = "text";
addFieldBtn.textContent = "Add Field";
const statusEl = document.getElementById('input-status');
if (statusEl) statusEl.innerHTML = '';
}
document.addEventListener('DOMContentLoaded', () => {
updatePreviewAndOutput();
// Real-time removal of spaces in the name field
nameEl.addEventListener('input', () => {
nameEl.value = nameEl.value.replace(/\s+/g, ''); // Remove spaces in real-time
});
valueEl.addEventListener('input', () => {
if (typeEl.value === 'select') {
valueEl.value = valueEl.value.replace(/\s+/g, ''); // Remove spaces
}
});
});