Um dem Chrome-Browser auf einem Raspberry mit Bildschirm die Funktionalitäten für history.back() (eine Seite zurück) und history.go() (auf die Hauptseite springen) in einem VIS-Dashboard zu entlocken, musste ich dann doch etwas kämpfen. Falls jemand die gleichen Funktionalitäten benötigt, seien hier meine Erkenntnisse dargestellt 
Zur Ausgangssituation: ich verwende als Ersatz für eine in die Jahre gekommene Wetterstation mit Außen- und Innenthermometer einen Raspberry Pi3 ModelB mit einem kleinen HDMI-Bildschirm 1024*600. Auf dem Raspi läuft ein Raspbian Bullseye und es wird ein Chromium-browser verwendet, der per Autostart im Kiosk und Incognito Modus gestartet wird. Da keine Tastatur angeschlossen ist, kann der Browser nur im Touch-Modus verwendet werden.
Der Start-Bildschirm ist bei uns ziemlich aufgeräumt

und stellt nur das zur Verfügung, was auf den ersten Blick erforderlich ist. Sollte ja auch nur ein Ersatz für eine Wetterstation sein
. Alle weiteren Seiten, sind per Touch über Datum, Kalender, Wetter usw. erreichbar. Um den Browser auch per Home- und Back-Button zu steuern, wurden in der Statuszeile zwei besondere Button eingebettet. Und genau um diese beiden Button geht es hier.
Die Browser-Steuerung findet auf dem Client statt und daher wird Javascript hier auch Client-seitig verwendet. D.h. also kein Javascript oder Blockly im Skripte-Verzeichnis auf dem ioBroker, da das die Server-Seite anspricht. Das Skript, um das es hier geht, wird unter dem Skripte-Reiter im VIS-Editor erstellt.
Wenn ich hier von VIS spreche ist schon vis-2 gemeint, da es für mich keinen Sinn macht eine neue Anwendung unter dem "Auslaufmodell" vis-1 zu erstellen.
Das im Editor erstellte Skript wird in der Anwendung unter
<script id="vis_user_scripts">...</script> im <head>..</head>
Bereich eingebunden. Dieser Aspekt ist wichtig für die weitere Betrachtung, da
- beim erstmaligen Laden der Webseite zunächst alle Scripte im <head>-Bereich ausgeführt werden und erst dann das DOM (Document Object Model) des <body>-Bereiches erstellt wird,
- bei einem Seitenwechsel nur das DOM im <body>-Bereich neu aufgebaut und der <head>-Bereich nicht angetastet wird.
Der erste Aspekt hat zur Folge, dass bei der Ausführung eines solchen Skripts noch kein DOM zur Verfügung steht. Normalerweise würde man hier die Funktion
window.addEventListener('DOMContentLoaded', ...)
einsetzen, um darauf zu warten, dass das DOM erstellt wurde. Die kommt aber offensichtlich schon zum Einsatz und stand an dieser Stelle nicht mehr zur Verfügung. Die Lösung ist, mit der Skipt-Ausführung eine gewisse Zeit zu warten, bis davon ausgegangen werden kann, dass das DOM aufgebaut wurde. Eine setTimeout() Funktion wurde an einigen Stellen hier im Forum schon erörtert. Ich habe die Lösung von @RKCCORIAN als Grundlage genommen, die im Script dieses Beitrags https://forum.iobroker.net/topic/49776/jarvis-v3-0-0-just-another-remarkable-vis/2137 als Funktion waitForElement() verwendet wird. Zudem wird nicht allein mit einem Timeout operiert, sondern auch mit der Prüfung, ob ein angegebenes Element bereits in das DOM aufgenommen wurde.
Mit der Funktion waitForElement() wartet das also Script darauf, dass die Button, zumindest einer der Button, im DOM zur Verfügung stehen und weist diesen die Funktion click() zu:
// Click für den Back-Button
$('#w000320').click(function () {
if(history.length > 1) {
history.back();
}
});
if(debug) console.log("Click für history_back etabliert.");
// Click für den Home-Button
$('#w000321').click(function () {
let length = history.length;
if (length > 1) {
length = (length - 1) * -1;
history.go(length);
}
});
if(debug) console.log("Click für history_go etabliert.");
Das Problem für diese Lösung ist allerdings der oben genannte Aspekt zwei, da
- der Neuaufbau des DOM zur Folge hat, dass alle erfolgten Funktions-Zuweisungen verloren gehen und
- das Skript nicht erneut startet, da der <head>-Bereich nicht angetastet wird.
Die Lösung hierfür ist das standardmäßig vorhandene Objekt MutationObserver, dass darauf reagiert, wenn ein zu überwachendes DOM Element geändert wird. Zu beachten ist auch hier, dass eine Element-Überwachung verloren geht, wenn das Element gelöscht wird, was bei einem Seitenwechsel ja nun mal der Fall ist. Zum Glück wird das DOM bei VIS nicht vollständig zerstört. Die Node <div id="root">, die gleich unterhalb der Node <body> eingebunden ist, wird bei einem Seitenwechsel nicht geändert.
Daher kann die Überwachung auf dieser Node eingesetzt werden
// observe direct children
observer.observe(document.getElementById("root"), {
childList: true, // observe direct children
attributes: false, // no attributes
subtree: false, // no lower descendants
characterData: false // no data
});
und auf alle Änderungen reagieren, die sich auf die Kinder dieser Node beziehen. Das vollständige Skript sieht nun so aus:
var debug = false;
function waitForElement(parent, elementPath, callBack, counter = 0, debug = false) {
if (counter < 10000) {
setTimeout(function () {
if (parent.find(elementPath).length > 0) {
if (debug) console.log(`it took ${counter}ms to wait for the element '${elementPath}'`);
callBack();
} else {
if (debug) console.log(`wait for element '${elementPath}'`);
counter++;
waitForElement(parent, elementPath, callBack, counter, debug);
}
}, 1);
} else {
console.warn(`stop waiting after ${counter} retries`);
callBack();
}
}
waitForElement($('body'),'#w000320', function () {
let observer = new MutationObserver(mutationRecords => {
for (let mutation of mutationRecords) {
if(mutation.addedNodes.length > 0) {
waitForElement($('body'),'#w000320', function () {
// Click für den Back-Button
$('#w000320').click(function () {
if(history.length > 1) {
history.back();
}
});
if(debug) console.log("Click für history_back etabliert.");
// Click für den Home-Button
$('#w000321').click(function () {
let length = history.length;
if (length > 1) {
length = (length - 1) * -1;
history.go(length);
}
});
if(debug) console.log("Click für history_go etabliert.");
}, 0, debug);
}
}
if (debug) console.log(mutationRecords); // console.log(the changes)
});
// observe direct children
observer.observe(document.getElementById("root"), {
childList: true, // observe direct children
attributes: false, // no attributes
subtree: false, // no lower descendants
characterData: false // no data
});
}, 0, debug);
Hinweis: Das Skript erstellt die click() Zuweisungen erst bei der ersten Änderung des DOM. Bis dahin sind die beiden Button funktionslos, da auf der Home-Seite weder eine Back-Funktion noch eine Home-Funktion Sinn macht.
Hinweis zum Abschluss: Ich binde die Statuszeile per "Basic-View in Widget" im View ein. Die Statuszeile ist auf allen Seiten die Gleiche. Daher ändern sich die ID's der beiden Browser-Button nicht. Bei den Button handelt es sich um "Basic-HTML Navigation" Widgets, bei denen der "View zum Navigieren"-Eintrag leer ist. Es lassen sich hier natürlich auch andere Widgets verwenden.
Ich hoffe, ich hab nichts vergessen.