// renderer.js document.addEventListener('DOMContentLoaded', () => { console.log('DOM fully loaded. renderer.js is now running.'); // --- 0. INITIALIZE FORM DEFAULTS --- const publicationDateInput = document.getElementById('publicationDate'); if (publicationDateInput) { publicationDateInput.value = new Date().toISOString().split('T')[0]; } // --- 1. TAB SWITCHING LOGIC --- const tabButtons = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabButtons.forEach(button => { button.addEventListener('click', () => { tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); button.classList.add('active'); document.getElementById(button.dataset.tab + '-tab').classList.add('active'); }); }); document.getElementById('machine-tab').classList.add('active'); // --- 2. GENERIC DYNAMIC TABLE LOGIC --- function setupDynamicTable({ tableBodyId, templateId, bulkInputId, bulkBtnId, fields, // Array of field names in order (e.g. ['alarm_id', 'alarm_name', 'alarm_message']) prefix, // e.g. 'ALARM' nameFormat, // e.g. (id, paddedId) => `ALARM_${paddedId}` initialRow = true, // Number of initial rows to add }) { const tableBody = document.getElementById(tableBodyId); const rowTemplate = document.getElementById(templateId); const bulkInput = bulkInputId ? document.getElementById(bulkInputId) : null; const bulkBtn = bulkBtnId ? document.getElementById(bulkBtnId) : null; // Add initial row if (initialRow) { addRow(tableBody, rowTemplate, 1, fields, prefix, nameFormat); } // Bulk add if (bulkBtn && bulkInput) { bulkBtn.addEventListener('click', () => { let count = parseInt(bulkInput.value, 10); if (isNaN(count) || count < 1) count = 1; const currentIdInputs = tableBody.querySelectorAll(`[name="${fields[0]}"]`); let maxId = 0; currentIdInputs.forEach(input => { const currentId = parseInt(input.value, 10); if (!isNaN(currentId) && currentId > maxId) maxId = currentId; }); for (let i = 1; i <= count; i++) { addRow(tableBody, rowTemplate, maxId + i, fields, prefix, nameFormat); } }); } else if (bulkBtn){ bulkBtn.addEventListener('click', () => { const currentIdInputs = tableBody.querySelectorAll(`[name="${fields[0]}"]`); let maxId = 0; currentIdInputs.forEach(input => { const currentId = parseInt(input.value, 10); if (!isNaN(currentId) && currentId > maxId) maxId = currentId; }); addRow(tableBody, rowTemplate, maxId + 1, fields, prefix, nameFormat); }); } // Paste from Excel tableBody.addEventListener('paste', (event) => { const targetCell = event.target.closest('td'); const targetRow = event.target.closest('tr'); const allRows = Array.from(tableBody.querySelectorAll('tr')); let startRowIdx = allRows.indexOf(targetRow); if (startRowIdx === -1) startRowIdx = allRows.length; // Find the cell index in the row let startCellIdx = 0; if (targetCell && targetRow) { const cells = Array.from(targetRow.children); startCellIdx = cells.indexOf(targetCell); if (startCellIdx === -1) startCellIdx = 0; } // Get clipboard data const clipboardData = event.clipboardData || window.clipboardData; const pastedData = clipboardData.getData('Text'); if (!pastedData) return; // Find current max ID for auto-increment const currentIdInputs = tableBody.querySelectorAll(`[name="${fields[0]}"]`); let maxId = 0; currentIdInputs.forEach(input => { const currentId = parseInt(input.value, 10); if (!isNaN(currentId) && currentId > maxId) maxId = currentId; }); // Split rows by newline, columns by tab const rows = pastedData.trim().split(/\r?\n/); rows.forEach((row, rowOffset) => { const cols = row.split('\t'); if (cols.every(col => !col.trim())) return; // Ignore empty rows let rowToFill = allRows[startRowIdx + rowOffset]; if (!rowToFill) { // Add new row if needed maxId += 1; rowToFill = addRow(tableBody, rowTemplate, maxId, fields, prefix, nameFormat); } // Get all editable fields in the row const editableFields = fields.map(f => rowToFill.querySelector(`[name="${f}"]`)); // If only last field (description/message) is pasted, auto-fill ID and name if (cols.length === 1 || (cols.length > 0 && cols.slice(0, -1).every(col => !col.trim()))) { maxId += (!rowToFill.querySelector(`[name="${fields[0]}"]`).value ? 1 : 0); const paddedId = maxId.toString().padStart(3, '0'); if (editableFields[0]) editableFields[0].value = maxId; if (editableFields[1] && nameFormat) editableFields[1].value = nameFormat(maxId, paddedId); if (editableFields[editableFields.length - 1]) editableFields[editableFields.length - 1].value = cols[cols.length - 1].trim(); } else { // Paste values to the right, only filling available cells for (let colOffset = 0; colOffset < cols.length; colOffset++) { const fieldIdx = startCellIdx + colOffset; if (fieldIdx < editableFields.length && editableFields[fieldIdx]) { editableFields[fieldIdx].value = cols[colOffset].trim(); } } } }); event.preventDefault(); // Prevent default paste }); } function addRow(tableBody, rowTemplate, id, fields, prefix, nameFormat) { const paddedId = id.toString().padStart(3, '0'); const clone = rowTemplate.content.cloneNode(true); if (fields[0]) clone.querySelector(`[name="${fields[0]}"]`).value = id; if (fields[1] && nameFormat) clone.querySelector(`[name="${fields[1]}"]`).value = nameFormat(id, paddedId); const removeBtn = clone.querySelector('.remove-row-btn'); if (removeBtn) { removeBtn.addEventListener('click', (e) => { e.target.closest('tr').remove(); }); } tableBody.appendChild(clone); return tableBody.querySelectorAll('tr')[tableBody.querySelectorAll('tr').length - 1]; } // --- ALARMS --- setupDynamicTable({ tableBodyId: 'alarms-table-body', templateId: 'alarm-row-template', bulkInputId: 'bulk-alarm-count', bulkBtnId: 'add-multiple-alarms-btn', fields: ['alarm_id', 'alarm_name', 'alarm_message'], prefix: 'ALARM', nameFormat: (id, paddedId) => `ALARM_${paddedId}`, initialRow: true }); // --- WARNINGS --- setupDynamicTable({ tableBodyId: 'warnings-table-body', templateId: 'warning-row-template', bulkInputId: 'bulk-warning-count', bulkBtnId: 'add-multiple-warnings-btn', fields: ['warning_id', 'warning_name', 'warning_message'], prefix: 'WARNING', nameFormat: (id, paddedId) => `WARNING_${paddedId}`, initialRow: true }); // --- COUNTERS --- setupDynamicTable({ tableBodyId: 'counters-table-body', templateId: 'counter-row-template', bulkInputId: null, // Add bulk if you want bulkBtnId: 'add-counter-btn', // Add bulk if you want fields: ['counter_id', 'counter_name', 'counter_type', 'counter_description'], prefix: 'COUNTER', nameFormat: (id, paddedId) => `COUNTER_${paddedId}`, initialRow: true }); // --- SETPOINTS --- setupDynamicTable({ tableBodyId: 'setpoints-table-body', templateId: 'setpoint-row-template', bulkInputId: null, // Add bulk if you want bulkBtnId: 'add-setpoint-btn', // Add bulk if you want fields: ['setpoint_id', 'setpoint_name', 'setpoint_message'], prefix: 'SETPOINT', nameFormat: (id, paddedId) => `SETPOINT_${paddedId}`, initialRow: false }); // --- REALTIME VARIABLES --- setupDynamicTable({ tableBodyId: 'realtime-table-body', templateId: 'realtime-row-template', bulkInputId: null, // Add bulk if you want bulkBtnId: 'add-realtime-btn', // Add bulk if you want fields: ['realtime_id', 'realtime_name', 'realtime_message'], prefix: 'REALTIME', nameFormat: (id, paddedId) => `REALTIME_${paddedId}`, initialRow: false }); // --- STATES --- setupDynamicTable({ tableBodyId: 'states-table-body', templateId: 'state-row-template', bulkInputId: null, bulkBtnId: 'add-state-btn', fields: ['state_id', 'state_name', 'state_description'], prefix: 'STATE', nameFormat: (id, paddedId) => `STATE_${paddedId}`, initialRow: false }); // 3. FORM SUBMISSION LOGIC (MODULARIZED) /** * Collects data from a dynamic table. * @param {string} tableBodyId - The ID of the table's tbody element. * @param {string[]} fields - An array of field names (name attributes of inputs). * @param {string} keyField - A field name that must have a value for the row to be included. * @returns {Object[]} An array of objects, where each object represents a row. */ const collectTableData = (tableBodyId, fields, keyField) => { const tableBody = document.getElementById(tableBodyId); if (!tableBody) return []; const rows = tableBody.querySelectorAll('tr'); const data = []; rows.forEach(row => { const rowData = {}; let hasKeyField = false; fields.forEach(field => { const input = row.querySelector(`[name="${field}"]`); if (input) { rowData[field.split('_').pop()] = input.value; if (field === keyField && input.value) { hasKeyField = true; } } }); // Only add the row if the key field is present and has a value if (hasKeyField) { data.push(rowData); } }); return data; }; const form = document.getElementById('machine-form'); if (form) { form.addEventListener('submit', async (event) => { event.preventDefault(); // A. Collect data from the main "Machine" tab using FormData const formData = new FormData(form); const mainData = {}; for (const [key, value] of formData.entries()) { // This check prevents multi-value fields (like addins) from being overwritten if (!mainData[key]) { mainData[key] = value; } } mainData.addins = formData.getAll('addins'); // B. Collect data from all dynamic tables using the modular function const alarmsData = collectTableData('alarms-table-body', ['alarm_id', 'alarm_name', 'alarm_message'], 'alarm_message'); const warningsData = collectTableData('warnings-table-body', ['warning_id', 'warning_name', 'warning_message'], 'warning_message'); const statesData = collectTableData('states-table-body', ['state_id', 'state_name', 'state_description'], 'state_description'); const countersData = collectTableData('counters-table-body', ['counter_id', 'counter_name', 'counter_type', 'counter_description'], 'counter_type'); const setpointsData = collectTableData('setpoints-table-body', ['setpoint_id', 'setpoint_name', 'setpoint_message'], 'setpoint_message'); const realtimeData = collectTableData('realtime-table-body', ['realtime_id', 'realtime_name', 'realtime_message'], 'realtime_message'); // C. Combine all data into a single object const finalData = { ...mainData, alarms: alarmsData, warnings: warningsData, states: statesData, counters: countersData, setpoints: setpointsData, realtime: realtimeData, states: statesData }; console.log('Sending all collected data to main process:', finalData); try { const result = await window.electronAPI.submitForm(finalData); if (result.success) { console.log('Form submitted successfully!'); // Optionally reset the form or give user feedback // form.reset(); // document.getElementById('publicationDate').value = new Date().toISOString().split('T')[0]; } } catch (error) { console.error('Error during form submission process:', error); } }); } // --- 4. TAB NAVIGATION BUTTONS --- const tabOrder = [ 'machine-tab', 'alarms-tab', 'warnings-tab', 'counters-tab', 'setpoints-tab', 'realtime-tab', 'states-tab' ]; // Add event listeners to all "Next" buttons document.querySelectorAll('.next-tab-btn').forEach((btn, idx) => { btn.addEventListener('click', () => { // Find the currently active tab const currentTab = document.querySelector('.tab-content.active'); if (!currentTab) return; const currentTabId = currentTab.id; const currentIdx = tabOrder.indexOf(currentTabId); const nextTabId = tabOrder[currentIdx + 1]; if (nextTabId) { // Switch tab button active state document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelector(`[data-tab="${nextTabId.replace('-tab','')}"]`).classList.add('active'); // Switch tab content active state document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active')); document.getElementById(nextTabId).classList.add('active'); } }); }); });