📋 Workbook Response System

Auto-Save Specifications & Implementation Guide

🎯 Overview

This document outlines the specifications for creating HTML workbooks with automatic response saving functionality via Flutter integration. All interactive workbooks that collect user data MUST follow these specifications to ensure consistent behavior and data integrity. The JavaScript code snippets provided in this documentation should be directly embedded within the HTML workbook content. Flutter only handles saving and loading JSON responses - all form interactions and auto-save logic are managed by the HTML workbook itself.

🔄 Response Lifecycle

1. Workbook Load Sequence

HTML Loads Flutter Channel Ready Check Response Exists Load if Found Populate Form Enable Auto-Save

2. First Time User Flow

  1. HTML displays normal entry form
  2. User fills out data
  3. As user fills out sections, responses are auto-saved (2-second debounce)
  4. Data sent to Flutter via channel
  5. Flutter saves to: AppData/JSONs/User_Workbook_Responses/
  6. Status banner shows real-time save status

3. Returning User Flow

  1. HTML checks for existing responses
  2. If found: Automatically load data into form
  3. Saved responses are automatically loaded
  4. Edit mode: Detect when data is changed
  5. Auto-save: Update same response file (don't create new)

⚡ Auto-Save Implementation

🔑 Key Concept:

The collectWorkbookData() function gathers ALL form elements regardless of type (text inputs, checkboxes, radio buttons, textareas, etc.), and triggerAutoSave() is attached to ALL interactive elements so any change triggers the save after 2 seconds.

JavaScript Auto-Save Mechanism

// Debounced auto-save implementation
let autoSaveTimer;
let saveInProgress = false;
const AUTO_SAVE_DELAY = 2000; // 2 seconds after user stops typing

function triggerAutoSave() {
    clearTimeout(autoSaveTimer);
    showStatus('typing');
    
    autoSaveTimer = setTimeout(() => {
        if (!saveInProgress) {
            saveInProgress = true;
            showStatus('saving');
            saveWorkbook();
        }
    }, AUTO_SAVE_DELAY);
}

// Attach to all form inputs
document.querySelectorAll('input, textarea, select, [contenteditable]').forEach(element => {
    element.addEventListener('input', triggerAutoSave);
    element.addEventListener('change', triggerAutoSave);
});

Status Indicator States

✅ Responses are auto-saved
✏️ Editing...
⏳ Saving...
✅ Saved
❌ Save failed - will retry

Data Collection Function Template

function collectWorkbookData() {
    const workbookData = {
        workbook_id: "your_workbook_id",
        created_at: savedData?.created_at || new Date().toISOString(),
        updated_at: new Date().toISOString(),
        ai_prompt: "This contains the user's journal responses. Analyze the patterns and provide personalized insights based on the recorded data.",
        data: {
            // Structure this based on your form fields
            // Examples of different input types:
            
            // Text input: document.getElementById('field-id').value
            // Textarea: document.getElementById('textarea-id').value
            // Radio button: document.querySelector('input[name="group"]:checked')?.value
            // Checkboxes: Array.from(document.querySelectorAll('input[name="group"]:checked')).map(cb => cb.value)
            // Select: document.getElementById('select-id').value
            // Number: parseInt(document.getElementById('number-id').value)
            // Date: document.getElementById('date-id').value
            // Range slider: document.getElementById('slider-id').value
            // Calendar: Array.from(document.querySelectorAll('.calendar-day.checked')).map(day => day.dataset.date)
            // Contenteditable: document.getElementById('editable-id').innerText
        }
    };
    
    return workbookData;
}

📝 Note: Include an ai_prompt field that provides context for AI analysis of the user's journal data. Customize this prompt based on the specific workbook type and analysis needs.

Auto-Save Event Listeners

function setupEventListeners() {
    // Attach auto-save to all standard form elements
    document.querySelectorAll('input, textarea, select').forEach(element => {
        element.addEventListener('input', triggerAutoSave);
        element.addEventListener('change', triggerAutoSave);
    });
    
    // Special handling for radio buttons and checkboxes
    document.querySelectorAll('input[type="radio"], input[type="checkbox"]').forEach(element => {
        element.addEventListener('change', triggerAutoSave);
    });
    
    // Content editable elements
    document.querySelectorAll('[contenteditable="true"]').forEach(element => {
        element.addEventListener('input', triggerAutoSave);
    });
    
    // Custom interactive elements (modify selectors as needed)
    document.querySelectorAll('.interactive-element').forEach(element => {
        element.addEventListener('click', function() {
            // Custom interaction logic
            triggerAutoSave();
        });
    });
}

Status Banner HTML

<!-- Status indicator for auto-save -->
<div id="auto-save-status" class="save-status">
    ✅ Responses are auto-saved
</div>

<style>
/* Style the save status indicator to match your design */
.save-status {
    /* Position and visibility styles */
}

/* Different states - customize colors and styling as needed */
.status-idle { /* Default state styling */ }
.status-typing { /* User is typing styling */ }
.status-saving { /* Saving in progress styling */ }
.status-saved { /* Successfully saved styling */ }
.status-error { /* Error state styling */ }
</style>

🔌 Flutter Channel Integration

Required JavaScript Functions

// Handle response from Flutter after save/load attempts
window.handleFlutterMessage = function(message) {
    const data = JSON.parse(message);
    
    switch(data.type) {
        case 'saveResult':
            handleSaveResult(data.data);
            break;
            
        case 'loadResult':
            handleLoadResult(data.data);
            break;
            
        case 'responseExistsResult':
            handleResponseExists(data.data);
            break;
    }
};

// Flutter channel ready callback
window.onFlutterChannelReady = function() {
    console.log('Flutter channel is ready');
    checkForExistingResponses();
};

Note: When loading data, access nested fields with data.data.data (e.g., savedData = data.data.data;)

Flutter Response Handler Functions

// These functions handle responses from Flutter - required for every journal

function handleSaveResult(data) {
    saveInProgress = false;
    if (data.success) {
        showStatus('saved');
        setTimeout(() => showStatus('idle'), 2000);
    } else {
        showStatus('error');
        setTimeout(() => showStatus('idle'), 3000);
    }
}

function handleLoadResult(data) {
    if (data.success && data.data) {
        // Access nested data structure
        savedData = data.data.data;
        populateFormWithSavedData(savedData);
        // Update any progress displays if needed
        // updateProgressStats();
        showStatus('idle');
        console.log('Existing responses loaded successfully');
    } else {
        console.log('No existing responses to load');
        showStatus('idle');
    }
}

function handleResponseExists(data) {
    if (data.exists) {
        loadExistingResponses();
    } else {
        console.log('No existing responses found, starting fresh');
        showStatus('idle');
    }
}

Message Formats

Save Message

window.FlutterChannel.postMessage(JSON.stringify({
    action: 'saveWorkbookResponse',
    responseData: workbookData,
    fileName: FILE_NAME // e.g., "C14ENB1_response.json"
}));

Load Message

window.FlutterChannel.postMessage(JSON.stringify({
    action: 'loadWorkbookResponse',
    fileName: FILE_NAME // e.g., "C14ENB1_response.json"
}));

Check Existence Message

window.FlutterChannel.postMessage(JSON.stringify({
    action: 'checkResponseExists',
    fileName: FILE_NAME // e.g., "C14ENB1_response.json"
}));

📊 Data Structure

File Naming Convention

Format: {course_code}{workbook_number}_response.json
📝 Note: All journal workbooks use workbook number 1
Examples:
  • C14ENB1_response.json (Confidence course, English, workbook 1)
  • H23ENB1_response.json (Smoking cessation course, English, workbook 1)
  • S66ENB1_response.json (Weight loss course, English, workbook 1)
  • P11ENM1_response.json (Productivity course, English, Male voice, workbook 1)
Location: Internal Storage/AppData/JSONs/User_Workbook_Responses/

📋 Implementation: Each HTML workbook MUST hardcode the appropriate filename in the FILE_NAME constant:
const FILE_NAME = "C14ENB1_response.json";

Example: Anxiety Daily Journal Implementation

Data Collection Function

function collectWorkbookData() {
    const workbookData = {
        workbook_id: "anxiety_daily_journal",
        created_at: savedData?.created_at || new Date().toISOString(),
        updated_at: new Date().toISOString(),
        ai_prompt: "This contains the user's anxiety journal responses. Analyze emotional patterns, identify triggers and effective coping strategies, and provide personalized recommendations for managing anxiety.",
        data: {
            // Text inputs
            other_triggers: document.getElementById('other-triggers').value,
            thoughts_feelings: document.getElementById('thoughts-feelings').value,
            positive_moments: document.getElementById('positive-moments').value,
            tomorrow_goal: document.getElementById('tomorrow-goal').value,
            
            // Radio buttons (single selection)
            mood_rating: document.querySelector('input[name="mood_rating"]:checked')?.value,
            
            // Range slider
            anxiety_level: parseInt(document.getElementById('anxiety-slider').value),
            
            // Checkboxes (multiple selections)
            triggers: Array.from(document.querySelectorAll('input[name="triggers"]:checked'))
                          .map(cb => cb.value),
            physical_symptoms: Array.from(document.querySelectorAll('input[name="physical_symptoms"]:checked'))
                          .map(cb => cb.value),
            mental_symptoms: Array.from(document.querySelectorAll('input[name="mental_symptoms"]:checked'))
                          .map(cb => cb.value),
            coping_strategies: Array.from(document.querySelectorAll('input[name="coping_strategies"]:checked'))
                          .map(cb => cb.value),
            
            // Select dropdown
            effectiveness_rating: parseInt(document.getElementById('effectiveness-rating').value) || null,
            
            // Date input
            entry_date: document.getElementById('entry-date').value,
            
            // Gratitude array from multiple text inputs
            gratitude: [
                document.getElementById('gratitude1').value,
                document.getElementById('gratitude2').value,
                document.getElementById('gratitude3').value
            ].filter(g => g.trim() !== ''),
            
            // Calendar tracking
            completed_days: Array.from(document.querySelectorAll('.calendar-day.checked'))
                                .map(day => day.dataset.date)
        }
    };
    
    return workbookData;
}

Event Listeners Setup

function setupEventListeners() {
    // Standard form elements
    document.querySelectorAll('input, textarea, select').forEach(element => {
        element.addEventListener('input', triggerAutoSave);
        element.addEventListener('change', triggerAutoSave);
    });
    
    // Radio buttons and checkboxes
    document.querySelectorAll('input[type="radio"], input[type="checkbox"]').forEach(element => {
        element.addEventListener('change', triggerAutoSave);
    });
    
    // Custom calendar days
    document.querySelectorAll('.calendar-day').forEach(day => {
        day.addEventListener('click', function() {
            this.classList.toggle('checked');
            triggerAutoSave();
        });
    });
    
    // Anxiety slider display update
    document.getElementById('anxiety-slider').addEventListener('input', updateAnxietySliderDisplay);
}

JSON Response Structure

{
    "workbook_id": "anxiety_daily_journal",
    "created_at": "2024-11-14T09:30:00.000Z",
    "updated_at": "2024-11-14T15:45:23.000Z",
    "ai_prompt": "This contains the user's anxiety journal responses. Analyze emotional patterns, identify triggers and effective coping strategies, and provide personalized recommendations for managing anxiety based on the recorded data.",
    "data": {
        "daily_entries": [
            {
                "date": "2024-11-14",
                "mood_rating": "7",
                "anxiety_level": 4,
                "triggers": ["work_stress", "social_situations"],
                "other_triggers": "Traffic jam on the way to work",
                "physical_symptoms": ["racing_heart", "sweating", "muscle_tension"],
                "mental_symptoms": ["worry", "overthinking", "concentration_issues"],
                "coping_strategies": ["deep_breathing", "grounding_5_4_3_2_1", "meditation"],
                "effectiveness_rating": 8,
                "thoughts_feelings": "Felt anxious about the presentation but breathing exercises helped calm me down. The grounding technique was particularly effective.",
                "positive_moments": "Received compliment from my boss on my work. Had a nice lunch with a colleague. Enjoyed the sunset on the way home.",
                "gratitude": ["Supportive colleagues", "Beautiful weather today", "Good coffee this morning"],
                "tomorrow_goal": "Practice presentation once more and get to bed earlier"
            }
        ],
        "completed_days": ["2024-11-01", "2024-11-02", "2024-11-03", "2024-11-14"],
        "progress_tracking": {
            "total_entries": 4,
            "streak_days": 1,
            "average_mood": "6.8",
            "average_anxiety": "4.2"
        }
    }
}