From 552d48a1424417da160c4952650ea6f4a3d7bafa Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 04 Jan 2026 20:18:23 +0000
Subject: [PATCH] =Taxonomy Selector and Creator refactor

---
 assets/js/concise/FormController.js |  223 +++++++++++--------------------------------------------
 1 files changed, 45 insertions(+), 178 deletions(-)

diff --git a/assets/js/concise/FormController.js b/assets/js/concise/FormController.js
index 38c1c22..a0388e2 100644
--- a/assets/js/concise/FormController.js
+++ b/assets/js/concise/FormController.js
@@ -1,7 +1,3 @@
-/**
- * Enhanced FormController - Manages forms with special fields, caching, and queue integration
- * Works with DataStore for CRUD operations and standalone for front-end forms
- */
 class FormController {
 	constructor(config = {}) {
 		this.config = {
@@ -289,6 +285,7 @@
 			status: '',
 			options: {
 				autosave: 'autosave' in formElement.dataset,
+				autoUpload: true,
 				saveDelay: this.autoSaveDefaults.delay,
 				endpoint: formElement.dataset.save ?? '',
 				formStatus: true,
@@ -334,7 +331,7 @@
 		this.initCharacterLimits(form);
 
 		// Initialize image upload fields
-		this.initImageUploadFields(form);
+		this.initImageUploadFields(form, formConfig);
 
 		// Initialize tabs if present
 		if (window.jvbTabs && form.querySelector('nav.tabs')) {
@@ -958,8 +955,8 @@
 	/**
 	 * Initialize image upload fields
 	 */
-	initImageUploadFields(form) {
-		window.jvbUploads.scanFields(form);
+	initImageUploadFields(form, config) {
+		window.jvbUploads.scanFields(form, config.options.autoUpload);
 	}
 
 	/* ========== Event Handlers ========== */
@@ -981,14 +978,7 @@
 				fullData: formData,
 				config: formConfig
 			});
-
-			// Don't delete yet - wait for success/error from subscriber
-			return;
 		}
-
-		// For forms that submit normally (not prevented)
-		// We can clean up the cache on successful submission
-		// This would typically be called from handleFormSuccess
 	}
 
 	handleFormSuccess(form, data) {
@@ -1320,7 +1310,7 @@
 				message: 'Please enter a valid URL starting with https://'
 			},
 			phone: {
-				pattern: /^[\d\s\-\+\(\)\.]+$/,
+				pattern: /^[\d\s\-+().]+$/,
 				message: 'Please enter a valid phone number'
 			},
 			number: {
@@ -1543,145 +1533,6 @@
 		}
 	}
 
-	/**
-	 * Validate all fields in a container (useful for step validation)
-	 */
-	validateAllFields(container) {
-		if (!container) return true;
-
-		const fields = container.querySelectorAll('.field:not([hidden])');
-		let allValid = true;
-
-		fields.forEach(fieldWrapper => {
-			// Skip complex parent wrappers (repeater, group) - validate their children
-			if (this.isComplexFieldWrapper(fieldWrapper)) {
-				return;
-			}
-
-			const input = fieldWrapper.querySelector('input:not([type="hidden"]), textarea, select');
-			if (input && !input.closest('[hidden]')) {
-				// Mark as touched so validation will run
-				const fieldName = fieldWrapper.dataset.field;
-				if (fieldName) {
-					this.touchedFields.add(fieldName);
-				}
-
-				const isValid = this.validateField(input, fieldWrapper);
-				if (!isValid) {
-					allValid = false;
-
-					// Scroll to first error
-					if (allValid === false) {
-						input.scrollIntoView({ behavior: 'smooth', block: 'center' });
-						input.focus();
-					}
-				}
-			}
-		});
-
-		return allValid;
-	}
-
-	/**
-	 * Check if field wrapper is a complex type (repeater, group, etc.)
-	 */
-	isComplexFieldWrapper(fieldWrapper) {
-		return fieldWrapper.classList.contains('repeater') ||
-			fieldWrapper.classList.contains('group') ||
-			fieldWrapper.classList.contains('upload');
-	}
-
-	/**
-	 * Special validation for repeater fields
-	 */
-	attachRepeaterValidation(form) {
-		// When a repeater row is added, attach validation to its fields
-		form.addEventListener('click', (e) => {
-			if (e.target.closest('.add-repeater-row')) {
-				// Wait for the DOM to update
-				setTimeout(() => {
-					const repeaterRows = form.querySelectorAll('.repeater-row');
-					repeaterRows.forEach(row => {
-						const inputs = row.querySelectorAll('input, textarea, select');
-						inputs.forEach(input => {
-							const fieldWrapper = this.findFieldWrapper(input);
-							if (fieldWrapper) {
-								// Validation listeners are already attached via event delegation
-								// Just clear any existing validation state for new rows
-								this.clearValidation(fieldWrapper);
-							}
-						});
-					});
-				}, 100);
-			}
-		});
-	}
-
-	/**
-	 * Special validation for group fields
-	 */
-	attachGroupValidation(form) {
-		// Group fields might have conditional fields
-		// Validate when conditions change
-		form.addEventListener('change', (e) => {
-			const changedInput = e.target.closest('input, select');
-			if (!changedInput) return;
-
-			// Check if this change affects conditional fields
-			const fieldName = changedInput.name;
-			if (!fieldName) return;
-
-			// Find any conditional fields that depend on this field
-			const conditionalFields = form.querySelectorAll(`[data-show-if*="${fieldName}"]`);
-			conditionalFields.forEach(conditionalField => {
-				// Clear validation for hidden fields
-				if (conditionalField.hidden) {
-					this.clearValidation(conditionalField);
-				}
-			});
-		});
-	}
-
-	/**
-	 * Reset validation state for a form
-	 */
-	resetForm(form) {
-		if (!form) return;
-
-		// Clear all touched fields
-		this.touchedFields.clear();
-
-		// Clear all validation states
-		const fields = form.querySelectorAll('.field');
-		fields.forEach(fieldWrapper => {
-			this.clearValidation(fieldWrapper);
-		});
-	}
-
-	/**
-	 * Get validation errors for a form
-	 */
-	getFormErrors(form) {
-		const errors = {};
-		const fields = form.querySelectorAll('.field.has-error');
-
-		fields.forEach(fieldWrapper => {
-			const fieldName = fieldWrapper.dataset.field;
-			const message = fieldWrapper.querySelector('.validation-message');
-			if (fieldName && message) {
-				errors[fieldName] = message.textContent;
-			}
-		});
-
-		return errors;
-	}
-
-	/**
-	 * Add custom validator
-	 */
-	addValidator(name, validator) {
-		this.validators[name] = validator;
-	}
 	/* ========== Auto-save functionality ========== */
 	/**
 	 * Get appropriate delay based on field type and context
@@ -1875,7 +1726,7 @@
 
 	/* ========== Form Data Methods ========== */
 
-	collectFormData(form, isInit = false) {
+	collectFormData(form) {
 		if (Object.hasOwn(form.dataset, 'timeline')) {
 			return this.collectTimeline(form);
 		}
@@ -1912,7 +1763,7 @@
 			if (this.ignore.includes(key) || key.endsWith('_temp')) {
 				continue;
 			}
-			const match = key.match(/^\[(\d+)\](.+)$/);
+			const match = key.match(/^\[(\d+)](.+)$/);
 			if (match) {
 				// Timeline-specific field: [postId]fieldName
 				const [, postId, fieldName] = match;
@@ -1950,7 +1801,7 @@
 	getFieldProcessor(key) {
 		if (key.includes('::')) return this.processGroupField;
 		if (key.includes(':')) return this.processRepeaterField;
-		if (/\[[^\]]+\]/.test(key)) return this.processLocationField;
+		if (/\[[^\]]+]/.test(key)) return this.processLocationField;
 		return this.processRegularField;
 	}
 
@@ -2122,7 +1973,9 @@
 			let p = field.querySelector('p');
 
 			title.textContent = fieldInfo.label;
-			let formatted = this.formatFieldValue(value, fieldInfo.type);
+
+
+			let formatted = this.formatFieldValue(value, fieldInfo.type, form);
 			if (this.isHtmlContent(formatted)) {
 				p.innerHTML = formatted;
 			} else {
@@ -2131,6 +1984,27 @@
 
 			summary.append(field);
 		}
+		let uploads = form.querySelectorAll('[data-upload-field]');
+		if (uploads) {
+			uploads.forEach(upload => {
+				let label = upload.querySelector('h2').textContent;
+
+				let imgs = upload.querySelectorAll('.item-grid.preview img');
+				if (imgs) {
+					let field = wrapper.cloneNode(true);
+					let title = field.querySelector('h3');
+					let p = field.querySelector('p');
+					p.remove();
+
+					title.textContent = label;
+					imgs.forEach(img => {
+						img = img.cloneNode(true);
+						field.append(img);
+					});
+					summary.append(field);
+				}
+			});
+		}
 
 		// Remove template
 		wrapper.remove();
@@ -2152,10 +2026,8 @@
 		if (Array.isArray(value) && value.length === 0) {
 			return true;
 		}
-		if (typeof value === 'object' && Object.keys(value).length === 0) {
-			return true;
-		}
-		return false;
+		return typeof value === 'object' && Object.keys(value).length === 0;
+
 	}
 
 	/**
@@ -2166,8 +2038,6 @@
 		// Try to find label by 'for' attribute (exact match)
 		let label = form.querySelector(`label[for="${fieldName}"]`);
 		let input = form.querySelector(`[name=${fieldName}]`);
-		let fieldWrapper = input?.closest('.field');
-
 		// Try to find the input field - check multiple patterns
 		if (!input) {
 			// Try exact match first
@@ -2193,12 +2063,12 @@
 			// Try closest field wrapper first
 			const field = input.closest('.field, fieldset');
 			if (field) {
-				label = field.querySelector('label, legend');
+				label = field.querySelector('label, legend, h2');
 			}
 		}
 
 		// Get field wrapper - always use base name (no special characters)
-		fieldWrapper = form.querySelector(`.field[data-field="${fieldName}"], fieldset[data-field="${fieldName}"]`);
+		let fieldWrapper = form.querySelector(`.field[data-field="${fieldName}"], fieldset[data-field="${fieldName}"]`);
 
 		// Determine field type
 		let fieldType = 'text';
@@ -2258,7 +2128,7 @@
 				if (Array.isArray(value)) {
 					return this.formatArrayValue(value);
 				}
-				return (value === '1' || value === 1 || value === true) ? 'Yes' : 'No';
+				return (value === '1' || value === 1 || value === true) ? 'Yes' : value;
 
 			case 'select':
 				// Handle both single and multi-select
@@ -2285,8 +2155,7 @@
 			case 'location':
 				return this.formatLocationValue(value);
 
-			case 'file':
-			case 'image':
+			case 'upload':
 				return this.formatFileValue(value);
 
 			case 'number':
@@ -2529,13 +2398,6 @@
 	}
 
 	/**
-	 * Convert newlines to <br> tags (kept for backwards compatibility)
-	 */
-	nl2br(text) {
-		return this.formatPlainText(text);
-	}
-
-	/**
 	 * Event system
 	 */
 	subscribe(callback) {
@@ -2594,6 +2456,11 @@
 	}
 }
 
-document.addEventListener('DOMContentLoaded', () => {
-	window.jvbForm = FormController;
+document.addEventListener('DOMContentLoaded', async function () {
+	window.auth.subscribe(event => {
+		if (event === 'auth-loaded') {
+			window.jvbForm = FormController;
+		}
+	});
+
 });

--
Gitblit v1.10.0