UNPKG

@360works/fmpromise

Version:

A modern JS toolkit for FileMaker Web Viewers, including a dev server and type generation.

271 lines (236 loc) 7.01 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>fmPromise Module Configuration</title> <style> :root { --primary-color: #007bff; --border-color: #dee2e6; --background-color: #f8f9fa; --text-color: #212529; --error-color: #dc3545; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 1.5rem; background-color: var(--background-color); color: var(--text-color); } .container { max-width: 800px; margin: 0 auto; background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } h1 { margin-top: 0; border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; font-weight: 500; } .form-group { margin-bottom: 1.5rem; } label { display: block; font-weight: 600; margin-bottom: 0.5rem; } textarea, input[type="text"], input[type="number"] { width: 100%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 4px; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 0.9rem; box-sizing: border-box; /* Important */ } textarea { min-height: 80px; resize: vertical; } .description { font-size: 0.85rem; color: #6c757d; margin-top: 0.5rem; } .footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border-color); display: flex; justify-content: flex-end; align-items: center; } button { padding: 0.75rem 1.5rem; border: none; border-radius: 4px; background-color: var(--primary-color); color: white; font-size: 1rem; font-weight: 600; cursor: pointer; } button:disabled { background-color: #6c757d; cursor: wait; } .status-message { margin-right: 1rem; color: var(--error-color); } .hidden { display: none; } </style> </head> <body> <div id="app-container" class="container"> <h1 id="module-title">Module Configuration</h1> <form id="config-form"></form> <div class="footer"> <span id="status-message" class="status-message hidden"></span> <button id="save-button">Save Configuration</button> </div> </div> <script type="module"> // This app is a self-contained module. We import fmPromise here. import fmPromise from '@360works/fmpromise'; const CONFIG_SESSION_KEY = 'FMPROMISE_CONFIG_SESSION'; const CONFIG_SAVED_SIGNAL_KEY = 'FMPROMISE_CONFIG_SAVED_SIGNAL'; const form = document.getElementById('config-form'); const saveButton = document.getElementById('save-button'); const statusMessage = document.getElementById('status-message'); const moduleTitle = document.getElementById('module-title'); /** * Maps our schema type to the correct FileMaker JSON function type constant. */ function getFmJsonType(type) { switch (type) { case 'number': return 'JSONNumber'; case 'calculation': return 'JSONRaw'; case 'field': case 'script': case 'text': case 'color': default: return 'JSONString'; } } /** * Takes the raw form values and constructs a FileMaker calculation string. */ function buildFmCalcString(formValues, schema) { let calc = 'JSONSetElement ( "{}"'; for (const key in schema) { const value = formValues[key] || ''; const attribute = schema[key]; // Only include non-empty values in the calculation if (value) { const jsonType = getFmJsonType(attribute.type); // For JSONString, we need to quote the value. For others, we don't. const formattedValue = jsonType === 'JSONString' ? `Quote(${value})` : value; calc += ` ; \n [ "${key}" ; ${formattedValue} ; ${jsonType} ]`; } } calc += '\n)'; return calc; } /** * Main application logic. */ async function main() { // 1. Get context from localStorage const sessionPayload = localStorage.getItem(CONFIG_SESSION_KEY); if (!sessionPayload) { document.body.innerHTML = '<h1>Error: Configuration session not found.</h1>'; return; } const {webViewerName, schema} = JSON.parse(sessionPayload); moduleTitle.textContent = `Configure: ${webViewerName}`; // 2. Fetch current UI values from FileMaker let currentUiValues = {}; let webViewerRecordId = null; try { const result = await fmPromise.executeFileMakerDataAPIRecords({ action: 'read', layouts: 'fmPromiseWebViewer', query: [{id: webViewerName}] }); if (result && result.length > 0) { webViewerRecordId = result[0].recordId; if (result[0].configuration_ui) { currentUiValues = JSON.parse(result[0].configuration_ui); } } else { throw new Error(`Web viewer with name "${webViewerName}" not found.`); } } catch (e) { statusMessage.textContent = e.message; statusMessage.classList.remove('hidden'); saveButton.disabled = true; return; } // 3. Dynamically build the form from the schema for (const key in schema) { const attribute = schema[key]; const formGroup = document.createElement('div'); formGroup.className = 'form-group'; const label = document.createElement('label'); label.htmlFor = key; label.textContent = `${attribute.label}${attribute.required ? ' *' : ''}`; // Use textarea for most types to accommodate calcs const input = document.createElement('textarea'); input.id = key; input.name = key; input.value = currentUiValues[key] || attribute.defaultValue || ''; const description = document.createElement('p'); description.className = 'description'; description.textContent = attribute.description || ''; formGroup.append(label, input, description); form.appendChild(formGroup); } // 4. Handle the save action saveButton.addEventListener('click', async () => { saveButton.disabled = true; saveButton.textContent = 'Saving...'; statusMessage.classList.add('hidden'); try { // Collect raw values from the form const formData = new FormData(form); const formValues = Object.fromEntries(formData.entries()); // Build the FileMaker calculation string const fmCalcString = buildFmCalcString(formValues, schema); // Save both the calc and the raw UI values await fmPromise.executeFileMakerDataAPI({ action: 'update', layouts: 'fmPromiseWebViewer', recordId: webViewerRecordId, fieldData: { configuration: fmCalcString, configuration_ui: JSON.stringify(formValues) } }); // Signal the original window and close this one localStorage.setItem(CONFIG_SAVED_SIGNAL_KEY, Date.now()); await fmPromise.performScript('fmPromise.closeConfigurationUI', null, {ignoreResult: true}); } catch (e) { statusMessage.textContent = `Error saving: ${e.message}`; statusMessage.classList.remove('hidden'); saveButton.disabled = false; saveButton.textContent = 'Save Configuration'; } }); } main(); </script> </body> </html>