form.builder.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*!
  2. * Simple Form Builder for HTMLy - Author @danpros
  3. *
  4. * const fields = [];
  5. */
  6. let editingIndex = -1; // Track index of the field being edited (-1 means not editing)
  7. // Elements
  8. const typeEl = document.getElementById('type');
  9. const nameEl = document.getElementById('name');
  10. const labelEl = document.getElementById('label');
  11. const valueEl = document.getElementById('value');
  12. const infoEl = document.getElementById('info'); // New element for 'info'
  13. const optionsContainerEl = document.getElementById('options-container');
  14. const optionListEl = document.getElementById('option-list');
  15. const addOptionBtn = document.getElementById('add-option');
  16. const addFieldBtn = document.getElementById('add-field');
  17. const formPreviewEl = document.getElementById('form-preview');
  18. const jsonOutputEl = document.getElementById('json-output');
  19. // Show or hide the options container and reset fields
  20. typeEl.addEventListener('change', () => {
  21. nameEl.value = '';
  22. labelEl.value = '';
  23. valueEl.value = '';
  24. infoEl.value = ''; // Reset 'info' field
  25. optionsContainerEl.style.display = typeEl.value === 'select' ? 'block' : 'none';
  26. optionListEl.innerHTML = '';
  27. addFieldBtn.textContent = "Add Field";
  28. });
  29. // Add new option for select field
  30. addOptionBtn.addEventListener('click', () => {
  31. const optionDiv = document.createElement('div');
  32. optionDiv.setAttribute('class', 'option-item row mt-1 mb-1');
  33. const col1Div = document.createElement('div');
  34. col1Div.classList.add('col');
  35. const valueInput = document.createElement('input');
  36. valueInput.type = 'text';
  37. valueInput.placeholder = 'Value (required)';
  38. valueInput.setAttribute('class', 'option-value form-control');
  39. valueInput.addEventListener('input', () => {
  40. valueInput.value = valueInput.value.replace(/\s+/g, ''); // Remove spaces in real-time
  41. });
  42. const removeBtn = document.createElement('button');
  43. removeBtn.textContent = 'Remove';
  44. removeBtn.addEventListener('click', () => optionDiv.remove());
  45. removeBtn.setAttribute('class', 'btn btn-danger');
  46. col1Div.appendChild(valueInput);
  47. optionDiv.appendChild(col1Div);
  48. optionDiv.appendChild(removeBtn);
  49. optionListEl.appendChild(optionDiv);
  50. });
  51. // Add field button logic (works for both adding and updating fields)
  52. addFieldBtn.addEventListener('click', () => {
  53. let field = {
  54. type: typeEl.value,
  55. name: nameEl.value.trim().replace(/\s+/g, ''), // Remove spaces
  56. label: labelEl.value.trim(),
  57. value: valueEl.value.trim(),
  58. info: infoEl.value.trim() // Add the 'info' property
  59. };
  60. if (field.type === 'select') {
  61. const options = Array.from(document.querySelectorAll('.option-item')).map(item => {
  62. return item.querySelector('.option-value').value.trim().replace(/\s+/g, '');
  63. });
  64. if (options.some(opt => !opt)) {
  65. alert("All options for a select must have a value!");
  66. return;
  67. }
  68. field.options = options; // now an array of strings
  69. }
  70. if (!field.name || !field.label || !field.type) {
  71. alert("Please fill in all required fields: Type, Name, and Label.");
  72. return;
  73. }
  74. if (editingIndex === -1) {
  75. const existingNames = fields.map(f => f.name);
  76. if (existingNames.includes(field.name)) {
  77. const timestamp = Date.now();
  78. field.name = `${field.name}_${timestamp}`;
  79. }
  80. fields.push(field);
  81. } else {
  82. fields[editingIndex] = field;
  83. editingIndex = -1;
  84. addFieldBtn.textContent = "Add Field";
  85. }
  86. updatePreviewAndOutput();
  87. const target = document.getElementById(field.name + '-form-preview');
  88. if (target) {
  89. target.scrollIntoView({
  90. behavior: 'smooth'
  91. });
  92. }
  93. });
  94. // Delete field logic
  95. function deleteField(index) {
  96. if (confirm("Are you sure you want to delete this field?")) {
  97. fields.splice(index, 1);
  98. updatePreviewAndOutput();
  99. }
  100. }
  101. // Edit field logic
  102. function editField(index) {
  103. const field = fields[index];
  104. typeEl.value = field.type;
  105. nameEl.value = field.name.replace(/\s+/g, ''); // Remove spaces
  106. labelEl.value = field.label;
  107. valueEl.value = field.value || '';
  108. infoEl.value = field.info || ''; // Populate 'info' input field
  109. if (field.type === 'select') {
  110. optionsContainerEl.style.display = 'block';
  111. optionListEl.innerHTML = '';
  112. // Accept both string array and legacy object array ({value: 'x'})
  113. field.options.forEach(opt => {
  114. const valueText = (typeof opt === 'string') ? opt : (opt && opt.value ? opt.value : '');
  115. const optionDiv = document.createElement('div');
  116. optionDiv.setAttribute('class', 'option-item row mt-1 mb-1');
  117. const col1Div = document.createElement('div');
  118. col1Div.classList.add('col');
  119. const valueInput = document.createElement('input');
  120. valueInput.type = 'text';
  121. valueInput.placeholder = 'Value (required)';
  122. valueInput.value = valueText.replace(/\s+/g, ''); // Remove spaces
  123. valueInput.setAttribute('class', 'option-value form-control');
  124. valueInput.addEventListener('input', () => {
  125. valueInput.value = valueInput.value.replace(/\s+/g, ''); // Remove spaces in real-time
  126. });
  127. const removeBtn = document.createElement('button');
  128. removeBtn.textContent = 'Remove';
  129. removeBtn.setAttribute('class', 'btn btn-danger');
  130. removeBtn.addEventListener('click', () => {
  131. if (confirm("Are you sure you want to remove this option?")) {
  132. optionDiv.remove();
  133. }
  134. });
  135. col1Div.appendChild(valueInput);
  136. optionDiv.appendChild(col1Div);
  137. optionDiv.appendChild(removeBtn);
  138. optionListEl.appendChild(optionDiv);
  139. });
  140. } else {
  141. optionsContainerEl.style.display = 'none';
  142. optionListEl.innerHTML = '';
  143. }
  144. editingIndex = index;
  145. addFieldBtn.textContent = "Update Field";
  146. const statusEl = document.getElementById('input-status');
  147. if (statusEl) {
  148. statusEl.innerHTML = `<div class="callout callout-warning">Editing:</small> <code>${field.label}</code></div>`;
  149. }
  150. const formInput = document.getElementById('form-input');
  151. if (formInput) {
  152. formInput.scrollIntoView({
  153. behavior: 'smooth'
  154. });
  155. }
  156. }
  157. // Update preview with Edit and Delete buttons
  158. function updatePreviewAndOutput() {
  159. formPreviewEl.innerHTML = '';
  160. fields.forEach((f, index) => {
  161. const wrapper = document.createElement('div');
  162. wrapper.setAttribute('class', 'field-preview callout callout-info');
  163. wrapper.setAttribute('id', f.name + '-form-preview');
  164. const label = document.createElement('label');
  165. label.innerHTML = `${f.label} <br><small>Field ID:</small> <code>${f.name}</code>`;
  166. wrapper.appendChild(label);
  167. let el;
  168. if (f.type === 'textarea') {
  169. el = document.createElement('textarea');
  170. el.placeholder = f.info || '';
  171. el.setAttribute('class', 'form-control');
  172. el.value = f.value || '';
  173. } else if (f.type === 'checkbox') {
  174. const spacer = document.createElement('br');
  175. el = document.createElement('input');
  176. el.type = 'checkbox';
  177. el.checked = (f.value === 'true' || f.value === true);
  178. const checkboxLabel = document.createElement('span');
  179. checkboxLabel.textContent = ` ${f.label} `;
  180. wrapper.appendChild(spacer);
  181. wrapper.appendChild(el);
  182. wrapper.appendChild(checkboxLabel);
  183. el = null;
  184. } else if (f.type === 'select') {
  185. el = document.createElement('select');
  186. el.setAttribute('class', 'form-control');
  187. // f.options is an array of strings
  188. (f.options || []).forEach(opt => {
  189. const option = document.createElement('option');
  190. option.value = opt;
  191. option.textContent = opt;
  192. el.appendChild(option);
  193. });
  194. } else {
  195. el = document.createElement('input');
  196. el.type = f.type;
  197. el.value = f.value || '';
  198. el.placeholder = f.info || '';
  199. el.setAttribute('class', 'form-control');
  200. }
  201. if (el) {
  202. wrapper.appendChild(el);
  203. }
  204. if (f.info) {
  205. const tip = document.createElement('span');
  206. tip.innerHTML = `<small class="d-block mt-1"><em>${f.info}</em></small>`;
  207. wrapper.appendChild(tip);
  208. }
  209. const editBtn = document.createElement('button');
  210. editBtn.textContent = 'Edit';
  211. editBtn.addEventListener('click', () => editField(index));
  212. editBtn.setAttribute('class', 'btn btn-primary btn-xs m-1');
  213. wrapper.appendChild(editBtn);
  214. const deleteBtn = document.createElement('button');
  215. deleteBtn.textContent = 'Delete';
  216. deleteBtn.addEventListener('click', () => deleteField(index));
  217. deleteBtn.setAttribute('class', 'btn btn-danger btn-xs m-1');
  218. wrapper.appendChild(deleteBtn);
  219. formPreviewEl.appendChild(wrapper);
  220. });
  221. jsonOutputEl.value = JSON.stringify(fields, null, 2);
  222. // Reset form inputs
  223. nameEl.value = '';
  224. labelEl.value = '';
  225. valueEl.value = '';
  226. infoEl.value = ''; // Reset 'info' field
  227. optionsContainerEl.style.display = 'none';
  228. optionListEl.innerHTML = '';
  229. typeEl.value = "text";
  230. addFieldBtn.textContent = "Add Field";
  231. const statusEl = document.getElementById('input-status');
  232. if (statusEl) statusEl.innerHTML = '';
  233. }
  234. document.addEventListener('DOMContentLoaded', () => {
  235. updatePreviewAndOutput();
  236. // Real-time removal of spaces in the name field
  237. nameEl.addEventListener('input', () => {
  238. nameEl.value = nameEl.value.replace(/\s+/g, ''); // Remove spaces in real-time
  239. });
  240. valueEl.addEventListener('input', () => {
  241. if (typeEl.value === 'select') {
  242. valueEl.value = valueEl.value.replace(/\s+/g, ''); // Remove spaces
  243. }
  244. });
  245. });