/** Bietet ein Hauptinterface für alle Navigationselemente unter `navbar`. * * Hat Implementierungen/Funktionen um alle Nodes anzusprechen. Siehe `NavbarNode` für genauere Beschreibung. * * @param {Array(NavbarNode/Object)} nodes --- Alle Elemente die verwaltet werden sollen. */ class NavbarCoreIndex{ nodes = []; // Nodes bezieht sich auf die Navigationselemente welche sich unterordnen nodesNeverHighlight = false; // Wenn true werden highlightText aufrufe niemals ausgeführt constructor(...nodes){ // Schaut ob jede Node schon ein NavbarNode ist, konvertiert ggf. Js-Objekte welche es noch nicht sind for(const rawNode of nodes){ let node = rawNode; if(typeof node !== "NavbarNode"){ node = new NavbarNode( // Erstellt neues NavbarNode aus Daten (die Werte haben für den Fall Ausweichwerte) rawNode["path"], rawNode["name"], rawNode["dbname"], rawNode["url"], rawNode["nodes"] ) } this.nodes.push(node); } //this.sort(); // (Sortierung passiert jetzt Serverseitig) } get hasNodes(){ return this.nodes.length != 0; } get allNodes(){ let all = []; for(const node of this.nodes) all = [...all, node, ...node.allNodes]; // Nodes von allen nodes hinzufügen return all; } get allNodesVisible(){ let all = []; for(const node of this.nodes){ if(node.selfHighlight || node.nodesHighlight){ all = [...all, node, ...node.allNodesVisible]; // Nodes von allen nodes hinzufügen } } return all; } /** Sortiert alphabetisch. __Diese Funktion hat derzeit keine Verwendung__ */ sort(){ this.nodes.sort((a, b) => { // Überschreiben die normale Sortierfunktion, damit nach .name geschaut wird const nameA = a.name; const nameB = b.name; if(nameA < nameB) return -1; if(nameA > nameB) return 1; return 0; } ) } /** `firstnode` (objekt ref) wird an den Anfang der Nodes gestellt. __Diese Funktion hat derzeit keine Verwendung__ */ putNodeFirst(firstnode){ this.nodes = [firstnode, ...this.nodes.filter(node => node != firstnode)]; } /** `lastnode` (objekt ref) wird ans Ende gestellt. __Diese Funktion hat derzeit keine Verwendung__ */ putNodeLast(lastnode){ this.nodes = [...this.nodes.filter(node => node != lastnode), lastnode]; } /** Hängt das html von allen `.htmlMakeElement` in dieses Element */ sendNodeHtml(target){ for(const node of this.nodes) target.appendChild(node.htmlMakeElement()); } /** Implementierung der Suche */ highlightText(regex, inAttributes, hideIfNot=true){ if(this.nodesNeverHighlight) return false; let nodesHighlight = false; // Vorhanden in den namen der Nodes const inst = this.fullpath; for(const node of this.nodes) nodesHighlight = node.highlightText(regex, inAttributes, hideIfNot) || nodesHighlight; // Alle nodes highlighten, und merken ob welche ge-highlighted return nodesHighlight; } highlightReset(){ for(const node of this.nodes) node.highlightReset(); } toString(){ // Besseres debuging return `NavbarCoreIndex`; } } /** Bietet ein Interface für die Navigationselemente unter `navbar`. * Node bezieht sich auf die Navigationselemente welche sich jeweils unterordnen, sie werden überall mal stellenweise Ordner oder Einträge (Entrys) gennant. * * Wärend der instanzierung wird anhand der Url erkennt ob dieses Element geöffnet sein soll (`.opened`), und ob dieses Element gerade von der aktiven Seite ist (`.current`). * * Die Html-Elemente werden von der Klasse erzeugt über die Funktion `.htmlMakeElement()` als Rückgabewert. * Diese Elemente sind so aufgebaut und als eigene Attribute registriert: * * ```html *
*
  • * * *
  • *
    *
    * ``` * * Die Suchfunktion realisiert sich über `highlightText(...)`, welches das erstellte Element ausblendet/öffnet/highlightet nach den Parametern die in der Funktion übergeben wurden. * `highlightText()` kann dann auch denn selben Vorgang für alle eigenen Nodes ausführen, sodas durch denn kaskadierenden Effekt eine Suchfunktion realisiert wird. * * @param {string} fullpath --- Der Zope-Pfad der zu diesem Ordner führt (zb `Intranet/Kundenauftragsinfo`), wird in der Url-erkennung benutzt. * @param {string} name --- Der Name, welcher auch in der Leiste angezeigt wird. * @param {string} dbname --- (Wenn vorhanden) der Name der Tabelle in der Datenbank, welche hier dargestellt wird. * @param {string} url --- Welche Seite aufgerufen werden soll wenn man auf dass Element klickt. * @param {Array(NavbarNode/Object)} nodes --- Alle Unterordner (wenn sie ein js-Obj sind werden sie als NavbarNode instanziert gespeichert). */ class NavbarNode extends NavbarCoreIndex{ opened = true; // Wenn der Unterordner hat, ob sie gerade zu sehen sein sollen current = false; // Ob gerade die Seite von diesem Eintrag aufgerufen wird. selfHighlight = false; // Ob gerade in der Suchansicht relevant nodesHighlight = false; // Ob eine der Nodes in der Suchansicht relevant constructor(fullpath, name="", dbname="", url="", nodes=[]){ super(...nodes); // Weiterleitung zur Auswertung der Nodes this.path = fullpath.split("/"); this.name = name; this.dbname = dbname; this.url = url; this.htmlElement = undefined; // Das eigentliche Element selbst this.htmlContainer = undefined; // Hier sollen die Elemente der Untereren rein kommen this.htmlTitle = undefined; // Hier ist der Text drinne this.htmlArrow = undefined; // Das ist der Blaue Pfeil, bzw Punkt this.current = this.isSiteUrl; this.opened = this.currentInNodes || (this.current && this.hasNodes); } toString(){ // Besseres debuging return "[NavbarNode for \"" + this.systemname + "\"]"; } get isSiteUrl(){ // Ob die aktuelle Seite auf diesem Element ist (vorher dadrauf geklickt wurde) // Der relevante Teil der url von der Seite und des Elements werden auf übereinstimmung getestet // Der relevante Teil ist alles ohne Domain und Unterseite ("index_html"). // "/Ven/AV/index_html" --> [ "", "Ven", "AV", "index_html" ] --> [ "Ven", "AV" ] --> "Ven/AV" const pathname = window.location.pathname.split("/").slice(1, -1).join("/"); // "https://www.domain.test/Ven/AV" --> ["https", "", "www.domain.test", "Ven", "AV", "index_html"] --> ["Ven", "AV"] --> "Ven/AV" const mypathname = this.url.split("/").slice(3, -1).join("/"); return pathname == mypathname; } get systemname(){ return this.path[this.path.length -1]; } get fullpath(){ return this.path.join("/"); } get navtype(){ // Ob Liste zum erweitern oder Menupunkt if(this.hasNodes) return "navlist"; else return "naventry"; } /** Ob eine Node darunter ist, welche current ist */ get currentInNodes(){ for(const node of this.nodes) if(node.currentInNodes || node.current) return true; return false; } get htmlContainerFilled(){ return this.htmlContainer.children.length != 0; } /** Erstellt und registriert ein eigenes html-Element, und gibt dieses zurück */ htmlMakeElement(){ // Element const element = document.createElement("div"); element.classList.add("bullet", this.navtype, this.systemname.replaceAll(" ", "_")); // damit Klasse keine Leerzeichen hat if(this.opened) element.classList.add("opened"); // So wird erstaml der Status gesetzt if(this.current) element.classList.add("current"); if(this.currentInNodes) element.classList.add("hascurrent"); // .hascurrent Damit man naher dass andere aktive wiederfindet wenn man dieses hier schließst element.setAttribute("title", this.name); // Titel const head = document.createElement("li"); head.classList.add("bullet_title"); head.addEventListener("click", this.mouseEventhandler.bind(this)); // Wenn man draufklickt head.addEventListener("contextmenu", this.mouseEventhandler.bind(this)); element.appendChild(head); // Pfeil (oder punkt) const arrow = document.createElement("span"); arrow.classList.add("bullet_arrow_placeholder"); if(this.hasNodes) arrow.addEventListener("click", this.mouseEventhandlerArrow.bind(this)); // Damit man die Listen aufmachen kann head.appendChild(arrow); // Titel Text const title = document.createElement("span"); title.innerHTML = this.name; head.appendChild(title); // Container element.appendChild(this.htmlMakeContainer(this.opened)); // Unterordner werden nur ondemand erstellt // Anmelden this.htmlArrow = arrow; this.htmlTitle = title; this.htmlElement = element; this.updateArrowState(); // Damit der Pfeil in die richtige Richtung zeigt return element; } /** Erstellt und registriert einen eigenen Container für das html-Element, und gibt diesen zurück */ htmlMakeContainer(fill=true){ const container = document.createElement("div"); container.classList.add("bullet_container"); this.htmlContainer = container; if(fill) this.htmlFillContainer(); return container; } /** Befüllt den registrierten Container mit dem html der Nodes */ htmlFillContainer(){ if(this.htmlContainer) this.sendNodeHtml(this.htmlContainer); } /** Soll die Suchfunktion ersetzten * Zurücksetzen möglich mit `.highlightReset()` * @param {RegExp/string} regex --- Das soll im titel ge-highlighted werden * @param {Array(string)} inAttributes --- Testet das Regex an diesem Attribut von sich selbst (Dafür muss das eigene Attribut auch immer string sein zb "name", "dbname", "fullpath" oder "systemname") * @param {bool} hideIfNot --- Wenn nichts ge-highlighted wird unsichbar machen * @param {bool} includeNodes --- Ob diese funktion bei allen Nodes angewand werden soll * @returns {bool} --- Ob was ge-highlighted wird (auch wenn nur in den Nodes) */ highlightText(regex, inAttributes=["name"], hideIfNot=true, includeNodes=true){ if(typeof regex == "string") regex = new RegExp(regex); // Falls regex string ist (noch kein regex obj) if(!this.htmlContainerFilled) this.htmlFillContainer(); // Container mit nodes füllen, für highlight brauchen alle ein html element let matchingTestval = ""; let matchingAttribute = ""; this.selfHighlight = false; // Vorhanden im eigenen Attribut this.nodesHighlight = false; // Vorhanden in den Attribut der Nodes for(const attribute of inAttributes){ const testval = this[attribute]; if(regex.test(testval) && !this.selfHighlight){ this.selfHighlight = true; matchingTestval = testval; matchingAttribute = attribute; } } if(this.selfHighlight){ // Wenn das Attribut nicht der Name ist (also normal nicht im Titel sein sollte) wird der Titel mit dem namen und den Attribut in Klammern modifiziert: Name (Attribut_hier) if(matchingAttribute=="name"){ this.htmlTitle.innerHTML = this.name.replace(regex, "$&"); } else{ this.htmlTitle.innerHTML = `${this.name} (${matchingTestval.replace(regex, "$&")})`; this.htmlTitle.setAttribute("title", `${this.name} (${matchingTestval})`); // So steht es sicherhaltshalber nochmal wenn man mit der Maus drübergeht } this.htmlElement.classList.add("bullet_highlight"); } // Test für alle Nodes (Aus NavbarCoreIndex) if(includeNodes) this.nodesHighlight = super.highlightText(regex, inAttributes, hideIfNot); // Sichtbarkeit wenn nichts ge-highlight (hideIfNot) if(!(this.selfHighlight || this.nodesHighlight) && hideIfNot) this.htmlElement.classList.add("bullethidden"); else this.htmlElement.classList.remove("bullethidden"); // Container schließen wenn keine Node dabei ist (und umgekehrt) if(this.nodesHighlight) this.htmlElement.classList.add("opened"); else this.htmlElement.classList.remove("opened"); return this.selfHighlight || this.nodesHighlight; } highlightReset(includeNodes=true){ if(!this.htmlContainerFilled) this.htmlFillContainer(); // Container mit nodes füllen, für highlight brauchen alle ein html element this.htmlTitle.innerHTML = this.name; this.htmlElement.classList.remove("bullethidden"); this.htmlElement.classList.remove("bullet_highlight"); this.htmlElement.setAttribute("title", this.name); if(this.opened) this.htmlElement.classList.add("opened"); // Offenheit wie es vorher war else this.htmlElement.classList.remove("opened"); if(includeNodes){ super.highlightReset(); // Reset für alle Nodes (Aus NavbarCoreIndex) this.nodesHighlight = false; } this.selfHighlight = false; } /** Box auf oder zu */ htmlSetContainer(opened=null){ this.opened = ((opened==null) ? !this.opened : opened); // Wenn nicht gesetzt, dann gegenteil von .opened (toogle) if(this.opened) this.htmlElement.classList.add("opened"); else this.htmlElement.classList.remove("opened"); if(this.opened && !this.htmlContainerFilled) this.htmlFillContainer(); // Wenn offen: Container mit nodes füllen, falls nicht schon passiert this.updateArrowState(); } /** Ersatz der Attribut eventhandler (`onclick` und `oncontextmenu`) */ mouseEventhandler(event){ this.openMyUrl(event.type=="contextmenu"); // Wenn Rechtsklick, dann ist newtab true event.stopPropagation(); event.preventDefault(); } /** Damit man auf denn Pfeil drücken kann, und sich der Inhalt offenbart */ mouseEventhandlerArrow(event){ // Wenn in der Suche geöffnet, wird ein Klick an die normale Klickfunktion weitergeleitet, // Sonst wird be einem Klick der Inhalt ganz normal geöffnet if(!this.nodesHighlight && !this.selfHighlight) this.htmlSetContainer(); else this.mouseEventhandler(event); event.preventDefault(); event.stopPropagation(); } openMyUrl(newtab=false){ if(newtab) window.open(this.url, "_blank"); else window.location.href=this.url; } /** Damit passt man den Pfeil an */ updateArrowState(opened=this.opened){ if(!this.hasNodes) this.htmlArrow.innerHTML = ``; // Hat sowieso keine nodes, ist also immer ein Punkt else if(opened) this.htmlArrow.innerHTML = ``; // Offen, Pfeil nach unten else this.htmlArrow.innerHTML = ``; // Geschlossen, Pfeil nach rechts } } /** Nimmt versehentlichen Regex syntax raus */ function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /* ####### IMPLEMENTIERUNG AB HIER ####### Alle globalen Funktions- und Variabelennamen benutzen ab hier die Präfixe `navbar` oder `navbar_` um Kollisionen mit bereits vorhandenen Namen zu vermeiden. NavbarIndex ist eine Instanz von NavbarCoreIndex welches die navbar Elemente mit Instanzen der eigenen Unterklasse NavbarNode verwaltet. NavbarCoreIndex wird mit dem umgewandelten json index von `layout_venjakob_folders` gefüttert und sollte alles weitere automatisch initalisieren. Die erstellung und registrierung der html-Elemente wird über `navbarIndex.sendNodeHtml(navbarDefaultContentElement)` gemacht wobei `navbarDefaultContentElement` das exsistierende html element ist, dass mit den Nav-Elementen gefüllt wird. Um die Ladezeiten zu verkürzen wird der von `layout_venjakob_folders` erstellte Index in den LocalStorage geladen. Der Index soll alle 24 Stunden, nach einem Login/Logout oder nach einem Ladefehler neu geholt werden. */ // (in Millisekunden) wie lange ein index halten soll (1000*60*60*24 = 24 Stunden, 0 = Würde Index effektiv deaktivieren) const navbarIndex_validtimeframe = 1000*60*60*24; // Falls sich was im Ausgabeformat von `layout_venjakob_folders` geändert hat hier die aktuelle Zeit eintragen. // Wird einen neuen Index holen wenn dieser Älter als dieser Zeitstempel ist, egal ob er noch in _validtimeframe const navbarIndex_expireAllwaysAt = Number(new Date("2024-08-23 10:13:10")); //window.addEventListener("load", function() {}) async function navbar_init(){ globalThis.navbarIndex = null; // Wird als json string im local storage unter unter "navbar_index" gespeichert globalThis.navbarSearchBlock = true; // Wenn gerade Geladet wird globalThis.navbarSearchInputElement = document.getElementById("navbar_search_input"); globalThis.navbarDefaultContentElement = document.getElementById("navbar_default_content"); globalThis.navbarSearchCancelElement = document.getElementById("navbar_search_cancel"); globalThis.navbarSearchHintsElement = document.getElementById("navbar_search_hint"); globalThis.navbarRefreshElement = document.getElementById("navbar_refresh"); // Erkennung ob unangemeldet // Schaut ob im dom ein Element mit dem Benutzernamen von Zope erzeugt wurde. // Erkennung des "__user_name" Cookies währe eine bessere Methode der Erkennung, ist aber leider "httponly". globalThis.isNotLoggedIn = document.querySelectorAll("#logon .user").length == 0; // Voreinstellungen für unangemeldete Nutzer if(globalThis.isNotLoggedIn){ navbar_killIndex(); // Damit der Index aus dem angemeldeten Zustand nicht auftaucht document.querySelectorAll(".navbar_search_box")[0].classList.add("noheight", "transparent"); // Suchleiste verstecken } await navbar_loadIndex(); // Löscht den durch navbar_loadIndex() gespeicherten Index wieder (im unangemeldeten Zustand), damit nach dem einloggen ein neuer Index bezogen wird. // Da der Index für unangemeldete nur 5ms dauert ist es auch akzeptabel ihn wiederholt anzufragen. // Die bessere Alternative währe es sich im localStorage auch zu vermerken dass dieser Index für logout bestimmt ist, und ihn nach der anmeldung neu anzufragen. // Aber es währe besser zu warten ob sich insgesamt eine bessere Möglichkeit ergibt das zu Verwalten. if(globalThis.isNotLoggedIn) navbar_killIndex(); // Nach dem Laden freigeben globalThis.navbarSearchInputElement.removeAttribute("disabled"); } /** Ladet den index (wenn nicht schon geladen) */ async function navbar_loadIndex(){ // Schauen ob der gespeicherte index vorhanden und valide ist const navbarIndex = localStorage.getItem("navbar_index"); const navbarIndex_recived = Number(localStorage.getItem("navbar_index_recived")); // Hinweiß: Wegen Number() wird wenn nicht vorhanden wird 0 sein und nicht `null` const currentTime = Number(new Date()); const timepassed = currentTime - navbarIndex_recived; if( navbarIndex != null && // Ist überhaupt ein Index vorhanden? timepassed < navbarIndex_validtimeframe && // Ist dieser Index im Zeitfenster? Number(navbarIndex_recived) > navbarIndex_expireAllwaysAt // Ist ein Zeitpunkt erreicht wo `layout_venjakob_folders` was anderes herausgibt? ){ try{ navbar_load(JSON.parse(navbarIndex)); // Aus der Cache ziehen return; }catch{ console.log("Erzwinge Index Update"); navbar_killIndex(); // Wenn veraltet, kann nicht laden } } // Neuen index ziehen try{ const data = await fetch("layout_venjakob_folders", {cache: "no-cache"}); // Index daten laden const json = await data.json(); // Index daten umwandeln (json umwandlung ist auch eine auch asynchrone operation) navbar_load(json); localStorage.setItem("navbar_index_recived", Number(new Date()) ); // Wann es Zeit wird für eine neuen index localStorage.setItem("navbar_index", JSON.stringify(json)); // Speichern für diese Sitzung }catch(exeption){ console.log(exeption); console.log("Fehler beim beziehen des Searchtool-pools"); } } /** Setzt denn eintrag in der cache zurück */ function navbar_killIndex(){ localStorage.removeItem("navbar_index_recived"); localStorage.removeItem("navbar_index"); } function navbar_load(data){ // Index laden navbarIndex = new NavbarCoreIndex(...data); // In die Navbar einsetzen navbarDefaultContentElement.innerHTML = ""; navbarIndex.sendNodeHtml(navbarDefaultContentElement); navbarSearchBlock = false; // Jetzt darf gesucht werden // Damit das Lesezeichen wie gewohnt aussieht const lesezeichen = navbarIndex.nodes.find((node) => node.systemname=="Lesezeichen"); if(lesezeichen){ lesezeichen.nodesNeverHighlight = true; // So wird Suche für alle Unterelemente von Lesezeichen abgeschaltet, der Eintrag Lesezeichen an sich kann aber immer noch gesucht werden lesezeichen.htmlSetContainer(true); // Damit die erste ebene vom Lesezeichen immer offen ist. } } /** Startet die Suche mit query */ function navbar_search(query){ navbarIndex.highlightReset(); if(query == ""){ // "" um die Suche abzuschalten navbarSearchInputElement.value = ""; navbarSearchCancelElement.classList.add("transparent"); navbarDefaultContentElement.classList.remove("searchmode"); return; } query = query.trim(); // Versehentliche Leerschritte und regex syntax wegmachen query = escapeRegExp(query); navbarSearchHintsElement.innerHTML = "Suche..."; navbarSearchCancelElement.classList.remove("transparent"); navbarDefaultContentElement.classList.add("searchmode"); navbarSearchBlock = true; if(navbarIndex != null){ if(query.length < 3){ // Zu umfangreiche suche (aber die searchbank ist schon geladen, was die nächste Eingabe schneller macht) navbarSearchHintsElement.innerHTML = "Mehr als 2 Zeichen benutzen"; }else{ let results = navbarIndex.highlightText(new RegExp(query, "i"), ["name","dbname"]); // new RegExp(query, "i") "i" um groß/klein zu ignorieren //if(!results) results = navbarIndex.highlightText(new RegExp("^"+query+"$", "i"), "dbname"); // Wenn nichts gefunden, wird geschaut ob der Datenbank name exakt irgentwo vorhanden ist if(!results){ navbarSearchHintsElement.innerHTML = "Nichts gefunden"; navbarIndex.highlightReset(); } else{ navbarSearchHintsElement.innerHTML = ""; } } }else{ navbarSearchHintsElement.innerHTML = "Fehler beim Suchen"; } // Erkennen ob nichts gefunden navbarSearchBlock = false; } /** Damit der X Button funktioniert */ function navbar_cancelClick(){ if(navbarSearchCancelElement.classList.contains("transparent")){ return; }else{ navbar_search(""); // Suche ausschalten navbar_removeArrowIndex(); } } /** Den Gespeicherten Suchindex aufrischen (für ↻ Button) */ function navbar_refresh(){ try{ // Im try da bei einem kaputten Index die Suchfunktion nicht richtig resettet werden kann navbar_search(""); // Suche resetten }catch(exeption){ console.log("Alter Suchindex beschädigt") } navbarSearchBlock = true; // Verhindert dass beim laden noch gesucht wird, wird von navbar_loadIndex() wieder freigeschaltet navbarRefreshElement.setAttribute("disabled", "true"); navbarDefaultContentElement.innerHTML = ``; // Platzhalter bis laden fertig ist navbar_killIndex(); navbar_loadIndex(); } let navbar_arrowNavigationIndex = 0; /** Duch die Eingabe in der Suchleiste, steuert dann den Suchaufruf. */ function navbar_searchKeyPress(event){ event.stopPropagation(); //navbarSearchHintsElement.innerHTML = "Suche mit Enter starten"; // Falls da noch eine Fehlermeldung steht wird die wieder hiermit überschrieben let workingList = navbarIndex.allNodesVisible; if(event.code == "Enter") { // Suche starten via Enter if(navbar_arrowNavigationIndex == 0){ // Wenn nicht mit den Pfeiltasten aus der Suchleiste raus if(navbarSearchBlock){ // Verhindern das parallel aufgerufen wird event.preventDefault(); return; } navbar_search(navbarSearchInputElement.value); }else{ workingList[navbar_arrowNavigationIndex - 1].openMyUrl(event.ctrlKey); return; } } else if(event.code == "Escape"){ // Suche und Pfeilauswahl beenden via Escape navbarSearchInputElement.blur(); navbar_search(""); navbar_endArrowNavigation(); } else if(event.code == "ArrowDown" || event.code == "ArrowUp" ){ // Navigation mit Pfeiltasten if(event.code == "ArrowDown"){ navbar_arrowNavigationIndex += 1; }else if(event.code == "ArrowUp"){ navbar_arrowNavigationIndex -= 1; } navbar_removeArrowIndex(); if(navbar_arrowNavigationIndex < 0){ navbar_arrowNavigationIndex = workingList.length; }else if(navbar_arrowNavigationIndex > workingList.length){ navbar_arrowNavigationIndex = 0; } if(navbar_arrowNavigationIndex != 0){ navbarSearchInputElement.parentNode.classList.add("fakeBlur"); const nodeAtIndex = workingList[navbar_arrowNavigationIndex - 1] nodeAtIndex.htmlElement.classList.add("fakeFocus"); // Logik ob gescrollt werden soll const distanceToTop = nodeAtIndex.htmlTitle.getBoundingClientRect().top; const saveAreaHeight = 60; // Soviel soll zusätzlich gescrollt werden //console.log(distanceToTop, window.innerHeight, workingList[navbar_arrowNavigationIndex - 1].name); if(distanceToTop + saveAreaHeight >= window.innerHeight){ window.scrollTo(0, window.scrollY + (distanceToTop + saveAreaHeight) - window.innerHeight); }else if(distanceToTop < saveAreaHeight){ window.scrollTo(0, window.scrollY + (distanceToTop - saveAreaHeight)); } }else{ navbarSearchInputElement.parentNode.classList.remove("fakeBlur"); window.scrollTo(0,0); } event.preventDefault(); }else if( event.ctrlKey || event.altKey || event.shiftKey){ // Beim halten dieser Tasten sollte nicht weiter passieren, ist sonst etwas irritierend return; }else{ // Jeder andere Kopf beendet dann die Auswahl über die Pfeiltasten navbar_endArrowNavigation(); } // ******* Hier könnte man eine Navigation mit den Pfeiltasten bauen ******* } function navbar_endArrowNavigation(){ navbar_arrowNavigationIndex = 0; navbar_removeArrowIndex(); window.scrollTo(0,0); } /** Makierung für dass durch die Pfeiltasten auserwählte Element entfernen. */ function navbar_removeArrowIndex(){ const oldArrow = document.querySelector(".fakeFocus"); if(oldArrow){ oldArrow.classList.remove("fakeFocus"); } navbarSearchInputElement.parentNode.classList.remove("fakeBlur"); } // Für die Anzeigen function navbar_showHint(){ navbarSearchHintsElement.innerHTML = ""; navbarSearchHintsElement.classList.remove("transparent"); } function navbar_hideHint(){ navbarSearchHintsElement.classList.add("transparent"); navbarSearchHintsElement.innerHTML = ""; }