Simplified data structure around template files
This commit is contained in:
@ -199,39 +199,279 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Load module data
|
||||
let allModules = {};
|
||||
try {
|
||||
// Check if moduleData is defined (from modules.js)
|
||||
if (typeof moduleData !== 'undefined') {
|
||||
allModules = moduleData;
|
||||
console.log('Module data loaded from modules.js');
|
||||
// Load template data
|
||||
let templates = [];
|
||||
|
||||
// Function to fetch templates - simplified approach
|
||||
function fetchTemplates() {
|
||||
console.log('Fetching templates...');
|
||||
|
||||
// Load all templates at startup
|
||||
try {
|
||||
// First try loading from the standard path
|
||||
fetch('/js/templates/index.js')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch templates: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(moduleText => {
|
||||
// Add a script element to the document
|
||||
const script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.textContent = moduleText;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Set a timeout to check for templates
|
||||
setTimeout(() => {
|
||||
// See if templates were loaded
|
||||
if (window.allTemplates && Array.isArray(window.allTemplates)) {
|
||||
console.log('Templates loaded successfully:', window.allTemplates.length, 'templates found');
|
||||
templates = window.allTemplates;
|
||||
|
||||
// Populate the template selector
|
||||
populateTemplateSelector(templates);
|
||||
|
||||
// Add template selection event handler
|
||||
if (protocolTemplateSelect) {
|
||||
protocolTemplateSelect.addEventListener('change', handleTemplateSelection);
|
||||
console.log('Template selector event handler attached');
|
||||
} else {
|
||||
console.error('Template select element not found');
|
||||
}
|
||||
} else {
|
||||
console.error('Templates not available after loading script');
|
||||
}
|
||||
}, 500);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching templates:', error);
|
||||
|
||||
// Try alternate loading method with ES modules import
|
||||
console.log('Trying alternate loading method with ES modules...');
|
||||
|
||||
import('/js/templates/index.js')
|
||||
.then(module => {
|
||||
templates = module.default;
|
||||
console.log('Templates loaded successfully using ES modules:', templates.length, 'templates found');
|
||||
|
||||
// Populate the template selector
|
||||
populateTemplateSelector(templates);
|
||||
|
||||
// Add template selection event handler
|
||||
if (protocolTemplateSelect) {
|
||||
protocolTemplateSelect.addEventListener('change', handleTemplateSelection);
|
||||
console.log('Template selector event handler attached');
|
||||
} else {
|
||||
console.error('Template select element not found');
|
||||
}
|
||||
})
|
||||
.catch(importError => {
|
||||
console.error('ES module import also failed:', importError);
|
||||
console.error('Could not load templates through either method');
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error in template loading:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading module data:', e);
|
||||
allModules = {};
|
||||
}
|
||||
|
||||
// Load and process template data
|
||||
let templates = [];
|
||||
try {
|
||||
// Check if raw templates are defined (from templates.js)
|
||||
if (typeof rawProtocolTemplates !== 'undefined' && typeof templateMapper !== 'undefined') {
|
||||
console.log('Raw protocol templates loaded from templates.js');
|
||||
// Function to populate the template selector dropdown
|
||||
function populateTemplateSelector(templatesList) {
|
||||
if (!protocolTemplateSelect || !templatesList || templatesList.length === 0) {
|
||||
console.error('Cannot populate template selector - missing element or templates');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Populating template selector with', templatesList.length, 'templates');
|
||||
|
||||
// Clear all existing options
|
||||
while (protocolTemplateSelect.options.length > 0) {
|
||||
protocolTemplateSelect.remove(0);
|
||||
}
|
||||
|
||||
// Add the default "Create Your Own" option
|
||||
const defaultOption = document.createElement('option');
|
||||
defaultOption.value = "";
|
||||
defaultOption.textContent = "-- Create Your Own Protocol --";
|
||||
protocolTemplateSelect.appendChild(defaultOption);
|
||||
|
||||
// Verify templates have required properties
|
||||
let validTemplateCount = 0;
|
||||
templatesList.forEach(template => {
|
||||
if (!template.id || !template.title || !template.description) {
|
||||
console.warn('Template missing required fields:', template);
|
||||
} else {
|
||||
validTemplateCount++;
|
||||
}
|
||||
});
|
||||
console.log(`Found ${validTemplateCount} valid templates out of ${templatesList.length} total`);
|
||||
|
||||
// Add template options
|
||||
templatesList.forEach(template => {
|
||||
const option = document.createElement('option');
|
||||
option.value = template.id;
|
||||
option.textContent = template.title;
|
||||
protocolTemplateSelect.appendChild(option);
|
||||
console.log('Added template option:', template.title, 'with ID:', template.id);
|
||||
|
||||
// Process each template to use module references
|
||||
rawProtocolTemplates.forEach(rawTemplate => {
|
||||
const processedTemplate = templateMapper.convertTemplateToModules(rawTemplate, allModules);
|
||||
templates.push(processedTemplate);
|
||||
// Debugging template structure
|
||||
console.log(' > Description:', template.description ? template.description.substring(0, 50) + '...' : 'MISSING');
|
||||
console.log(' > Has data:', template.data ? 'Yes' : 'No');
|
||||
console.log(' > Has stages:', template.data?.stages ? 'Yes - ' + Object.keys(template.data.stages).length + ' stages' : 'No');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle template selection
|
||||
function handleTemplateSelection() {
|
||||
const selectedTemplateId = this.value;
|
||||
console.log('Template selection changed to:', selectedTemplateId);
|
||||
|
||||
if (selectedTemplateId) {
|
||||
// Find the selected template from our loaded templates
|
||||
const selectedTemplate = templates.find(t => t.id === selectedTemplateId);
|
||||
|
||||
if (selectedTemplate) {
|
||||
console.log('Found template:', selectedTemplate.title);
|
||||
applyTemplate(selectedTemplate);
|
||||
} else {
|
||||
console.error('Template not found:', selectedTemplateId);
|
||||
}
|
||||
} else {
|
||||
// Clear all fields if "Create Your Own" is selected
|
||||
document.querySelectorAll('textarea').forEach(textarea => {
|
||||
textarea.value = '';
|
||||
});
|
||||
|
||||
console.log('Processed templates to use module references:', templates);
|
||||
// Reset protocol data
|
||||
protocol = {
|
||||
metadata: {
|
||||
communityName: "",
|
||||
summary: ""
|
||||
},
|
||||
stages: {}
|
||||
};
|
||||
|
||||
// Collapse all sections
|
||||
stageContents.forEach(content => {
|
||||
content.style.display = 'none';
|
||||
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = '+';
|
||||
}
|
||||
});
|
||||
|
||||
// Update preview mode if active
|
||||
if (previewModeActive) {
|
||||
markComponentsWithContent();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error processing protocol templates:', e);
|
||||
templates = [];
|
||||
}
|
||||
|
||||
// Function to apply a template to the form
|
||||
function applyTemplate(selectedTemplate) {
|
||||
if (!selectedTemplate) {
|
||||
console.error('Cannot apply template: template is null or undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Applying template:', selectedTemplate.title);
|
||||
console.log('Template data structure:', selectedTemplate);
|
||||
|
||||
// Reset protocol data while preserving metadata
|
||||
protocol = {
|
||||
metadata: {
|
||||
communityName: communityNameInput.value || "",
|
||||
summary: protocolSummaryTextarea.value || ""
|
||||
},
|
||||
stages: {},
|
||||
templateId: selectedTemplate.id,
|
||||
templateTitle: selectedTemplate.title,
|
||||
templateDescription: selectedTemplate.description
|
||||
};
|
||||
|
||||
// Always use the template description for summary when applying a template
|
||||
protocolSummaryTextarea.value = selectedTemplate.description;
|
||||
protocol.metadata.summary = selectedTemplate.description;
|
||||
console.log('Set summary to template description:', selectedTemplate.description);
|
||||
|
||||
// Check if template has data.stages
|
||||
if (!selectedTemplate.data || !selectedTemplate.data.stages) {
|
||||
console.error('Template has invalid structure - missing data.stages:', selectedTemplate);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Template has these stages:', Object.keys(selectedTemplate.data.stages));
|
||||
|
||||
// Count fields that will be populated
|
||||
let fieldsPopulated = 0;
|
||||
|
||||
// Apply the template content directly to the form
|
||||
for (const stageId in selectedTemplate.data.stages) {
|
||||
console.log(`Processing stage ${stageId}...`);
|
||||
|
||||
if (!protocol.stages[stageId]) {
|
||||
protocol.stages[stageId] = {};
|
||||
}
|
||||
|
||||
for (const componentId in selectedTemplate.data.stages[stageId]) {
|
||||
console.log(` Processing component ${componentId}...`);
|
||||
|
||||
if (!protocol.stages[stageId][componentId]) {
|
||||
protocol.stages[stageId][componentId] = {};
|
||||
}
|
||||
|
||||
for (const fieldId in selectedTemplate.data.stages[stageId][componentId]) {
|
||||
const content = selectedTemplate.data.stages[stageId][componentId][fieldId];
|
||||
const textarea = document.getElementById(fieldId);
|
||||
|
||||
console.log(` Processing field ${fieldId}:`);
|
||||
console.log(` Content exists: ${Boolean(content)}`);
|
||||
console.log(` Field element exists: ${Boolean(textarea)}`);
|
||||
|
||||
if (textarea && content) {
|
||||
// Apply the content to the textarea
|
||||
textarea.value = content;
|
||||
|
||||
// Store in protocol data
|
||||
protocol.stages[stageId][componentId][fieldId] = content;
|
||||
fieldsPopulated++;
|
||||
console.log(` ✓ Populated field ${fieldId} with content (${content.length} chars)`);
|
||||
} else {
|
||||
if (!textarea) {
|
||||
console.warn(` ✗ Textarea element not found for field ID: ${fieldId}`);
|
||||
}
|
||||
if (!content) {
|
||||
console.warn(` ✗ No content for field ID: ${fieldId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Template application complete: ${fieldsPopulated} fields populated`);
|
||||
|
||||
// Expand all sections to show the populated content
|
||||
stageContents.forEach(content => {
|
||||
content.style.display = 'block';
|
||||
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = '-';
|
||||
}
|
||||
});
|
||||
|
||||
// Update preview mode if active
|
||||
if (previewModeActive) {
|
||||
markComponentsWithContent();
|
||||
}
|
||||
}
|
||||
|
||||
// No helper functions needed anymore as we've simplified the approach
|
||||
|
||||
// Start loading templates
|
||||
fetchTemplates();
|
||||
|
||||
// Protocol data structure
|
||||
let protocol = {
|
||||
metadata: {
|
||||
@ -262,9 +502,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const importJsonInput = document.getElementById('import-json');
|
||||
const importBtn = document.getElementById('import-btn');
|
||||
|
||||
// Populate protocol template select
|
||||
if (protocolTemplateSelect && templates.length > 0) {
|
||||
templates.forEach(template => {
|
||||
// Function to initialize the template selector
|
||||
function initializeTemplateSelector(templatesList) {
|
||||
if (!protocolTemplateSelect || !templatesList || templatesList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing options
|
||||
while (protocolTemplateSelect.options.length > 1) {
|
||||
protocolTemplateSelect.remove(1);
|
||||
}
|
||||
|
||||
// Add template options
|
||||
templatesList.forEach(template => {
|
||||
const option = document.createElement('option');
|
||||
option.value = template.id;
|
||||
option.textContent = template.title;
|
||||
@ -272,239 +522,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
// Add template selection event handler
|
||||
protocolTemplateSelect.addEventListener('change', function() {
|
||||
const selectedTemplateId = this.value;
|
||||
|
||||
if (selectedTemplateId) {
|
||||
// Find the selected template
|
||||
const selectedTemplate = templates.find(t => t.id === selectedTemplateId);
|
||||
|
||||
if (selectedTemplate) {
|
||||
console.log('Applying template:', selectedTemplate);
|
||||
|
||||
console.log('Selected template:', selectedTemplate.title);
|
||||
|
||||
// Reset protocol data while preserving metadata
|
||||
protocol = {
|
||||
metadata: {
|
||||
communityName: communityNameInput.value || "",
|
||||
summary: protocolSummaryTextarea.value || ""
|
||||
},
|
||||
stages: {},
|
||||
templateId: selectedTemplate.id,
|
||||
templateTitle: selectedTemplate.title,
|
||||
templateDescription: selectedTemplate.description
|
||||
};
|
||||
|
||||
// If summary is empty, use template description
|
||||
if (!protocol.metadata.summary) {
|
||||
protocolSummaryTextarea.value = selectedTemplate.description;
|
||||
protocol.metadata.summary = selectedTemplate.description;
|
||||
}
|
||||
|
||||
// Apply the template module references to the form
|
||||
for (const stageId in selectedTemplate.moduleRefs) {
|
||||
if (!protocol.stages[stageId]) {
|
||||
protocol.stages[stageId] = {};
|
||||
}
|
||||
|
||||
for (const componentId in selectedTemplate.moduleRefs[stageId]) {
|
||||
if (!protocol.stages[stageId][componentId]) {
|
||||
protocol.stages[stageId][componentId] = {};
|
||||
}
|
||||
|
||||
for (const fieldId in selectedTemplate.moduleRefs[stageId][componentId]) {
|
||||
const moduleId = selectedTemplate.moduleRefs[stageId][componentId][fieldId];
|
||||
const textarea = document.getElementById(fieldId);
|
||||
|
||||
if (textarea) {
|
||||
// Find the module with this ID
|
||||
let moduleContent = '';
|
||||
|
||||
for (const category in allModules) {
|
||||
const foundModule = allModules[category].find(m => m.id === moduleId);
|
||||
if (foundModule) {
|
||||
moduleContent = foundModule.content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the module content to the textarea
|
||||
textarea.value = moduleContent;
|
||||
|
||||
// Store in protocol data
|
||||
protocol.stages[stageId][componentId][fieldId] = moduleContent;
|
||||
|
||||
// If there's a template selector for this field, update it
|
||||
const moduleSelector = document.querySelector(`select.module-select[data-field-id="${fieldId}"][data-component-id="${componentId}"]`);
|
||||
if (moduleSelector) {
|
||||
moduleSelector.value = moduleId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expand all sections to show the populated content
|
||||
stageContents.forEach(content => {
|
||||
content.style.display = 'block';
|
||||
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = '-';
|
||||
}
|
||||
});
|
||||
|
||||
// Update preview mode if active
|
||||
if (previewModeActive) {
|
||||
markComponentsWithContent();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Clear all fields if "Create Your Own" is selected
|
||||
|
||||
document.querySelectorAll('textarea').forEach(textarea => {
|
||||
textarea.value = '';
|
||||
});
|
||||
|
||||
// Reset protocol data
|
||||
protocol = { stages: {} };
|
||||
|
||||
// Collapse all sections
|
||||
stageContents.forEach(content => {
|
||||
content.style.display = 'none';
|
||||
const toggleBtn = content.parentElement.querySelector('.toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = '+';
|
||||
}
|
||||
});
|
||||
|
||||
// Update preview mode if active
|
||||
if (previewModeActive) {
|
||||
markComponentsWithContent();
|
||||
}
|
||||
}
|
||||
});
|
||||
protocolTemplateSelect.addEventListener('change', handleTemplateSelection);
|
||||
}
|
||||
|
||||
// Initialize all module selects
|
||||
populateModuleSelects();
|
||||
|
||||
// Module selection handlers
|
||||
moduleSelects.forEach(select => {
|
||||
select.addEventListener('change', function() {
|
||||
const fieldId = this.getAttribute('data-field-id');
|
||||
const componentId = this.getAttribute('data-component-id');
|
||||
const targetTextarea = document.getElementById(fieldId);
|
||||
|
||||
if (targetTextarea && this.value) {
|
||||
// Find the selected module
|
||||
for (const category in allModules) {
|
||||
const selectedModule = allModules[category].find(m => m.id === this.value);
|
||||
if (selectedModule) {
|
||||
targetTextarea.value = selectedModule.content;
|
||||
|
||||
// Update protocol data
|
||||
updateProtocolData();
|
||||
|
||||
// Update preview mode if active
|
||||
if (previewModeActive) {
|
||||
markComponentsWithContent();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Hide module selectors since we're using templates directly
|
||||
document.querySelectorAll('.module-selector').forEach(selector => {
|
||||
selector.style.display = 'none';
|
||||
});
|
||||
|
||||
// Function to populate module select dropdowns
|
||||
function populateModuleSelects() {
|
||||
console.log('Populating module selects...');
|
||||
console.log('Available module categories:', Object.keys(allModules));
|
||||
|
||||
// Debugging: Log all modules to check componentId and fieldId
|
||||
console.log('All module mapping:');
|
||||
for (const category in allModules) {
|
||||
console.log(`Category: ${category}`);
|
||||
allModules[category].forEach(module => {
|
||||
console.log(` Module: ${module.id}, Component: ${module.componentId}, Field: ${module.fieldId}`);
|
||||
});
|
||||
}
|
||||
|
||||
moduleSelects.forEach(select => {
|
||||
const fieldId = select.getAttribute('data-field-id');
|
||||
const componentId = select.getAttribute('data-component-id');
|
||||
|
||||
console.log(`Processing module select for fieldId: ${fieldId}, componentId: ${componentId}`);
|
||||
|
||||
// Clear existing options except the first one
|
||||
while (select.options.length > 1) {
|
||||
select.remove(1);
|
||||
}
|
||||
|
||||
// Find modules that match this field and component
|
||||
let hasOptions = false;
|
||||
|
||||
// Always show select first - we'll hide it later if no options found
|
||||
select.closest('.module-selector').style.display = 'flex';
|
||||
|
||||
// Check for case matching issues and missing references
|
||||
for (const category in allModules) {
|
||||
let exactMatches = allModules[category].filter(m =>
|
||||
m.fieldId === fieldId && m.componentId === componentId
|
||||
);
|
||||
|
||||
let caseInsensitiveMatches = allModules[category].filter(m =>
|
||||
m.fieldId.toLowerCase() === fieldId.toLowerCase() &&
|
||||
m.componentId.toLowerCase() === componentId.toLowerCase() &&
|
||||
!exactMatches.includes(m)
|
||||
);
|
||||
|
||||
if (caseInsensitiveMatches.length > 0) {
|
||||
console.warn(`Found ${caseInsensitiveMatches.length} case-insensitive matches for ${componentId}/${fieldId}. Consider fixing these module references.`);
|
||||
|
||||
// Add case-insensitive matches to the collection
|
||||
caseInsensitiveMatches.forEach(module => {
|
||||
// Create a copy with corrected references
|
||||
const correctedModule = {
|
||||
...module,
|
||||
componentId: componentId,
|
||||
fieldId: fieldId
|
||||
};
|
||||
|
||||
// Add to the exact matches
|
||||
exactMatches.push(correctedModule);
|
||||
});
|
||||
}
|
||||
|
||||
if (exactMatches.length > 0) {
|
||||
console.log(`Found ${exactMatches.length} modules in category ${category} for ${componentId}/${fieldId}`);
|
||||
hasOptions = true;
|
||||
|
||||
// Don't use option groups - add options directly to the select
|
||||
// This avoids showing category labels which can be confusing
|
||||
exactMatches.forEach(module => {
|
||||
const option = document.createElement('option');
|
||||
option.value = module.id;
|
||||
|
||||
// Use the module title directly from the definition
|
||||
// This relies on proper module titles being defined in modules.js
|
||||
option.textContent = module.title;
|
||||
|
||||
// Add directly to select instead of to a group
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If no modules found, hide the selector
|
||||
if (!hasOptions) {
|
||||
console.log(`No modules found for ${componentId}/${fieldId}, hiding selector`);
|
||||
select.closest('.module-selector').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update protocol data from form inputs
|
||||
function updateProtocolData() {
|
||||
// Update metadata
|
||||
@ -862,7 +887,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
// Import from JSON
|
||||
importBtn.addEventListener('click', function() {
|
||||
importBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault(); // Prevent the default anchor behavior
|
||||
importJsonInput.click();
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user