Thank you for the hint. I got it working (see flow below), but I have some small problems/questions about it.
If I try to access the table element by its id inside scope.$watch() in the UI template, I got a null element
var elmtTable = document.getElementById("trace-table");
I can access the table element only as child of div
var elmt = document.getElementById("div-table");
var nrCh = elmt.children.length;
if (nrCh > 0) {
// the only div child is the table !
var elmtChild = elmt.children[0];
Why does this occur?
And the second question is why the table scrolls correctly to the bottom only after the trace buffer size is reached (only when more than 20 trace messages generated, in the example below).
Here is the flow I used:
[{"id":"efd360ba.a6cb","type":"inject","z":"d57cfd26.f61e1","name":"Trace1","topic":"","payload":"{\"t\":\"Trace message one, hello world\",\"l\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":300,"wires":[["e8472d92.442e6"]]},{"id":"8dbbf957.24d1f8","type":"inject","z":"d57cfd26.f61e1","name":"Trace2","topic":"","payload":"{\"t\":\"Trace message two, how are you\",\"l\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":340,"wires":[["e8472d92.442e6"]]},{"id":"cd720de.72391f","type":"inject","z":"d57cfd26.f61e1","name":"Trace3","topic":"","payload":"{\"t\":\"Trace message three, thats for me\",\"l\":3}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":380,"wires":[["e8472d92.442e6"]]},{"id":"a8c47c53.a9065","type":"inject","z":"d57cfd26.f61e1","name":"Trace4","topic":"","payload":"{\"t\":\"Trace message four, lets go !\",\"l\":2}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":420,"wires":[["e8472d92.442e6"]]},{"id":"213a470b.3158f8","type":"inject","z":"d57cfd26.f61e1","name":"Trace5","topic":"","payload":"{\"t\":\"Trace message five, big surprise\",\"l\":4}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":460,"wires":[["e8472d92.442e6"]]},{"id":"e8472d92.442e6","type":"function","z":"d57cfd26.f61e1","name":"set timestamp","func":"function date_format( d, p ) {\n var pad = function (n, l) {\n for (n = String(n), l -= n.length; --l >= 0; n = '0'+n);\n return n;\n };\n var tz = function (n, s) {\n return ((n<0)?'+':'-')+pad(Math.abs(n/60),2)+s+pad(Math.abs(n%60),2);\n };\n return p.replace(/([DdFHhKkMmSsyZ])\\1*|'[^']*'|\"[^\"]*\"/g, function (m) {\n l = m.length;\n switch (m.charAt(0)) {\n case 'd': return pad(d.getDate(), l);\n case 'H': return pad(d.getHours(), l);\n case 'h': return pad(d.getHours() % 12 || 12, l);\n case 'K': return pad(d.getHours() % 12, l);\n case 'k': return pad(d.getHours() || 24, l);\n case 'm': return pad(d.getMonth() + 1, l );\n case 'M': return pad(d.getMinutes(), l);\n case 'S': return pad(d.getMilliseconds(), l);\n case 's': return pad(d.getSeconds(), l);\n case 'y': return (l == 2) ? String(d.getFullYear()).substr(2) : pad(d.getFullYear(), l);\n case 'Z': return tz(d.getTimezoneOffset(), ' ');\n case \"'\":\n case '\"': return m.substr(1, l - 2);\n default: throw new Error('Illegal pattern');\n }\n });\n}\n\njs_obj = msg.payload;\n\nvar jstime = date_format(new Date(), 'dd-mm-yyyy HH:MM:ss.SSS');\n//var jstime = d.format(\"DD-MM-yyyy hh:mm:ss SSS\");\n\njs_obj.timestamp = jstime;\n\nmsg.payload = js_obj ;\n\nreturn msg;\n\n","outputs":1,"noerr":0,"x":420,"y":340,"wires":[["78e5413a.04c8c"]]},{"id":"78e5413a.04c8c","type":"function","z":"d57cfd26.f61e1","name":"handle trace_events","func":"var msg_obj = msg.payload ;\nvar arr_msgs = flow.get(\"trace_events\", 'memoryOnly');\n\nif (arr_msgs===undefined ) {\n // Create an empty array if it does not exist yet\n arr_msgs = [];\n}\n\narr_msgs.push(msg_obj);\nflow.set(\"trace_events\",arr_msgs, 'memoryOnly');\n\nmsg.payload = flow.get(\"trace_events\", 'memoryOnly');\nreturn msg;\n","outputs":1,"noerr":0,"x":440,"y":420,"wires":[["b29f238c.b59e7"]]},{"id":"b29f238c.b59e7","type":"change","z":"d57cfd26.f61e1","name":"sort","rules":[{"t":"set","p":"payload","pt":"msg","to":"($sort(payload,function($l , $r){$l.timestamp > $r.timestamp }) )","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":650,"y":420,"wires":[["14b85e9b.acdff1"]]},{"id":"14b85e9b.acdff1","type":"function","z":"d57cfd26.f61e1","name":"trace buffer","func":"var arr = msg.payload ;\n\nvar bufferSize = 20;\nif(typeof arr === undefined) {\n return ;\n} else {\n var arrSize = arr.length;\n var minSlice = 0;\n var maxSlice = arrSize;\n if (arrSize > bufferSize) {\n minSlice = arrSize - bufferSize;\n }\n msg.payload = arr.slice(minSlice, maxSlice); \n msg.topic = 'The newest 20 messages :';\n return msg;\n}","outputs":1,"noerr":0,"x":830,"y":380,"wires":[["88c6a602.0cdba8"]]},{"id":"88c6a602.0cdba8","type":"template","z":"d57cfd26.f61e1","name":"css","field":"payload.style","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"table {\n color: #333;\n font-family: Helvetica, Arial, sans-serif;\n width: 100%;\n border-collapse: collapse;\n border-spacing: 0;\n overflow-x: auto;\n overflow-y: auto;\n}\ntd, th {\n border: 1px solid transparent;\n /* No more visible border */\n height: 30px;\n transition: all 0.3s;\n /* Simple transition for hover effect */\n}\nth {\n background: #DFDFDF;\n /* Darken header a bit */\n font-weight: bold;\n}\ntd {\n background: #FAFAFA;\n text-align: center;\n}\n\n/* Cells in even rows (2,4,6...) are one color */\n\ntr:nth-child(even) td {\n background: #F1F1F1;\n}\n\n/* Cells in odd rows (1,3,5...) are another (excludes header cells) */\n\ntr:nth-child(odd) td {\n background: #FEFEFE;\n}\ntr td:hover {\n background: #666;\n color: #FFF;\n}\n\n/* Hover cell effect! */","output":"str","x":970,"y":380,"wires":[["fb4adece.6d85b"]]},{"id":"fb4adece.6d85b","type":"template","z":"d57cfd26.f61e1","name":"html","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<head>\n <style>\n {{{payload.style}}}\n </style>\n</head>\n\n\n<table id=\"trace-table\" border=\"1\">\n \n \n <thead>\n <tr>\n <th colspan=\"3\">{{topic}}</th>\n </tr>\n </thead>\n \n \n <tr>\n <th>TimeStamp</th>\n <th>Trace</th>\n <th>Level</th>\n\n </tr>\n {{#payload}}\n <tr class=\"\">\n <td>{{timestamp}}</td> \n <td>{{t}}</td>\n <td>{{l}}</td>\n </tr>\n {{/payload}}\n</table>\n","output":"str","x":830,"y":480,"wires":[["5654ee4c.5b0a"]]},{"id":"5654ee4c.5b0a","type":"ui_template","z":"d57cfd26.f61e1","group":"cc45e554.8ef688","name":"Scrolling Traces","order":0,"width":"12","height":"8","format":"<div ng-bind-html=\"msg.payload\" id=\"div-table\" height=\"500\" ></div>\n\n<script>\n \n (function(scope) {\n scope.$watch('msg.payload', function(data) {\n var elmt = document.getElementById(\"div-table\");\n console.log(elmt);\n // this one is null... why?\n var elmtTable = document.getElementById(\"trace-table\");\n console.log(elmtTable);\n var nrCh = elmt.children.length;\n console.log(\"nrCh: \" + nrCh);\n if (nrCh > 0) {\n // the only div child is the table !\n var elmtChild = elmt.children[0];\n console.log(elmtChild);\n elmtChild.scrollIntoView(false);\n console.log('scroll to bottom');\n }\n });\n })(scope);\n\n</script>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":1020,"y":480,"wires":[[]]},{"id":"cc45e554.8ef688","type":"ui_group","z":"","name":"Test Traces","tab":"2f7c01d7.51b69e","order":2,"disp":true,"width":"12","collapse":false},{"id":"2f7c01d7.51b69e","type":"ui_tab","z":"","name":"Demo Html Scrollable Table","icon":"dashboard","disabled":false,"hidden":false}]