Hi everyone. I've used this forum over the years and received tons of help. Just wanted to give back and share a Status Popup that I add to my custom nodes. Hopefully it will be useful to someone.
The motivation was to get a little more status information on the screen than what's provided by the built-in status text.
It looks like this:
You click on the node to open the popup. And click again to close. It can be programmed to stay open or auto-close after a timeout. The popup text is sent from the backend JS.
To add it to your custom node:
1: Add this file to your node's "resources" directory.
Located at:
.node-red/node_modules/YourNodeDirectory/resources/AddPopupSupport.js
(Create the directory if necessary.)
AddPopupSupport.js
//
// Popup for a custom node
//
// To use:
//
// 1. Add this file to the resources directory: .node-red/node_modules/<custom node>/resources/
//
// 2. Make the HTML file look like this:
//
// --------------------------------------------------
// Add this to import the popup code
// --------------------------------------------------
// <script src="/resources/<custom node>/AddPopupSupport.js"></script>
//
// <script type="text/javascript">
//
// --------------------------------------------------
// Make this function auto run
// --------------------------------------------------
// (function() {
// RED.nodes.registerType('<custom node>',{ // this is the label in the pallette
// ...
// });
// })();
//
// --------------------------------------------------
// Create the popup
// --------------------------------------------------
// AddPopupSupport('<custom node>', {width:160, height:80, autoCloseTime: 10000});
// // width, height, autoCloseTime are optional
//
// </script>
//
// 3. Send Message from the JS file with this:
// RED.comms.publish('<custom node>/popupstatustext', {
// nodeID: node.id,
// popupHTML: "Custom message for<br>"+ node.id + "<br>" + Date(), // put custom status message here
// });
//
//
function AddPopupSupport(nodeType, options) {
const width = (options?.width) ? options.width : 160;
const height = (options?.height) ? options.height : 80;
const autoCloseTime = (options?.autoCloseTime) ? options.autoCloseTime : -1;
// subscribe to backend messages for this nodetype
if (!window[nodeType+'Subscribed']) {
window[nodeType+'Subscribed'] = true;
RED.comms.subscribe(nodeType+'/popupstatustext', function (topic, msg) {
const popupDiv = document.getElementById("popup.div." + msg.nodeID); // access the div
popupDiv.innerHTML = msg.popupHTML;
});
}
// Create a popup to display diagnostics
RED.events.on('runtime-state', function () {
RED.nodes.eachNode( (node)=>{
if ((node.type === nodeType) & (!document.getElementById('popup.div.'+node.id))) {
//------------------------------------------------------------------------------------------------
// Create the Popup elements
//------------------------------------------------------------------------------------------------
const svgElement = document.getElementById(node.id);
// create svg group
let group = document.createElementNS("http://www.w3.org/2000/svg", "g");
group.setAttribute("transform", "translate(0, 50)");
group.style.display = 'none';
// Create a svg rect element for the background
let rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("id", "rect");
rect.setAttribute("x", "0");
rect.setAttribute("y", "0");
rect.setAttribute("width", width);
rect.setAttribute("height", height);
rect.setAttribute("fill", "#f1f1f1");
rect.setAttribute("stroke", "#d4d4d4");
rect.setAttribute("stroke-width", "1");
rect.setAttribute("rx", "6"); // Border radius
// Create a foreignObject element
let foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
const sourceText = svgElement.querySelector('.red-ui-flow-node-status-label');
for (const attr of sourceText.attributes) { foreignObject.setAttribute(attr.name, attr.value) };
foreignObject.setAttribute("x", "6");
foreignObject.setAttribute("y", "5");
foreignObject.setAttribute("width", width - 12);
foreignObject.setAttribute("height", height - 10);
// Create div for our HTML to live
let divElement = document.createElement("div");
divElement.setAttribute("id", "popup.div." + node.id);
divElement.style.width = width;
divElement.style.height = height;
divElement.innerHTML = '<p>' + node.id + '</p>'
// put it all together
foreignObject.appendChild(divElement);
group.appendChild(rect);
group.appendChild(foreignObject);
svgElement.appendChild(group);
//------------------------------------------------------------------------------------------------
// Event Handlers
//------------------------------------------------------------------------------------------------
let dblClicked = false;
let mousedownLongTime = false;
let autoCloseTimeout = null;
svgElement.addEventListener('click', function() {
setTimeout(function() {
if (!dblClicked && !mousedownLongTime) {
if (group.style.display === 'block') {
if (autoCloseTimeout) { clearTimeout(autoCloseTimeout) };
group.style.display = 'none';
} else {
svgElement.parentNode.appendChild(svgElement); // move to end (top)
group.style.display = 'block';
if (autoCloseTime > 0) {
autoCloseTimeout = setTimeout(() => { group.style.display = 'none'; }, autoCloseTime);
}
}
}
}, 250);
});
svgElement.addEventListener('dblclick', function(event) {
dblClicked = true;
setTimeout(() => { dblClicked = false; }, 300);
event.stopPropagation();
});
let mousedownTimer = null;
svgElement.addEventListener('mousedown', function(event) {
mousedownTimer = setTimeout(() => { mousedownLongTime = true; }, 150);
}, true);
svgElement.addEventListener('mouseup', function(event) {
if (mousedownTimer) { clearTimeout(mousedownTimer) };
setTimeout(() => { mousedownLongTime = false; }, 300);
}, true);
}
})
});
}
2: SETUP YOUR HTML SCRIPT
Setup your custom node HTML script to import the popup like this:
HTML File
<script src="/resources/<YourNodeName>/AddPopupSupport.js"></script>
<script type="text/javascript">
//---------------------------------------
// make this an autorun function
//---------------------------------------
(function() {
RED.nodes.registerType('<YourNodeName>',{
// your typical custom code goes here
});
})();
//---------------------------------------
//These are the only new lines to add
//---------------------------------------
AddPopupSupport('<YourNodeName>', {width:160, height:80, autoCloseTime:10000});
// width, height and autoCloseTime are optional
</script>
// the rest of your HTML file goes here
3: SEND STATUS TEXT FROM BACKEND
Similar to how you send status using node.status
use this to send HTML formatted status messages to the popup from the backend JS:
JS File
RED.comms.publish('<YourNodeName>/popupstatustext', {
nodeID: node.id,
popupHTML: "This is a test<br>For "+ node.id,
});
It seems to work for all my use cases.
I'm probably breaking a lot of accepted practices with this and there's probably better solutions out there but I haven't found it.
I'm just a novice and took some shortcuts with formatting and such too. I know this can be done cleaner but hey it works.
Have fun.
edit: Updated the code with some bug fixes that were pointed out.