Scrolling Text box on dashboard?

Guys,

Wondering if anyone knows a way to implement a scrolling text box in Dashboard ? Lets say something like 30 chars wide by 10 lines deep, everytime i add a new line it is added at the top and all other lines scroll down with the bottom line dropping off ? (of course it could work the other way also such as Tailing a Linux file etc)

regards

Craig

As usual with Node-RED there are many possible ways to achieve what you want. Considering that you want a table with very few lines and, more important, with a fixed amount of lines (only 10 lines) then I would do it with SVG text in a UI template node. An auxiliary function would manage an array of elements and have them stored in the flow context.

Flow:

[{"id":"594506cc.51e408","type":"tab","label":"Scrolling text ","disabled":false,"info":""},{"id":"f26df972.5d3ae8","type":"ui_template","z":"594506cc.51e408","group":"1ebe6305.3aa6ad","name":"SVG based template","order":0,"width":"6","height":"6","format":"<style>\n\n    #tex1 {\n        font-weight:bolder;\n        font-size: 12;\n        letter-spacing: 4px;\n        fill: white;\n    }\n    \n</style>\n\n\n<svg height=\"300\" width=\"300\" >\n\n<text id=\"tex1\" x=\"10\" y=\"30\" > {{msg.payload[0]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"50\" > {{msg.payload[1]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"70\" > {{msg.payload[2]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"90\" > {{msg.payload[3]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"110\" > {{msg.payload[4]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"130\" > {{msg.payload[5]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"150\" > {{msg.payload[6]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"170\" > {{msg.payload[7]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"190\" > {{msg.payload[8]}} </text>\n<text id=\"tex1\" x=\"10\" y=\"210\" > {{msg.payload[9]}} </text>\n</svg>\n\n","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":760,"y":380,"wires":[[]]},{"id":"3438664c.1d39ba","type":"function","z":"594506cc.51e408","name":"Create table in context","func":"let sctab = [];\nflow.set(\"sctab\", sctab);\nreturn msg;","outputs":"1","noerr":0,"x":400,"y":220,"wires":[[]]},{"id":"6b543271.7a47fc","type":"inject","z":"594506cc.51e408","name":"","topic":"","payload":"Start","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"5","x":150,"y":220,"wires":[["3438664c.1d39ba"]]},{"id":"67195cf7.a5ea24","type":"function","z":"594506cc.51e408","name":"Add word to scrolling table","func":"let pay = msg.payload;\n\n// Read scrolling table from context\nlet sctab = flow.get(\"sctab\");\n\n// Modify scrolling table by adding msg.payload as first element\nlet size = sctab.unshift(pay);\n\n// Remove last element from scrolling table\nif (size >9) sctab.pop();\n\n// Update context for scrolling text\nflow.set(\"sctab\",sctab);\n\n// Shalow copy updated scrolling table to msg.payload\nmsg.payload = [...sctab];\n\nreturn msg;","outputs":1,"noerr":0,"x":500,"y":380,"wires":[["f26df972.5d3ae8"]]},{"id":"e4b42f5e.2458c","type":"inject","z":"594506cc.51e408","name":"","topic":"","payload":"Alfa","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":340,"wires":[["67195cf7.a5ea24"]]},{"id":"d5d60837.4cadd8","type":"inject","z":"594506cc.51e408","name":"","topic":"","payload":"Bravo","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":380,"wires":[["67195cf7.a5ea24"]]},{"id":"79f8982e.19f1d8","type":"inject","z":"594506cc.51e408","name":"","topic":"","payload":"Charlie","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":420,"wires":[["67195cf7.a5ea24"]]},{"id":"68b03084.d691","type":"inject","z":"594506cc.51e408","name":"","topic":"","payload":"Delta","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":460,"wires":[["67195cf7.a5ea24"]]},{"id":"1ebe6305.3aa6ad","type":"ui_group","z":"","name":"LAB","tab":"2d3c18ea.7ea3d8","order":3,"disp":false,"width":"6","collapse":false},{"id":"2d3c18ea.7ea3d8","type":"ui_tab","z":"","name":"Table","icon":"dashboard"}]
5 Likes

You are the man Andrei !!!

That is fantastic thanks - almost perfectly what i want !

regards

Craig

2 Likes

Good to know it helped. If you see any opportunity to improve the code or restyle the dashboard for better please let me know. Perhaps if it is improved I can make it available in the flow library.

Let's make less code lines with usage of ng-repeat

<svg height="300" width="300" > <text id="tex1" ng-repeat="line in msg.payload track by $index" x="10" ng:attr:y="{{$index * 20}}" > {{line}} </text> </svg>

:slight_smile:

Will do - but i think it will be a long time until i can improve on your code !!

regards

Craig

Thanks for that hotNipi - i am sure that is more elegant and tidier - but as a "babe in the woods" i find Andrei's is easier for me to follow and understand.

regards

Craig

I have just used your code and it works but it keeps cutting out part of the first payload... the full text is not shown, only parts of the string come up on the dashboard. like the cuts of some part of the string.. please do you know why this is

Yep. lets shift textlines down a bit
<svg height="300" width="300" > <text id="tex1" ng-repeat="line in msg.payload track by $index" x="10" ng:attr:y="{{($index * 20)+20}}" > {{line}} </text> </svg>

1 Like

Thank you soo mucch... that works.. youre a genius :DD

Oo no I'm not. It's just html :smiley:

Considering that you want a table with very few lines and, more important, with a fixed amount of lines (only 10 lines) then I would do it with SVG text in a UI template node

@Andrei : Do you suggest that for other scenarios another approach would be better suitable? I have an application where the lines in the text box are created dynamically (kind of log messages received asynchronously via mqtt, generated more or less continuously). I would limit the number of lines to be displayed at for example 100, and practically it should be possible to scroll the laast 100 lines stored in a ring buffer.

Hi @abra, I don't know how to do it with SVG (used in previous flow) as this would require complex handling of x, y positions. It's is not difficult to do something with the template node though. I just drafted a solution that seems to work (not thoroughly tested).

You will need to install node-red-contrib-ring-buffer, which is a fine node.

In this flow I use the moment.js library to generate fake time and some additional nodes to generate a fake list of events. The template node expects msg.payload to be an object with the properties time and event but, of course, you will modify this (and everything else) to suit your needs.

Flow:

[{"id":"f6b18fb3.2e17c","type":"tab","label":"Attempt Scrolling widget","disabled":false,"info":"https://groups.google.com/d/topic/node-red/oJyDSrXHvpg/discussion\n\n\n\n```\n    {\n        \"building\": \"home\",\n        \"room\": \"bedroom\",\n        \"device\": \"light\",\n        \"tech\": \"X10\",\n        \"id\": \"C5\",\n        \"type\": \"actuator\",\n        \"desc\": \"x10 device and lamp\"\n    },\n    {\n        \"building\": \"home\",\n        \"room\": \"office\",\n        \"device\": \"light\",\n        \"tech\": \"X10\",\n        \"id\": \"C6\",\n        \"type\": \"actuator\",\n        \"desc\": \"x10 device and lamp\"\n    }\n```\n\n![template-view-object](/nri/template-view-object.png)\n"},{"id":"71c11480.98778c","type":"template","z":"f6b18fb3.2e17c","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html>\n    <head></head>\n    <body>\n        <ul>\n            {{#payload}}\n                <li>{{time}}, {{event}}</li>\n            {{/payload}}\n        </ul>\n</body>\n</html>","x":620,"y":180,"wires":[["b6a83bc0.08c928"]]},{"id":"b6a83bc0.08c928","type":"ui_template","z":"f6b18fb3.2e17c","group":"435d299e.91f718","name":"","order":0,"width":"12","height":"3","format":"<style>\n\n    #tex1 {\n        font-weight:bolder;\n        font-family: Menlo, monospace;\n        font-size: 5;\n        letter-spacing: 4px;\n        color: lime;\n    }\n    \n</style>\n<div ng-bind-html=\"msg.payload\" id=\"tex1\"></div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":780,"y":180,"wires":[[]]},{"id":"f0a87718.580bb8","type":"comment","z":"f6b18fb3.2e17c","name":"Attempt to create a scrolling widget","info":"","x":180,"y":100,"wires":[]},{"id":"d9b9a91b.d40098","type":"ring-buffer","z":"f6b18fb3.2e17c","name":"","capacity":16,"order":"old-to-new","sendOnlyIfFull":false,"pushAfterClear":false,"extra":false,"x":460,"y":180,"wires":[["71c11480.98778c"]]},{"id":"f1f3829a.64333","type":"inject","z":"f6b18fb3.2e17c","name":"","topic":"","payload":"","payloadType":"date","repeat":"2","crontab":"","once":true,"onceDelay":"5","x":130,"y":180,"wires":[["4722a34d.ba57bc"]]},{"id":"4722a34d.ba57bc","type":"function","z":"f6b18fb3.2e17c","name":"Random object","func":"let mRandom = msg.payload % 26;\nlet moment = global.get('moment'); \nlet tim = moment().format('MMMM Do YYYY, h:mm:ss a');\n\nmsg.payload = {\"time\" : tim, \"event\" : flow.get(\"fakeEvents\")[mRandom]};\nreturn msg;","outputs":1,"noerr":0,"x":300,"y":180,"wires":[["d9b9a91b.d40098"]]},{"id":"1238b2c5.173cbd","type":"function","z":"f6b18fb3.2e17c","name":"Create fake events","func":"\nlet words = [\n    \"Alfa\",\n    \"Bravo\",\n    \"Charlie\", \n    \"Delta\",\n    \"Echo\",\n    \"Foxtrot\",\n    \"Golf\",\n    \"Hotel\",\n    \"India\",\n    \"Juliett\", \n    \"Kilo\", \n    \"Lima\", \n    \"Mike\", \n    \"November\", \n    \"Oscar\", \n    \"Papa\", \n    \"Quebec\", \n    \"Romeo\", \n    \"Sierra\", \n    \"Tango\", \n    \"Uniform\", \n    \"Victor\", \n    \"Whiskey\", \n    \"X-ray\", \n    \"Yankee\", \n    \"Zulu\"\n    \n    ];\n\nflow.set(\"fakeEvents\", words);\n\nmsg.payload = words;\nreturn msg;","outputs":"1","noerr":0,"x":390,"y":280,"wires":[["1c76d58b.f2018a"]]},{"id":"496927ba.28ba08","type":"inject","z":"f6b18fb3.2e17c","name":"","topic":"","payload":"Start","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"0.1","x":130,"y":280,"wires":[["1238b2c5.173cbd"]]},{"id":"1c76d58b.f2018a","type":"debug","z":"f6b18fb3.2e17c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":580,"y":280,"wires":[]},{"id":"435d299e.91f718","type":"ui_group","z":"","name":"Group 1","tab":"7ac377c5.e6ac08","disp":true,"width":"12","collapse":false},{"id":"7ac377c5.e6ac08","type":"ui_tab","z":"","name":"Tab1","icon":"dashboard"}]
1 Like

I forgot that there is a much better looking solution in Node-RED library:

https://flows.nodered.org/flow/6f164cfd4b548d603c7387b29ed54027

1 Like

Thaks for your sample and for the link. I probably need a combination of both, as the HTML scroll sample does not have a vertical scroll bar to see the older messages. I also have to search a sample about how to select a line from the table by double-clicking it.
Would be possible to open a "detailed view" when a line from the table is double-clicked, and to pass the context (the message payload for the line) to it?

It would be necessary a good amount of time and effort to devolop a widget with such capability. This would require an experienced front end developer and possibly additional libraries being added to the project. Perhaps a better approach is using Node-RED + influx stack (db + other tools).

Is it possible to use bootstrap in a node red ui_template ? If yes, does anyone have a sample or a link to some sample?

Yes, you can use the Angular scope to send the data for each row of your table back to the downstream flow. This discussion has some examples of how to do that.

Put a debug node on the output of your ui_template node that builds the table, so you can see what is being passed back when you click the row. Once you have the right data being returned, you can pass that message to a change node to set the msg.tab property to be the "next page" to show, and send that to to a ui_control node, which then switches the dashboard to that page. At that point you just use the payload from the clicked table row to populate the widgets showing more details -- simple, eh? ;*)

Thanks for your reply.
How could be possible for the vertically scrollable table from the example above to automatically set the vertical scroll position to the newest entry, at the bottom of the table?

1 Like

One technique is to have the table reverse sorted, so newest to oldest -- then any new rows will always be visible at the top of the page.

Another way would be to add a bit of javascript to the page, that gets the last row of the table and scrolls the browser to show it, using the DOM scrollIntoView() method.

1 Like