Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Visualisierung
  4. Vis2 Tabs Widget - Tab per URL anspringen

NEWS

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    17
    1
    2.9k

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    1.1k

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    25
    1
    2.4k

Vis2 Tabs Widget - Tab per URL anspringen

Geplant Angeheftet Gesperrt Verschoben Visualisierung
9 Beiträge 2 Kommentatoren 67 Aufrufe 3 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • S Offline
    S Offline
    Sesamstrasse
    schrieb am zuletzt editiert von
    #1

    Hallo zusammen,

    weiß jemand ob es möglich ist in der VIS2, wenn man auf einer Seite das Tabs Widget verwendet, noch explizit ein bestimmtest Tabs beim aufrufen auszuwählen. In der Adressleiste steht nur die URL von der Hauptseite, in der man das Tabs Widget eingebunden hat. Das Widget selbst öffnet immer automatisch in seinem letzten aktiven Tab. Ich würde aber gerne per URL aufruf immer ein bestimmtes Tab selektiert haben, bzw. je nach bedarf von wo der Trigger kommt, ein bestimmtes Tab direkt mit auswählen beim Seitenaufruf.

    Viele Grüße
    Flo

    M 1 Antwort Letzte Antwort
    0
    • S Sesamstrasse

      Hallo zusammen,

      weiß jemand ob es möglich ist in der VIS2, wenn man auf einer Seite das Tabs Widget verwendet, noch explizit ein bestimmtest Tabs beim aufrufen auszuwählen. In der Adressleiste steht nur die URL von der Hauptseite, in der man das Tabs Widget eingebunden hat. Das Widget selbst öffnet immer automatisch in seinem letzten aktiven Tab. Ich würde aber gerne per URL aufruf immer ein bestimmtes Tab selektiert haben, bzw. je nach bedarf von wo der Trigger kommt, ein bestimmtes Tab direkt mit auswählen beim Seitenaufruf.

      Viele Grüße
      Flo

      M Offline
      M Offline
      MCU
      schrieb am zuletzt editiert von
      #2

      @Sesamstrasse Meinst du dieses?

      http://<iobroker>:8082/vis-2/?<Projekt>#<ViewName>
      

      NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
      Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

      1 Antwort Letzte Antwort
      0
      • S Offline
        S Offline
        Sesamstrasse
        schrieb am zuletzt editiert von
        #3

        Jain. So würde ich es gerne machen. Nur erzeugt das Tabs Widget oben keine Veränderung an der URL, egal welches der 3 Tabs ich wähle, die URL oben bleibt gleich. Und wenn ich die URL woanders aufrufe, dann ist einfach immer das letztgewählte Tab sofort aktiv. Und das würde ich gerne gezielt steuern, also z.b. gezielt per URL die View Strom mit dem Tab Energiefluss aktiv aufrufen, obwohl davor Details gewählt war. Ich befürchte nur, dass das sehr praktische Tabs Widget in der VIS das nicht hergibt und ich doch "echte" einzelne Seiten bauen muss.

        93c25b9f-7470-4668-aa40-6dd1e0648d06-grafik.png

        M 2 Antworten Letzte Antwort
        0
        • S Sesamstrasse

          Jain. So würde ich es gerne machen. Nur erzeugt das Tabs Widget oben keine Veränderung an der URL, egal welches der 3 Tabs ich wähle, die URL oben bleibt gleich. Und wenn ich die URL woanders aufrufe, dann ist einfach immer das letztgewählte Tab sofort aktiv. Und das würde ich gerne gezielt steuern, also z.b. gezielt per URL die View Strom mit dem Tab Energiefluss aktiv aufrufen, obwohl davor Details gewählt war. Ich befürchte nur, dass das sehr praktische Tabs Widget in der VIS das nicht hergibt und ich doch "echte" einzelne Seiten bauen muss.

          93c25b9f-7470-4668-aa40-6dd1e0648d06-grafik.png

          M Offline
          M Offline
          MCU
          schrieb am zuletzt editiert von MCU
          #4

          @Sesamstrasse Beispiel

          http://192.168.178.150:8083/vis-2/?main&tab=2#datum
          

          In skripte einfügen WidgetID anpassen
          d7d6f60d-a6e4-4f61-87fa-706f3dcb8b01-image.png

          // VIS2: Tab beim Laden per URL wählen
          // URL-Beispiele:
          //   .../vis-2/?tab=Heizung#visview_datum
          //   .../vis-2/?tab=2#visview_datum   (1-basiert: 2 => zweiter Tab)
          
          const TABS_WIDGET_ID = "w000016";      // <- aus deinem DOM
          const PARAM_NAME     = "tab";          // ?tab=Heizung oder ?tab=2
          const MAX_TRIES      = 60;             // ~15s bei 250ms
          const INTERVAL_MS    = 250;
          
          function getUrlParam(name) {
            // Query-String (vor #)
            const sp = new URLSearchParams(window.location.search);
            let v = sp.get(name);
            if (v) return v;
          
            // optional: auch aus Hash (#view?tab=...)
            const hash = window.location.hash || "";
            const qPos = hash.indexOf("?");
            if (qPos !== -1) {
              const hp = new URLSearchParams(hash.slice(qPos + 1));
              v = hp.get(name);
              if (v) return v;
            }
            return null;
          }
          
          function getTabButtons() {
            const root = document.getElementById(TABS_WIDGET_ID);
            if (!root) return [];
            return Array.from(root.querySelectorAll('button[role="tab"]'));
          }
          
          function isSelected(btn) {
            return btn?.getAttribute("aria-selected") === "true" || btn?.classList?.contains("Mui-selected");
          }
          
          function selectTabByIndex(index) {
            const tabs = getTabButtons();
            if (!tabs.length) return false;
            if (index < 0 || index >= tabs.length) return false;
          
            const btn = tabs[index];
            if (isSelected(btn)) return true; // schon aktiv
            btn.click();
            return true;
          }
          
          function selectTabByName(name) {
            const tabs = getTabButtons();
            if (!tabs.length) return false;
          
            const needle = String(name).trim().toLowerCase();
            const btn = tabs.find(b => (b.textContent || "").trim().toLowerCase() === needle);
            if (!btn) return false;
          
            if (isSelected(btn)) return true;
            btn.click();
            return true;
          }
          
          function applyTabFromUrl() {
            const tabParam = getUrlParam(PARAM_NAME);
            if (!tabParam) return;
          
            // Nummer? -> 1-basiert (2 => index 1). "0" bleibt 0.
            if (/^\d+$/.test(tabParam)) {
              const n = parseInt(tabParam, 10);
              const idx = (n === 0) ? 0 : (n - 1);
              selectTabByIndex(idx);
              return;
            }
          
            // sonst Name
            selectTabByName(tabParam);
          }
          
          function waitAndApply() {
            let tries = 0;
            const timer = setInterval(() => {
              tries++;
          
              const tabs = getTabButtons();
              if (tabs.length) {
                applyTabFromUrl();
                clearInterval(timer);
              }
          
              if (tries >= MAX_TRIES) clearInterval(timer);
            }, INTERVAL_MS);
          }
          
          // Beim ersten Laden
          waitAndApply();
          
          // Wenn die App ohne Reload navigiert (z. B. Back/Forward)
          window.addEventListener("popstate", () => waitAndApply());
          window.addEventListener("hashchange", () => waitAndApply());
          
          

          NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
          Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

          1 Antwort Letzte Antwort
          0
          • S Sesamstrasse

            Jain. So würde ich es gerne machen. Nur erzeugt das Tabs Widget oben keine Veränderung an der URL, egal welches der 3 Tabs ich wähle, die URL oben bleibt gleich. Und wenn ich die URL woanders aufrufe, dann ist einfach immer das letztgewählte Tab sofort aktiv. Und das würde ich gerne gezielt steuern, also z.b. gezielt per URL die View Strom mit dem Tab Energiefluss aktiv aufrufen, obwohl davor Details gewählt war. Ich befürchte nur, dass das sehr praktische Tabs Widget in der VIS das nicht hergibt und ich doch "echte" einzelne Seiten bauen muss.

            93c25b9f-7470-4668-aa40-6dd1e0648d06-grafik.png

            M Offline
            M Offline
            MCU
            schrieb am zuletzt editiert von
            #5

            @Sesamstrasse Mit dieser Variante wird die URL bei Tabwechsel neu geschrieben

            // VIS-2: Tabs per URL (?…&tab=) setzen + URL bei Tabwechsel aktualisieren
            const TABS_WIDGET_ID = "w000016";  // deine Tabs-Widget-ID
            const PARAM = "tab";
            
            // true => Back/Forward pro Tab, false => URL nur ersetzen (empfohlen)
            const USE_PUSHSTATE = false;
            
            const OBSERVE_TIMEOUT_MS = 15000;
            
            let suppressUntil = 0;
            function suppress(ms = 350) {
              suppressUntil = Date.now() + ms;
            }
            function isSuppressed() {
              return Date.now() < suppressUntil;
            }
            
            function norm(s) {
              return String(s ?? "").trim().toLowerCase();
            }
            
            // -------- URL: nur Query-Teil (?main&tab=...) --------
            function getTabParamFromSearch() {
              const sp = new URLSearchParams(window.location.search);
              const v = sp.get(PARAM);
              return v || null;
            }
            
            function setTabParamInSearch(tabValue) {
              if (!tabValue) return;
            
              const path = window.location.pathname;
              const hash = window.location.hash || "";
              const raw = window.location.search || ""; // z.B. "?main&tab=Heizung"
            
              // tab ersetzen oder anhängen, OHNE main zu "main=" umzuschreiben
              let next = raw;
            
              if (/[?&]tab=/.test(next)) {
                next = next.replace(/([?&]tab=)[^&]*/i, `$1${encodeURIComponent(tabValue)}`);
              } else {
                next += (next.includes("?") ? "&" : "?") + `tab=${encodeURIComponent(tabValue)}`;
              }
            
              const newUrl = `${path}${next}${hash}`;
            
              // optional: pushState / replaceState wie vorher
              history.replaceState(null, "", newUrl);
            }
            
            
            // -------- Tabs Zugriff --------
            function getTabButtons() {
              const root = document.getElementById(TABS_WIDGET_ID);
              if (!root) return [];
              return Array.from(root.querySelectorAll('button[role="tab"]'));
            }
            
            function isSelected(btn) {
              return (
                btn?.getAttribute("aria-selected") === "true" ||
                btn?.classList?.contains("Mui-selected")
              );
            }
            
            function getSelectedTabName() {
              const tabs = getTabButtons();
              const sel = tabs.find(isSelected);
              return sel ? (sel.textContent || "").trim() : "";
            }
            
            function trySelectTab(tabParam) {
              if (!tabParam) return false;
            
              const tabs = getTabButtons();
              if (!tabs.length) return false;
            
              let target = null;
            
              // Zahl => 1-basiert (tab=1 => erster Tab). tab=0 erlaubt auch ersten Tab.
              if (/^\d+$/.test(tabParam)) {
                const n = parseInt(tabParam, 10);
                const idx = (n === 0) ? 0 : (n - 1);
                target = tabs[idx];
              } else {
                // Name
                const needle = norm(tabParam);
                target = tabs.find(b => norm(b.textContent) === needle);
              }
            
              if (!target) return false;
            
              if (!isSelected(target)) {
                suppress(450);
                target.click();
              }
              return true;
            }
            
            // -------- URL -> Tab (beim Laden / Navigation) --------
            function applyTabFromUrl() {
              if (isSuppressed()) return;
            
              const tabParam = getTabParamFromSearch();
              if (!tabParam) return;
            
              // sofort versuchen
              if (trySelectTab(tabParam)) return;
            
              // warten bis gerendert
              const obs = new MutationObserver(() => {
                if (trySelectTab(tabParam)) obs.disconnect();
              });
            
              obs.observe(document.body, { childList: true, subtree: true });
              setTimeout(() => obs.disconnect(), OBSERVE_TIMEOUT_MS);
            }
            
            // -------- Tab -> URL (bei Tabwechsel) --------
            let lastWritten = "";
            
            function writeUrlFromSelection() {
              if (isSuppressed()) return;
            
              const name = getSelectedTabName();
              if (!name) return;
              if (name === lastWritten) return;
            
              lastWritten = name;
              setTabParamInSearch(name);
            }
            
            function wireTabHandlers() {
              const tabs = getTabButtons();
              if (!tabs.length) return false;
            
              tabs.forEach(btn => {
                if (btn.__vis2TabUrlWired) return;
                btn.__vis2TabUrlWired = true;
            
                const handler = () => setTimeout(writeUrlFromSelection, 0);
            
                btn.addEventListener("click", handler);
                btn.addEventListener("keydown", (e) => {
                  if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") handler();
                });
              });
            
              return true;
            }
            
            function observeTabChanges() {
              const root = document.getElementById(TABS_WIDGET_ID);
              if (!root) return false;
            
              const obs = new MutationObserver(() => {
                wireTabHandlers();
                writeUrlFromSelection();
              });
            
              obs.observe(root, {
                subtree: true,
                childList: true,
                attributes: true,
                attributeFilter: ["aria-selected", "class"],
              });
            
              wireTabHandlers();
              writeUrlFromSelection();
              return true;
            }
            
            // -------- Init --------
            (function init() {
              applyTabFromUrl();
            
              if (!observeTabChanges()) {
                const obs = new MutationObserver(() => {
                  if (observeTabChanges()) obs.disconnect();
                });
                obs.observe(document.body, { childList: true, subtree: true });
                setTimeout(() => obs.disconnect(), OBSERVE_TIMEOUT_MS);
              }
            
              // Wenn du URL manuell änderst / Back-Forward
              window.addEventListener("popstate", applyTabFromUrl);
            })();
            
            

            NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
            Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

            1 Antwort Letzte Antwort
            0
            • S Offline
              S Offline
              Sesamstrasse
              schrieb am zuletzt editiert von
              #6

              ufff... alter falter... jetzt bin ich erstmal beschäftigt, dass zu verstehen :) Vielen Dank für die schnelle Antwort!

              M 1 Antwort Letzte Antwort
              0
              • S Sesamstrasse

                ufff... alter falter... jetzt bin ich erstmal beschäftigt, dass zu verstehen :) Vielen Dank für die schnelle Antwort!

                M Offline
                M Offline
                MCU
                schrieb am zuletzt editiert von MCU
                #7

                @Sesamstrasse Also man kann die Umschaltung auch per DP steuern, so dass man von ioBroker aus auf unterschiedlichen Geräte die Tab festlegen kann.
                Interesse?

                NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
                Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

                1 Antwort Letzte Antwort
                0
                • S Offline
                  S Offline
                  Sesamstrasse
                  schrieb am zuletzt editiert von
                  #8

                  Wenn das einfacher ist, klar. :D dann kann ich mir überlegen, wie ich es am besten mache.

                  M 1 Antwort Letzte Antwort
                  0
                  • S Sesamstrasse

                    Wenn das einfacher ist, klar. :D dann kann ich mir überlegen, wie ich es am besten mache.

                    M Offline
                    M Offline
                    MCU
                    schrieb am zuletzt editiert von MCU
                    #9

                    @Sesamstrasse Einfach ist relativ. DPs anlegen und den Code in Skripte austauschen.

                    1. DPs in ioBroker anlegen mit dem Skript
                      Beispiele für DP setzen sind ausgeklammert, da die Werte angepasst werden müssen

                    // ************************
                    // VIS2TabControl v1.0.0
                    // Copyright ©MCU 
                    // ************************
                    // ioBroker javascript.0 - DPs für VIS2 Tabs anlegen
                    const DP_MAIN       = '0_userdata.0.vis2'
                    const DP_TAB_EVENT  = DP_MAIN + ".tabEvent"; // Client -> ioBroker
                    const DP_TAB_CMD    = DP_MAIN + ".tabCmd";   // ioBroker -> Client
                    const DP_DEVICE_MAP = DP_MAIN + ".deviceMap"; // clientId -> clientName
                    
                    createState(DP_TAB_CMD,   '', {name: 'VIS2 Tab Command',type: 'string', role:'', def: '', read: true, write: true, desc: ''});
                    createState(DP_TAB_EVENT,   '', {name: 'VIS2 Tab Event',type: 'string', role:'', def: '', read: true, write: true, desc: ''});
                    createState(DP_DEVICE_MAP,   '', {name: 'VIS2 Client Map',type: 'string', role:'', def: '', read: true, write: true, desc: ''});
                    
                    // DEVICE_MAP - Aufbau
                    /*
                       {
                           "d787264857bc0d13c393d37336f43dc9": "PC",
                           "d7282dhf83hf832f944mhn4345n345n3": "Tablet"
                       }
                    */
                    
                    // Beispiele
                    /*
                    setState(DP_TAB_CMD, JSON.stringify({
                     cmdId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
                     target: ["d787264857bc0d13c393d37336f43dc9"],
                     view: "datum",
                     tab: "Licht",
                     force: true
                    }), false);
                    
                    */
                    // Wenn eine Zuordnung im Device_MAP vorhanden ist, kann man auch die Name nutzen
                    /*
                    
                    setState(DP_TAB_CMD, JSON.stringify({
                     cmdId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
                     target: ["PC"],
                     view: "datum",
                     tab: 2,
                     force: true
                    }), false);
                    
                    
                    */
                    

                    1. Script in VIS2 unter Skripte austauschen

                    /********************************************************************
                    * VIS-2 Global Script: Tabs steuern + Sync + Multi-Client Commands
                    *
                    * Fixes:
                    *  - Commands funktionieren mehrfach (Dedup nur über cmdId, wenn vorhanden)
                    *  - force:true klickt Tab auch wenn bereits selektiert
                    *  - periodisches resubscribe gegen verlorene Subscriptions
                    ********************************************************************/
                    
                    // =================== KONFIG ===================
                    const DEBUG = false;
                    
                    // Tabs-Widget ID (aus deinem DOM)
                    const TABS_WIDGET_ID = "w000016";
                    
                    // URL Parameter
                    const URL_PARAM_TAB = "tab"; // ?main&tab=Licht#datum
                    
                    // Datenpunkte (string)
                    const DP_TAB_EVENT   = "0_userdata.0.vis2.tabEvent";   // Client -> ioBroker
                    const DP_TAB_CMD     = "0_userdata.0.vis2.tabCmd";     // ioBroker -> Client
                    const DP_DEVICE_MAP  = "0_userdata.0.vis2.deviceMap";  // ioBroker -> Client (DeviceId -> Name)
                    
                    // Index-Format
                    const INDEX_ONE_BASED = true;  // true => 1..N, false => 0..N-1
                    
                    // History
                    const USE_PUSHSTATE = false;   // false = replaceState (empfohlen)
                    
                    // Timing
                    const OBSERVE_TIMEOUT_MS = 15000;
                    
                    // Re-Subscribe (gegen „geht nur einmal“)
                    const RESUBSCRIBE_EVERY_MS = 30000;
                    
                    // localStorage Keys
                    const LS_KEYS = {
                     deviceId:   "vis2.deviceId",
                     deviceName: "vis2.deviceName",
                    };
                    // ==============================================
                    
                    
                    // =================== Helpers ===================
                    function dlog(...a) { if (DEBUG) console.log("[VIS2Tabs]", ...a); }
                    
                    function getVis() {
                     return (typeof window !== "undefined" && (window.vis || window.VIS)) || (typeof vis !== "undefined" ? vis : null);
                    }
                    
                    let suppressUntil = 0;
                    function suppress(ms = 350) { suppressUntil = Date.now() + ms; }
                    function isSuppressed() { return Date.now() < suppressUntil; }
                    
                    function norm(s) { return String(s ?? "").trim().toLowerCase(); }
                    function safeJsonParse(s) { try { return JSON.parse(s); } catch { return null; } }
                    
                    function getSearchParams() {
                     return new URLSearchParams(window.location.search);
                    }
                    
                    function getCurrentViewFromHash() {
                     const h = (window.location.hash || "").replace(/^#/, "");
                     return (h.split("?")[0] || "").trim();
                    }
                    
                    function gotoView(view) {
                     if (!view) return;
                     const cur = getCurrentViewFromHash();
                     if (cur === view) return;
                     suppress(600);
                     window.location.hash = `#${view}`;
                    }
                    
                    function getOrCreateDeviceId() {
                     let id = localStorage.getItem(LS_KEYS.deviceId);
                     if (id) return id;
                    
                     const buf = new Uint8Array(16);
                     if (crypto?.getRandomValues) crypto.getRandomValues(buf);
                     else for (let i = 0; i < buf.length; i++) buf[i] = Math.floor(Math.random() * 256);
                    
                     id = Array.from(buf).map(b => b.toString(16).padStart(2, "0")).join("");
                     localStorage.setItem(LS_KEYS.deviceId, id);
                     return id;
                    }
                    
                    // robustes Lesen aus vis.states Cache (DP oder DP.val / ggf. State-Objekt)
                    function readVisValue(id) {
                     const v = getVis();
                     if (!v?.states) return null;
                    
                     const candidates = [id + ".val", id];
                    
                     for (const key of candidates) {
                       try {
                         let val = (v.states.attr && v.states.attr(key)) ?? v.states[key];
                         if (typeof val === "object" && val && "val" in val) val = val.val; // unwrap
                         if (val !== undefined && val !== null) return val;
                       } catch {}
                     }
                     return null;
                    }
                    
                    function writeVisValue(id, value) {
                     const v = getVis();
                     if (!v) return false;
                    
                     if (typeof v.setValue === "function") {
                       v.setValue(id, value);
                       return true;
                     }
                     if (v.conn && typeof v.conn.setState === "function") {
                       v.conn.setState(id, value);
                       return true;
                     }
                     return false;
                    }
                    // ==============================================
                    
                    
                    // =================== Device Map ===================
                    function applyDeviceNameFromMap() {
                     const myId = getOrCreateDeviceId();
                    
                     const raw = readVisValue(DP_DEVICE_MAP);
                     const map = (typeof raw === "string" && raw.trim()) ? safeJsonParse(raw) : null;
                    
                     const name = map && typeof map === "object" ? map[myId] : null;
                    
                     if (name && String(name).trim()) {
                       const n = String(name).trim();
                       localStorage.setItem(LS_KEYS.deviceName, n);
                       return n;
                     }
                    
                     return (localStorage.getItem(LS_KEYS.deviceName) || "").trim();
                    }
                    // ==============================================
                    
                    
                    // =================== URL: tab aus Query ===================
                    function getTabParamFromSearch() {
                     return getSearchParams().get(URL_PARAM_TAB) || null;
                    }
                    
                    // NICHT URLSearchParams serialisieren (sonst wird aus ?main -> ?main=)
                    // Wir ersetzen/appendieren nur "&tab=..."
                    function setTabParamInSearch(tabValue) {
                     if (!tabValue) return;
                    
                     const path = window.location.pathname;
                     const hash = window.location.hash || "";
                     const raw  = window.location.search || "";
                    
                     const current = getTabParamFromSearch();
                     if (current === tabValue) return;
                    
                     let next = raw;
                    
                     if (/[?&]tab=/.test(next)) {
                       next = next.replace(/([?&]tab=)[^&]*/i, `$1${encodeURIComponent(tabValue)}`);
                     } else {
                       next += (next.includes("?") ? "&" : "?") + `tab=${encodeURIComponent(tabValue)}`;
                     }
                    
                     const newUrl = `${path}${next}${hash}`;
                    
                     suppress(450);
                     if (USE_PUSHSTATE) history.pushState(null, "", newUrl);
                     else history.replaceState(null, "", newUrl);
                    }
                    // ==============================================
                    
                    
                    // =================== Tabs DOM Zugriff ===================
                    function getTabButtons() {
                     const root = document.getElementById(TABS_WIDGET_ID);
                     if (!root) return [];
                     return Array.from(root.querySelectorAll('button[role="tab"]'));
                    }
                    
                    function isSelected(btn) {
                     return (
                       btn?.getAttribute("aria-selected") === "true" ||
                       btn?.classList?.contains("Mui-selected")
                     );
                    }
                    
                    function getSelectedTab() {
                     const tabs = getTabButtons();
                     return tabs.find(isSelected) || null;
                    }
                    
                    function getSelectedTabName() {
                     const sel = getSelectedTab();
                     return sel ? (sel.textContent || "").trim() : "";
                    }
                    
                    function getSelectedTabIndex() {
                     const tabs = getTabButtons();
                     const sel  = getSelectedTab();
                     if (!sel) return null;
                    
                     const idx0 = tabs.indexOf(sel);
                     if (idx0 < 0) return null;
                    
                     return INDEX_ONE_BASED ? (idx0 + 1) : idx0;
                    }
                    
                    // force=true: klickt auch wenn schon selected
                    function trySelectTab(tabParam, force = false) {
                     if (tabParam == null) return false;
                    
                     const tabs = getTabButtons();
                     if (!tabs.length) return false;
                    
                     let target = null;
                     const s = String(tabParam).trim();
                    
                     if (/^\d+$/.test(s)) {
                       const n = parseInt(s, 10);
                       const idx = (n === 0) ? 0 : (n - 1);
                       target = tabs[idx];
                     } else {
                       const needle = norm(s);
                       target = tabs.find(b => norm(b.textContent) === needle);
                     }
                    
                     if (!target) return false;
                    
                     if (force || !isSelected(target)) {
                       suppress(450);
                       target.click();
                     }
                     return true;
                    }
                    
                    function forceSelectTab(tabParam, force = false) {
                     if (trySelectTab(tabParam, force)) return;
                    
                     const obs = new MutationObserver(() => {
                       if (trySelectTab(tabParam, force)) obs.disconnect();
                     });
                    
                     obs.observe(document.body, { childList: true, subtree: true });
                     setTimeout(() => obs.disconnect(), OBSERVE_TIMEOUT_MS);
                    }
                    // ==============================================
                    
                    
                    // =================== TabEvent -> DP ===================
                    let lastSentSig = "";
                    
                    function sendTabEventToDP() {
                     const idx  = getSelectedTabIndex();
                     const name = getSelectedTabName();
                     if (idx == null || !name) return;
                    
                     const sig = `${idx}|${name}`;
                     if (sig === lastSentSig) return;
                     lastSentSig = sig;
                    
                     const deviceId   = getOrCreateDeviceId();
                     const deviceName = applyDeviceNameFromMap();
                     const view       = getCurrentViewFromHash();
                    
                     const payload = {
                       deviceId,
                       deviceName: deviceName || undefined,
                       view: view || undefined,
                       tabIndex: idx,
                       tabName: name,
                       ts: Date.now()
                     };
                    
                     writeVisValue(DP_TAB_EVENT, JSON.stringify(payload));
                    }
                    // ==============================================
                    
                    
                    // =================== URL -> Tab (Load/Navi) ===================
                    function applyTabFromUrl() {
                     if (isSuppressed()) return;
                    
                     const tabParam = getTabParamFromSearch();
                     if (!tabParam) return;
                    
                     forceSelectTab(tabParam, false);
                    }
                    // ==============================================
                    
                    
                    // =================== Tab -> URL (bei Wechsel) ===================
                    let lastWrittenName = "";
                    
                    function writeUrlFromSelection() {
                     if (isSuppressed()) return;
                    
                     const name = getSelectedTabName();
                     if (!name) return;
                     if (name === lastWrittenName) return;
                    
                     lastWrittenName = name;
                     setTabParamInSearch(name);
                    }
                    
                    function wireTabHandlers() {
                     const tabs = getTabButtons();
                     if (!tabs.length) return false;
                    
                     tabs.forEach(btn => {
                       if (btn.__vis2TabWired) return;
                       btn.__vis2TabWired = true;
                    
                       const handler = () => setTimeout(() => {
                         writeUrlFromSelection();
                         sendTabEventToDP();
                       }, 0);
                    
                       btn.addEventListener("click", handler);
                       btn.addEventListener("keydown", (e) => {
                         if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") handler();
                       });
                     });
                    
                     return true;
                    }
                    
                    function observeTabChanges() {
                     const root = document.getElementById(TABS_WIDGET_ID);
                     if (!root) return false;
                    
                     const obs = new MutationObserver(() => {
                       wireTabHandlers();
                       writeUrlFromSelection();
                       sendTabEventToDP();
                     });
                    
                     obs.observe(root, {
                       subtree: true,
                       childList: true,
                       attributes: true,
                       attributeFilter: ["aria-selected", "class"],
                     });
                    
                     wireTabHandlers();
                     writeUrlFromSelection();
                     sendTabEventToDP();
                     return true;
                    }
                    // ==============================================
                    
                    
                    // =================== Command-DP -> Tab setzen ===================
                    let lastCmdToken = "";
                    
                    function isTargetForMe(cmd) {
                     const myId   = getOrCreateDeviceId();
                     const myName = applyDeviceNameFromMap();
                    
                     if (!cmd || cmd.target == null) return false;
                    
                     const t = cmd.target;
                    
                     if (t === "*") return true;
                    
                     if (Array.isArray(t)) {
                       return t.includes(myId) || (myName && t.includes(myName));
                     }
                    
                     if (typeof t === "string") {
                       return t === myId || (myName && t === myName);
                     }
                    
                     if (typeof t === "object") {
                       if (t.deviceId && t.deviceId === myId) return true;
                       if (t.deviceName && myName && t.deviceName === myName) return true;
                     }
                    
                     return false;
                    }
                    
                    function handleTabCmd(rawVal) {
                     if (rawVal == null) return;
                    
                     // unwrap State-Objekt
                     if (typeof rawVal === "object" && rawVal && "val" in rawVal) {
                       rawVal = rawVal.val;
                     }
                     if (rawVal == null) return;
                    
                     let cmd = null;
                    
                     if (typeof rawVal === "string") {
                       const s = rawVal.trim();
                       if (!s) return;
                    
                       if (s.startsWith("{") || s.startsWith("[")) cmd = safeJsonParse(s);
                       if (!cmd) cmd = { target: "*", tab: s, ts: Date.now() };
                     } else if (typeof rawVal === "number") {
                       cmd = { target: "*", tab: rawVal, ts: Date.now() };
                     } else if (typeof rawVal === "object") {
                       cmd = rawVal;
                     }
                     if (!cmd) return;
                    
                     // Kompatibilität: event-artiges Format akzeptieren
                     if (cmd.target == null && (cmd.deviceId || cmd.deviceName)) {
                       cmd.target = { deviceId: cmd.deviceId, deviceName: cmd.deviceName };
                     }
                     if (cmd.tab == null) {
                       if (cmd.tabName != null) cmd.tab = cmd.tabName;
                       else if (cmd.tabIndex != null) cmd.tab = cmd.tabIndex;
                     }
                    
                     // ✅ Dedup NUR über cmdId (wenn vorhanden). Ohne cmdId wird NICHT deduped.
                     if (cmd.cmdId != null) {
                       const token = String(cmd.cmdId);
                       if (token === lastCmdToken) return;
                       lastCmdToken = token;
                     }
                    
                     // Zielprüfung
                     applyDeviceNameFromMap();
                     if (!isTargetForMe(cmd)) return;
                    
                     dlog("CMD accepted:", cmd);
                    
                     if (cmd.view) gotoView(String(cmd.view));
                     if (cmd.tab != null) forceSelectTab(cmd.tab, !!cmd.force);
                    }
                    // ==============================================
                    
                    
                    // =================== Subscribe/Bind Setup ===================
                    let subscribedIds = new Set();
                    
                    function subscribeNow(ids) {
                     const v = getVis();
                     if (!v?.conn?.subscribe) return;
                    
                     try {
                       v.conn.subscribe(ids);
                       ids.forEach(id => subscribedIds.add(id));
                       dlog("subscribed:", ids);
                     } catch (e) {
                       dlog("subscribe err", e);
                     }
                    }
                    
                    function ensureSubscribeAndBind(ids, onChange) {
                     const v = getVis();
                     if (!v?.conn?.getStates || !v?.conn?.subscribe || !v?.states?.bind) {
                       return false;
                     }
                    
                     try { v.conn.gettingStates = 0; } catch {}
                    
                     v.conn.getStates(ids, (err, states) => {
                       if (err) dlog("getStates err", err);
                    
                       // subscribe
                       subscribeNow(ids);
                    
                       // cache füllen (hilft je nach Build)
                       try {
                         if (states && typeof v.updateStates === "function") v.updateStates(states);
                       } catch {}
                    
                       ids.forEach(id => {
                         const cb = (e, newVal, oldVal) => onChange(newVal, oldVal, id);
                    
                         // robust: DP und DP.val
                         try { v.states.bind(id + ".val", cb); } catch {}
                         try { v.states.bind(id, cb); } catch {}
                       });
                     });
                    
                     return true;
                    }
                    
                    function setupCmdListener() {
                     const ok = ensureSubscribeAndBind([DP_TAB_CMD], (newVal) => {
                       if (isSuppressed()) return;
                       handleTabCmd(newVal);
                     });
                    
                     if (ok) return true;
                    
                     // Fallback Polling
                     let last = null;
                     const timer = setInterval(() => {
                       try {
                         let v = readVisValue(DP_TAB_CMD);
                         if (typeof v === "object" && v && "val" in v) v = v.val;
                         if (v != null && v !== last) {
                           last = v;
                           if (!isSuppressed()) handleTabCmd(v);
                         }
                       } catch {}
                     }, 500);
                    
                     window.addEventListener("beforeunload", () => clearInterval(timer));
                     return true;
                    }
                    
                    function setupDeviceMapListener() {
                     const ok = ensureSubscribeAndBind([DP_DEVICE_MAP], () => {
                       if (isSuppressed()) return;
                       const n = applyDeviceNameFromMap();
                       dlog("deviceName updated:", n);
                     });
                    
                     applyDeviceNameFromMap();
                     return ok;
                    }
                    
                    function startPeriodicResubscribe() {
                     setInterval(() => {
                       // immer wieder subscribe, falls VIS2 nach Reconnect „vergisst“
                       subscribeNow([DP_TAB_CMD, DP_DEVICE_MAP]);
                     }, RESUBSCRIBE_EVERY_MS);
                    }
                    // ==============================================
                    
                    
                    // =================== Init ===================
                    (function init() {
                     const id = getOrCreateDeviceId();
                     dlog("deviceId:", id);
                    
                     const n = applyDeviceNameFromMap();
                     dlog("deviceName:", n);
                    
                     applyTabFromUrl();
                    
                     if (!observeTabChanges()) {
                       const obs = new MutationObserver(() => {
                         if (observeTabChanges()) obs.disconnect();
                       });
                       obs.observe(document.body, { childList: true, subtree: true });
                       setTimeout(() => obs.disconnect(), OBSERVE_TIMEOUT_MS);
                     }
                    
                     setupCmdListener();
                     setupDeviceMapListener();
                     startPeriodicResubscribe();
                    
                     window.addEventListener("popstate", applyTabFromUrl);
                    })();
                    
                    

                    vis2-TabControl.gif

                    NUC i7 64GB mit Proxmox ---- Jarvis Infos Aktualisierungen der Doku auf Instagram verfolgen -> mcuiobroker Instagram
                    Wenn Euch mein Vorschlag geholfen hat, bitte rechts "^" klicken.

                    1 Antwort Letzte Antwort
                    0
                    Antworten
                    • In einem neuen Thema antworten
                    Anmelden zum Antworten
                    • Älteste zuerst
                    • Neuste zuerst
                    • Meiste Stimmen


                    Support us

                    ioBroker
                    Community Adapters
                    Donate

                    560

                    Online

                    32.6k

                    Benutzer

                    82.3k

                    Themen

                    1.3m

                    Beiträge
                    Community
                    Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                    ioBroker Community 2014-2025
                    logo
                    • Anmelden

                    • Du hast noch kein Konto? Registrieren

                    • Anmelden oder registrieren, um zu suchen
                    • Erster Beitrag
                      Letzter Beitrag
                    0
                    • Home
                    • Aktuell
                    • Tags
                    • Ungelesen 0
                    • Kategorien
                    • Unreplied
                    • Beliebt
                    • GitHub
                    • Docu
                    • Hilfe