I have to merge some js code in uibuilder

hi there,
i tried to make two code in two different uibuilder node, now wanted to make it in single code for single uibuilder but there is some issued which i dont understand how to solve it
so for payload which i send to uibuilder from function node

msg.topic = "control";
 msg.payload = {
        "FRONT_ZONE_TEMP": Math.floor(Math.random() * 500),
        "MIDDLE_ZONE_TEMP": Math.floor(Math.random() * 500),
        "BACK_ZONE_TEMP": Math.floor(Math.random() * 500),
        "BLOWER_FAN1": Math.round(Math.random()), // Random 0 or 1
        "BLOWER_FAN2": Math.round(Math.random()), // Random 0 or 1
        "door": true,  // Alternates between true/false
        "cont": true   // Matches door state (both true or both false)
    };

    return msg;

so below is index.js code of first uibuilder node

'use strict';
let chartConfigurations = [];
let deviceStatus = {};
document.addEventListener("DOMContentLoaded", function() {
  let progressBar = document.getElementById("loading-bar");
  let progress = 0;
  // Simulate loading progress
  let interval = setInterval(function() {
    progress += 10;
    progressBar.style.width = progress + "%";
    progressBar.setAttribute("aria-valuenow", progress);
    if (progress >= 100) {
      clearInterval(interval);
      setTimeout(() => {
        document.getElementById("loading-container").style.display = "none";
        document.getElementById("content").style.display = "block";
      }, 500);
    }
  }, 300);
});
// Function to update the UI based on deviceStatus
function updateUI(container) {
  container.innerHTML = ''; // Clear previous UI elements
  // Iterate through the stored statuses and update the UI
  Object.entries(deviceStatus).forEach(([host, info]) => {
    const {
      name,
      status
    } = info;
    // Create a new container for the status box and the IP address
    const statusContainer = document.createElement('div');
    statusContainer.className = 'text-center mx-2'; // Center text and add margin
    const statusIndicator = document.createElement('div');
    statusIndicator.className = `status-box ${status === 'online' ? 'bg-success blink-fast' : 'bg-danger blink-slow'}`;
    // Append the status indicator to the container
    statusContainer.appendChild(statusIndicator);
    // Create a container for the name and IP address
    const nameContainer = document.createElement('div');
    nameContainer.className = 'text-center text-bold'; // Apply bold text
    // Create and append the device name above the IP address
    const nameText = document.createElement('p');
    nameText.textContent = name || 'Unknown Name'; // Display the name, or "Unknown Name" if not available
    nameText.className = 'font-weight-bold'; // Make the name bold
    nameContainer.appendChild(nameText);
    // Create and append the host/IP address below the name
    const hostText = document.createElement('p');
    hostText.textContent = host;
    nameContainer.appendChild(hostText);
    // Append the name container to the status container
    statusContainer.appendChild(nameContainer);
    // Append the status container to the main ping status container
    container.appendChild(statusContainer);
  });
}
// Fetch stored chart configurations from server
function fetchConfigurationsFromServer() {
  fetch('/fetch-config')
    .then(response => response.json())
    .then(data => {
      console.log('Fetched configurations:', data);
      chartConfigurations = data;
      initializeCharts();
    })
    .catch(error => console.error('Error fetching configurations:', error));
}
// Auto-refresh configurations on page load
document.addEventListener('DOMContentLoaded', () => {
  fetchConfigurationsFromServer();
});
// Handle incoming messages from Node-RED
uibuilder.onChange('msg', (msg) => {
  if (msg.payload) {
    console.log('Received payload:', msg.payload); // Log the received payload
    updateCharts(msg.payload); // Update charts based on new payload data
  } else {
    console.error('No payload received in message.');
  }
  console.log('Received message:', msg); // Debugging log
  const pingStatusContainer = document.getElementById('ping-status');
  // Validate msg.payload is an object
  if (msg.payload && typeof msg.payload === 'object') {
    const {
      host,
      name = 'Unknown Name',
      status = 'offline'
    } = msg.payload; // Provide defaults if undefined
    if (!host) {
      //console.warn("Missing 'host' in payload:", msg.payload);
      return; // Skip processing if host is missing
    }
    // Update global device status tracking
    deviceStatus[host] = {
      name,
      status
    };
    // Update UI
    updateUI(pingStatusContainer);
  } else {
    console.error('Invalid payload format:', msg.payload);
  }
  console.log("Updated Device Status:", deviceStatus);
});
// Load chart configurations from the server
function loadConfigurationsFromServer() {
  fetch('/fetch-config')
    .then(response => response.json())
    .then(data => {
      chartConfigurations = data;
      console.log('Loaded chart configurations from server:', chartConfigurations);
      initializeCharts(); // Reinitialize charts with the fetched configurations
    })
    .catch(err => {
      console.error('Error fetching configurations:', err);
    });
}
document.addEventListener('DOMContentLoaded', () => {
  loadConfigurationsFromServer(); // Load configurations on page load
});
// Dynamically create charts based on configurations
function initializeCharts() {
  const chartsContainer = document.getElementById('charts-container');
  if (!chartsContainer) {
    console.error('Charts container not found!');
    return;
  }
  chartsContainer.innerHTML = ''; // Clear any existing charts
  if (Array.isArray(chartConfigurations) && chartConfigurations.length > 0) {
    chartConfigurations.forEach((config, configIndex) => {
      if (config.payloadNames && Array.isArray(config.payloadNames)) {
        config.payloadNames.forEach((payloadName, keyIndex) => {
          const chartWrapper = document.createElement('div');
          chartWrapper.className = 'chart-wrapper';
          const chartCanvas = document.createElement('canvas');
          chartCanvas.id = `chart-${configIndex}-${keyIndex}`;
          chartWrapper.appendChild(chartCanvas);
          chartsContainer.appendChild(chartWrapper);
          const ctx = chartCanvas.getContext('2d');
          const dataset = {
            label: payloadName,
            data: [], // Empty dataset initially
            borderColor: `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 1)`,
            backgroundColor: `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.2)`,
            borderWidth: 2,
          };
          new Chart(ctx, {
            type: config.chartType || 'line', // Default to 'line' if no type is provided
            data: {
              labels: [], // Labels are dynamically updated
              datasets: [dataset],
            },
            options: {
              responsive: true,
              maintainAspectRatio: false,
              plugins: {
                legend: {
                  labels: {
                    color: 'black', // Set legend text color to black
                    generateLabels: (chart) => {
                      return chart.data.datasets.map((dataset, index) => {
                        let lastValue;
                        // Handle different data types
                        if (dataset.data.length > 0) {
                          const lastDataPoint = dataset.data[dataset.data.length - 1];
                          if (typeof lastDataPoint === 'object' && lastDataPoint !== null) {
                            // For Scatter/Bubble charts, format the object {x, y, r} or {x, y}
                            const x = lastDataPoint.x !== undefined ? `x: ${lastDataPoint.x}` : '';
                            const y = lastDataPoint.y !== undefined ? `y: ${lastDataPoint.y}` : '';
                            lastValue = `${x}, ${y}`.trim();
                          } else {
                            // For other chart types, use the raw value directly
                            lastValue = lastDataPoint;
                          }
                        } else {
                          lastValue = 'N/A'; // Default to 'N/A' if no value exists
                        }
                        return {
                          text: `${dataset.label}: ${lastValue}`,
                          fillStyle: dataset.borderColor, // Use dataset color as fill style
                          fontColor: 'black', // Set text color explicitly to black
                          hidden: !chart.isDatasetVisible(index),
                        };
                      });
                    },
                  },
                },
                tooltip: {
                  callbacks: {
                    label: (tooltipItem) => {
                      const point = tooltipItem.raw;
                      return `${tooltipItem.dataset.label}: ${point}`;
                    },
                  },
                },
              },
            },
          });
        });
      }
    });
  } else {
    console.warn('No valid chart configurations found.');
  }
}
// Update chart data dynamically when new payload is received
function updateCharts(payload) {
  console.log('Updating charts with new payload:', payload);
  chartConfigurations.forEach((config, configIndex) => {
    if (config.payloadNames && Array.isArray(config.payloadNames)) {
      config.payloadNames.forEach((key, keyIndex) => {
        const chartId = `chart-${configIndex}-${keyIndex}`;
        const chart = Chart.getChart(chartId);
        if (!chart) {
          console.warn(`Chart ${chartId} not found.`);
          return;
        }
        if (payload[key] !== undefined) {
          const currentTime = new Date().toLocaleTimeString();
          if (config.chartType === 'bubble') {
            // For Bubble chart: {x, y, r}
            chart.data.datasets[0].data.push({
              x: Math.random() * 100, // Example value, replace as needed
              y: payload[key],
              r: Math.random() * 10 + 5, // Example radius
            });
          } else if (config.chartType === 'scatter') {
            // For Scatter chart: {x, y}
            chart.data.datasets[0].data.push({
              x: Math.random() * 100, // Example value, replace as needed
              y: payload[key],
            });
          } else {
            // For other chart types
            chart.data.labels.push(currentTime);
            chart.data.datasets[0].data.push(payload[key]);
          }
          // Keep only the last 10 data points
          if (chart.data.labels.length > 10) {
            chart.data.labels = chart.data.labels.slice(-10);
            chart.data.datasets[0].data = chart.data.datasets[0].data.slice(-10);
          }
          chart.update();
        }
      });
    }
  });
}
// for header and footer 
// Function to load an external HTML file into a div
function loadHTML(id, file) {
  fetch(file)
    .then(response => response.text())
    .then(data => {
      document.getElementById(id).innerHTML = data;
    })
    .catch(error => console.error("Error loading " + file, error));
}

and below is another index.js code

// State variables to track the last-known state of the blower fans
const lastFanState = {
  'BLOWER_FAN1': 0,
  'BLOWER_FAN2': 0,
};
// Listen for incoming messages and process them
uibuilder.onChange('msg', function(msg) {
  if (!msg || !msg.payload) {
    console.error("No payload in message.");
    return;
  }
  console.log('Message received from Node-RED:', msg);
  // Update temperatures and fire particles directly using real names
  updateFireParticles('fire-1', 'temp-reading-1', 'BACK ZONE TEMP', msg.payload['BACK_ZONE_TEMP']);
  updateFireParticles('fire-2', 'temp-reading-2', 'MIDDLE ZONE TEMP', msg.payload['MIDDLE_ZONE_TEMP']);
  updateFireParticles('fire-3', 'temp-reading-3', 'FRONT ZONE TEMP', msg.payload['FRONT_ZONE_TEMP']);
  // Update fan rotations only if the state changes
  updateFanRotation('fan1', msg.payload.BLOWER_FAN1, 'BLOWER_FAN1');
  updateFanRotation('fan2', msg.payload.BLOWER_FAN2, 'BLOWER_FAN2');
  // Update dynamic data display if additional data exists
  updateData(msg.payload);
  // Update the last updated time
  updateDateTime();
});
// Function to update fire particles and temperature readings
function updateFireParticles(fireId, tempReadingId, zoneName, temperature) {
  const fire = document.getElementById(fireId);
  const tempReading = document.getElementById(tempReadingId);
  if (!fire || !tempReading) {
    console.error(`Element with ID ${fireId} or ${tempReadingId} not found.`);
    return;
  }
  if (temperature === 0) {
    fire.style.display = 'none';
    tempReading.textContent = `${zoneName}: No fire (0°C)`;
    return;
  } else {
    fire.style.display = 'block';
  }
  // Determine fire scale and particle count based on temperature
  let color, scale, numParticles;
  if (temperature >= 500) {
    color = 'red';
    scale = 1.5; // Large fire
    numParticles = 50; // Maximum particles
  } else if (temperature >= 250) {
    color = 'orange';
    scale = 1.2; // Medium fire
    numParticles = 30; // Moderate particles
  } else {
    color = 'yellow';
    scale = 0.8 + (temperature / 250) * 0.4; // Smaller fire, dynamically scaled
    numParticles = Math.max(10, Math.floor((temperature / 250) * 30)); // Minimum 10 particles
  }
  // Adjust fire size (scale)
  fire.style.transform = `scale(${scale})`;
  // Set fire color (if applicable) and particle count
  fire.style.backgroundColor = color; // Assuming fire color is represented by background color
  fire.dataset.particles = numParticles; // Store particle count in a custom data attribute
  // Update the temperature reading
  tempReading.textContent = `${zoneName}: ${temperature} °C`;
}
// Function to update fan rotation based on blower status
function updateFanRotation(fanId, fanStatus, fanName) {
  const fan = document.getElementById(fanId);
  if (!fan) {
    console.error(`Fan element with ID ${fanId} not found.`);
    return;
  }
  const spinElement = fan.querySelector('.spin');
  if (!spinElement) {
    console.error(`Spin element not found inside fan ${fanId}.`);
    return;
  }
  // Debugging log
  console.log(`${fanName} received status:`, fanStatus);
  // Ensure only updates when the status changes
  if (lastFanState[fanName] !== fanStatus) {
    if (fanStatus === 1) {
      spinElement.style.animation = 'spin 1s linear infinite'; // Ensure animation runs
      console.log(`${fanName} is ON.`);
    } else { // Ensure OFF state is properly handled
      spinElement.style.animation = 'none'; // Stop animation immediately
      console.log(`${fanName} is OFF.`);
    }
    // Update the last-known state
    lastFanState[fanName] = fanStatus;
  }
}
// Function to update dynamic data display
function updateData(data) {
  const dataContent = document.getElementById('data-content');
  if (!dataContent) return;
  dataContent.innerHTML = '';
  for (const [key, value] of Object.entries(data)) {
    const card = document.createElement('div');
    card.className = 'col-md-4';
    card.innerHTML = `
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">${key}</h5>
                    <p class="card-text">Value: ${value}</p>
                </div>
            </div>
        `;
    dataContent.appendChild(card);
  }
}
// Function to update the last updated date and time
function updateDateTime() {
  const now = new Date();
  const dateTimeElement = document.getElementById('date-time');
  if (dateTimeElement) {
    dateTimeElement.innerText = `Last Updated: ${now.toLocaleString()}`;
  }
}
// Automatically update date and time every second
setInterval(updateDateTime, 1000);
// Function to request fresh data from Node-RED
function requestData() {
  uibuilder.send({
    topic: 'requestData',
    payload: {}
  });
}
// Wait for message from Node-RED
// Listen for messages from Node-RED
uibuilder.onChange('msg', function(msg) {
  console.log("Received message from Node-RED:", msg); // Debugging log
  // Ensure the message has the correct topic
  if (msg.topic === "control") {
    updateUI(msg.payload);
  }
});
// Function to update the UI based on received payload
function updateUI(payload) {
  let doorElement = document.querySelector('.door');
  let contElement = document.querySelector('.cont');
  let shapeElement = document.querySelector('.shape-dark-outline');
  if (!doorElement || !contElement || !shapeElement) {
    console.error("One or more elements not found!");
    return;
  }
  if (payload.door) {
    // Step 1: Open the door
    doorElement.style.top = "35%";
    console.log("Door is OPEN");
    // Step 2: Move cont smoothly with conveyor animation
    shapeElement.style.animation = "conveyor-belt 5s linear infinite";
    contElement.style.transition = "left 5s linear"; // Smooth movement
    contElement.style.left = "28em";
    console.log("Cont moving with animation");
    // Step 3: Hold at left:28em for 3 seconds before disappearing
    setTimeout(() => {
      console.log("Cont is holding at left: 28em");
      // Step 4: Make cont disappear after holding for 3 seconds
      setTimeout(() => {
        contElement.style.display = "none"; // Hide cont
        shapeElement.style.animation = "none"; // Stop conveyor animation
        console.log("Cont disappeared, conveyor animation stopped");
        // Step 5: Reset everything after 2 more seconds (Total: 10s)
        setTimeout(() => {
          doorElement.style.top = "42%"; // Reset door position
          contElement.style.display = "block"; // Show cont again
          contElement.style.left = "15em"; // Reset cont position
          contElement.style.transition = "none"; // Remove transition for instant reset
          console.log("Reset complete, system ready for next cycle");
        }, 2000);
      }, 3000); // Hold at 28em for 3 seconds before disappearing
    }, 5000); // Move to 28em over 5 seconds
  }
}

which both i have to merged in single code so that it can work.

The usual way to handle multiple different data coming into a single page would be to either use uibuilder.onChange('msg', (msg) => { ... }) with a test for different msg.topic's in the function. Alternatively, you can use two uibuilder.onTopic('sometopic', (msg) => { ... }) functions based on the msg.topic.

Of course, if you like, you can move some of the detail code into one or more separate script files and simply load them in your HTML (or import them in your JavaScript if you prefer to use ES Module style).

uibuilder already has a function for that. And, I believe, one that will incorporate the safety of DOMpurify if you have that library loaded.

You might find it easier to do that using messaging to/from Node-RED. Remember that any browser tab connecting to your uibuilder endpoint triggers a connection control message which you can either manually use to trigger sending the chart config or you could use a uib-cache node.

i might thought a bit help in code if you can do coz really i tell i dont know about js this code is done by AI and i tried it to play with it but did not got a solution so i posted it to forum.

It would help if you explained a bit more - I don't really have time at the moment to write code for you.

You might also find it easier to create a brand new, clean uibuilder node to work with so that you can build things up piece by piece. At the moment, you have a lot of JavaScript and if you don't fully understand it, it will only get in the way for now. You can combine it fully later.

But assuming that you have two separate web pages, each of which is working. Combining them is a matter of making sure that you have HTML with unique ID's which combine the bodies from your separate pages.

Then you need to be able to differentiate between the two sets of data - originally one set for each endpoint and now you need the two sets to go to a single page via a single uibuilder node. So that is the first thing to work out.

Lets say that your two sets of data from node-red each have their own unique payload, you need to set two different msg.topic's - lets say "topic1" and "topic2".

In your (temporarily) simplified index.js, you just need two functions:

uibuilder.onTopic('topic1', (msg) => {
  console.log('TOPIC1: ', msg)
})

uibuilder.onTopic('topic2', (msg) => {
  console.log('TOPIC2: ', msg)
})

So each msg type will be separately processed.

Now you currently have some JSON data that you want to pre-load that will be the settings for one or more of your charts? You probably don't need to fetch that now, just set it as a variable in your code: const chartConfig = { ... } or some such. That will replace function fetchConfigurationsFromServer(). You can always come back to fetching the data if you need to later on, or more likely sending it from node-red. For now, keep things as simple as possible.

Once you've got that far. The next step will be to create two functions that you call when you receive and appropriate msg.

uibuilder.onTopic('topic1', (msg) => {
  // console.log('TOPIC1: ', msg)
  // ... do your initial msg content validation here ...
  processCharts(msg.payload)  // make your fn names reflect what they actually do
  // ... if you need to do more things, use functions to keep this code easy to comprehend ...
})

uibuilder.onTopic('fanData', (msg) => {
  // console.log('TOPIC2: ', msg)
  // ... do your initial msg content validation here ...
  processFans(msg.payload)
})

const chartConfig = { ... }

function processCharts(chartData) {
   console.log('processing chart data: ', chartData)
}

function processFans(fanData) {
   console.log('processing new fan data: ', fanData)
}

Note that you are slowly building up your processing, don't try to do everything at once because it will be confusing.

Also, take a step back and maybe jot down a list of the steps you need to take to process everything. Then slowly build out the functions, giving each a meaningful name.

Some questions you need to answer for yourself to make sure you can build the structures:

  • What defines the fans? Does each have a unique identifier in the Node-RED data? If not, you probably have to build the whole dashboard each time which is terribly inefficient. If you do have that, then make sure that each data update contains the id and you only need to update a single chart/card. If you keep a list of the fan id's in your front-end code, you know if a new one appears and you can just add that one to the dashboard. Otherwise, you only need to update a single chart/card when new data is received.
  • Do all the charts/cards need the same settings/structure? If so, you can create functions that build a single one and then call that function with appropriate data.

Above all, take a slow, methodical approach and gradually build up the process in small stages using repeatable functions wherever possible.