/**
* Form Manager Module
* Handles form operations, validation, and data management
*/
export class FormManager {
constructor(apiClient, uiManager) {
this.apiClient = apiClient;
this.uiManager = uiManager;
this.uploadedPhotos = {};
this.audioFile = null;
this.currentEditId = null;
}
async initialize() {
try {
const formOptions = await this.apiClient.loadFormOptions();
this.renderMultiSelect('utilityOptions', formOptions.utilities);
this.renderMultiSelect('phenologyOptions', formOptions.phenologyStages);
this.renderPhotoCategories(formOptions.photoCategories);
} catch (error) {
console.error('Error initializing form:', error);
throw error;
}
}
renderMultiSelect(containerId, options) {
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
options.forEach(option => {
const label = document.createElement('label');
label.innerHTML = `
${option}
`;
container.appendChild(label);
});
}
renderPhotoCategories(categories) {
const container = document.getElementById('photoCategories');
if (!container) return;
container.innerHTML = '';
categories.forEach(category => {
const categoryDiv = document.createElement('div');
categoryDiv.className = 'photo-category';
categoryDiv.innerHTML = `
+
Click to select ${category} photo
`;
container.appendChild(categoryDiv);
});
}
getSelectedValues(containerId) {
const container = document.getElementById(containerId);
if (!container) return [];
const checkboxes = container.querySelectorAll('input[type="checkbox"]:checked');
return Array.from(checkboxes).map(cb => cb.value);
}
getFormData() {
const utilityValues = this.getSelectedValues('utilityOptions');
const phenologyValues = this.getSelectedValues('phenologyOptions');
return {
latitude: this.getNumericValue('latitude'),
longitude: this.getNumericValue('longitude'),
location_name: this.getStringValue('locationName'),
local_name: this.getStringValue('localName'),
scientific_name: this.getStringValue('scientificName'),
common_name: this.getStringValue('commonName'),
tree_code: this.getStringValue('treeCode'),
height: this.getNumericValue('height'),
width: this.getNumericValue('width'),
utility: utilityValues.length > 0 ? utilityValues : [],
phenology_stages: phenologyValues.length > 0 ? phenologyValues : [],
storytelling_text: this.getStringValue('storytellingText'),
storytelling_audio: this.audioFile,
photographs: Object.keys(this.uploadedPhotos).length > 0 ? this.uploadedPhotos : null,
notes: this.getStringValue('notes')
};
}
getNumericValue(fieldId) {
const element = document.getElementById(fieldId);
if (!element || !element.value) return null;
const value = parseFloat(element.value);
return isNaN(value) ? null : value;
}
getStringValue(fieldId) {
const element = document.getElementById(fieldId);
return element && element.value ? element.value : null;
}
populateForm(treeData) {
this.setFieldValue('latitude', treeData.latitude);
this.setFieldValue('longitude', treeData.longitude);
this.setFieldValue('locationName', treeData.location_name || '');
this.setFieldValue('localName', treeData.local_name || '');
this.setFieldValue('scientificName', treeData.scientific_name || '');
this.setFieldValue('commonName', treeData.common_name || '');
this.setFieldValue('treeCode', treeData.tree_code || '');
this.setFieldValue('height', treeData.height || '');
this.setFieldValue('width', treeData.width || '');
this.setFieldValue('storytellingText', treeData.storytelling_text || '');
this.setFieldValue('notes', treeData.notes || '');
// Handle utility checkboxes
if (treeData.utility && Array.isArray(treeData.utility)) {
this.setCheckboxValues('utilityOptions', treeData.utility);
}
// Handle phenology checkboxes
if (treeData.phenology_stages && Array.isArray(treeData.phenology_stages)) {
this.setCheckboxValues('phenologyOptions', treeData.phenology_stages);
}
}
setFieldValue(fieldId, value) {
const element = document.getElementById(fieldId);
if (element) {
element.value = value;
}
}
setCheckboxValues(containerId, values) {
const container = document.getElementById(containerId);
if (!container) return;
container.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.checked = values.includes(checkbox.value);
});
}
resetForm(silent = false) {
const form = document.getElementById('treeForm');
if (form) {
form.reset();
}
this.uploadedPhotos = {};
this.audioFile = null;
this.currentEditId = null;
// Clear uploaded file indicators
document.querySelectorAll('.uploaded-file').forEach(el => {
el.style.display = 'none';
el.innerHTML = '';
});
// Reset audio controls
this.resetAudioControls();
if (!silent) {
this.uiManager.showMessage('Form has been reset.', 'success');
}
}
resetAudioControls() {
const audioElement = document.getElementById('audioPlayback');
if (audioElement) {
audioElement.classList.add('hidden');
audioElement.src = '';
}
const recordingStatus = document.getElementById('recordingStatus');
if (recordingStatus) {
recordingStatus.textContent = 'Click to start recording';
}
const audioUploadResult = document.getElementById('audioUploadResult');
if (audioUploadResult) {
audioUploadResult.innerHTML = '';
}
}
async handleFileUpload(file, type, category = null) {
try {
const result = await this.apiClient.uploadFile(file, type, category);
if (type === 'image' && category) {
this.uploadedPhotos[category] = result.filename;
this.showUploadSuccess(category, file.name, 'photo');
} else if (type === 'audio') {
this.audioFile = result.filename;
this.showUploadSuccess(null, file.name, 'audio');
}
return result;
} catch (error) {
console.error(`Error uploading ${type}:`, error);
this.uiManager.showMessage(`Error uploading ${type}: ${error.message}`, 'error');
throw error;
}
}
showUploadSuccess(category, filename, type) {
if (type === 'photo' && category) {
const resultDiv = document.getElementById(`photo-${category}`);
if (resultDiv) {
resultDiv.style.display = 'block';
resultDiv.innerHTML = `${filename} uploaded successfully`;
}
} else if (type === 'audio') {
const resultDiv = document.getElementById('audioUploadResult');
if (resultDiv) {
resultDiv.innerHTML = `${filename} uploaded successfully
`;
}
}
}
setEditMode(treeId) {
this.currentEditId = treeId;
// Update submit button
const submitBtn = document.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.textContent = 'Update Tree Record';
}
// Add cancel edit button if it doesn't exist
this.addCancelButton();
}
addCancelButton() {
if (document.getElementById('cancelEdit')) return;
const cancelBtn = document.createElement('button');
cancelBtn.type = 'button';
cancelBtn.id = 'cancelEdit';
cancelBtn.className = 'tt-btn tt-btn-outline';
cancelBtn.textContent = 'Cancel Edit';
const formActions = document.querySelector('.form-actions');
const submitBtn = document.querySelector('button[type="submit"]');
if (formActions && submitBtn) {
formActions.insertBefore(cancelBtn, submitBtn);
}
}
exitEditMode() {
this.currentEditId = null;
// Restore original submit button text
const submitBtn = document.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.textContent = 'Save Tree Record';
}
// Remove cancel button
const cancelBtn = document.getElementById('cancelEdit');
if (cancelBtn) {
cancelBtn.remove();
}
}
isInEditMode() {
return this.currentEditId !== null;
}
getCurrentEditId() {
return this.currentEditId;
}
validateForm() {
const latitude = this.getNumericValue('latitude');
const longitude = this.getNumericValue('longitude');
if (latitude === null || longitude === null) {
throw new Error('Latitude and longitude are required');
}
if (latitude < -90 || latitude > 90) {
throw new Error('Latitude must be between -90 and 90 degrees');
}
if (longitude < -180 || longitude > 180) {
throw new Error('Longitude must be between -180 and 180 degrees');
}
return true;
}
}