DocHub
JavaScript-to-Flutter communication, message formats, and response handler functions

Flutter Channel Integration

Workbooks communicate with Flutter through the FlutterChannel JavaScript bridge. All messages are JSON-encoded strings.

Required JavaScript Callbacks

Every workbook must implement these two global functions:

handleFlutterMessage

Receives responses from Flutter after save/load/check operations:

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;
    }
};

onFlutterChannelReady

Called when the Flutter WebView channel is initialized:

window.onFlutterChannelReady = function() {
    checkForExistingResponses();
};

Message Formats (JS to Flutter)

Save Workbook Response

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

Load Workbook Response

window.FlutterChannel.postMessage(JSON.stringify({
    action: 'loadWorkbookResponse',
    fileName: FILE_NAME
}));

Check Response Exists

window.FlutterChannel.postMessage(JSON.stringify({
    action: 'checkResponseExists',
    fileName: FILE_NAME
}));

Response Handler Functions

handleSaveResult

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

handleLoadResult

function handleLoadResult(data) {
    if (data.success && data.data) {
        savedData = data.data.data;    // Note: nested data.data.data
        populateFormWithSavedData(savedData);
        showStatus('idle');
    } else {
        showStatus('idle');
    }
}

Important: When loading data, access nested fields with data.data.data — the response wraps the actual workbook data inside a data property.

handleResponseExists

function handleResponseExists(data) {
    if (data.exists) {
        loadExistingResponses();
    } else {
        showStatus('idle');
    }
}

Event Listener Setup

Every workbook must attach auto-save to all interactive elements:

function setupEventListeners() {
    // Standard form elements
    document.querySelectorAll('input, textarea, select').forEach(el => {
        el.addEventListener('input', triggerAutoSave);
        el.addEventListener('change', triggerAutoSave);
    });
    // Radio buttons and checkboxes
    document.querySelectorAll('input[type="radio"], input[type="checkbox"]').forEach(el => {
        el.addEventListener('change', triggerAutoSave);
    });
    // Content editable elements
    document.querySelectorAll('[contenteditable="true"]').forEach(el => {
        el.addEventListener('input', triggerAutoSave);
    });
    // Custom interactive elements (calendars, sliders, etc.)
    document.querySelectorAll('.interactive-element').forEach(el => {
        el.addEventListener('click', () => triggerAutoSave());
    });
}