NEWS
Eigene Widget mit HTML/CSS/JAVASCRIPT + AI
-
Hey Leute,
mir fehlen leider die Programmierkentnnise um das HTML Widget selber zu machen also habe ich mir mit Claude.ai etwas Hilfe geholt.Ich bin auch fast am Ziel meines Vorhabens allerdings bekomme ich einen Fehler seit Stunden nicht heraus.
Ich habe mir ein Widget gemacht wo ich Jalousien direkt hoch und runter fahren kann. Und links davon der aktuelle Status von der Jalousie angezeigt werden soll
Hier ist bereits der erste Fehler weil da 0% angezeigt wird statt der echte Wert.Wenn ich auf das Widget klicke wird ein PopUp geöffnet mit mehr Funktionen, auch hier werden die aktuellen Werte nicht angezeigt.
Der Regler funktioniert und die Prozente werden beim bedienen angezeigt,
allerdings nur solange das PopUp offen ist. sobal ich das schliese und neu öffne steht oben wieder 0
P.S im Editor wird die Jalousien Position agezeigt
Ich gehe aktuell also davon aus, dass auch die Werte im PopUp im Editor angezeigt werden und das das Problem nur in der VIS Ansicht ist.
Das ist der Code dazu
<style> .jalousie-widget-container { width: 100%; height: 100%; box-sizing: border-box; border-radius: 20px; padding: 0; background: #f5f5f5; font-family: sans-serif; position: relative; margin: 0 auto; border: 1px solid #e0e0e0; display: flex; flex-direction: column; } .jalousie-widget-header { display: flex; align-items: center; padding: calc(min(10px, 3%)); border-bottom: 1px dotted #ccc; flex: 0 0 auto; } .jalousie-widget-icon { width: calc(min(24px, 15%)); height: calc(min(24px, 15%)); margin-right: calc(min(10px, 3%)); min-width: 16px; min-height: 16px; } .jalousie-widget-title { font-size: calc(min(14px, 4vw)); margin: 0; padding: calc(min(8px, 3%)) 0; text-align: left; border-bottom: 1px dotted #ccc; padding-left: calc(min(10px, 3%)); flex: 0 0 auto; } .jalousie-widget-status { display: flex; align-items: center; padding: calc(min(10px, 3%)) calc(min(12px, 4%)); justify-content: space-between; flex: 1 0 auto; } .jalousie-status-text { font-size: calc(min(16px, 4.5vw)); margin: 0; font-weight: 500; } .jalousie-widget-buttons { display: flex; } .jalousie-widget-button { width: calc(min(44px, 35%)); height: calc(min(44px, 35%)); display: flex; align-items: center; justify-content: center; border: 1px solid #ccc; background: white; border-radius: 50%; margin-left: 8px; cursor: pointer; font-size: calc(min(26px, 8vw)); padding: 0; min-width: 40px; min-height: 40px; font-weight: bold; color: #333; } .jalousie-popup-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: none; z-index: 9998; touch-action: none; } .jalousie-popup { display: none; position: fixed; top: 5%; left: 5%; width: 90%; height: 90%; max-height: 90vh; background: white; border-radius: 8px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); z-index: 9999; font-family: sans-serif; overflow: hidden; touch-action: none; } .jalousie-popup.active { display: flex; flex-direction: column; } .jalousie-popup-header { padding: 15px; border-bottom: 1px solid #eee; font-size: 18px; font-weight: 500; display: flex; justify-content: space-between; align-items: center; } .jalousie-popup-status { padding: 15px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; background: #f9f9f9; } .jalousie-popup-status-item { display: flex; align-items: center; } .jalousie-popup-status-label { font-size: 14px; color: #666; margin-right: 10px; } .jalousie-popup-status-value { font-size: 16px; font-weight: bold; } .jalousie-popup-content { flex: 1; display: flex; padding: 20px; justify-content: center; overflow: hidden; touch-action: none; } .jalousie-sliders-container { display: flex; width: 100%; max-width: 600px; justify-content: space-around; align-items: flex-start; } .jalousie-slider-column { display: flex; flex-direction: column; align-items: center; width: 45%; } .jalousie-slider-wrapper { display: flex; flex-direction: column; align-items: center; width: 100%; height: 100%; max-height: 500px; } .jalousie-slider-icon { margin-bottom: 10px; width: 24px; height: 24px; } .jalousie-slider-vertical { -webkit-appearance: slider-vertical; appearance: slider-vertical; height: 80%; min-height: 200px; width: 50px; margin: 20px 0; transform: rotate(180deg); background: #e0e0e0; border-radius: 25px; outline: none; cursor: pointer; } .jalousie-slider-vertical::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 60px; height: 60px; background: #007bff; border-radius: 50%; cursor: pointer; box-shadow: 0 0 5px rgba(0,0,0,0.2); } .jalousie-slider-vertical::-moz-range-thumb { width: 60px; height: 60px; background: #007bff; border-radius: 50%; cursor: pointer; box-shadow: 0 0 5px rgba(0,0,0,0.2); border: none; } .jalousie-slider-label { font-size: 16px; margin-top: 10px; text-align: center; } .jalousie-slider-buttons { display: flex; gap: 10px; margin-top: 15px; } .jalousie-slider-button { width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; border: 1px solid #ccc; background: white; border-radius: 50%; cursor: pointer; font-size: 24px; padding: 0; font-weight: bold; touch-action: manipulation; color: #333; } .jalousie-slider-button:hover { background: #f5f5f5; } .jalousie-slider-button:active { background: #e0e0e0; } .jalousie-popup-footer { padding: 15px; border-top: 1px solid #eee; display: flex; justify-content: space-between; } .jalousie-popup-button { padding: 10px 20px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; cursor: pointer; color: #333; } .jalousie-popup-button:hover { background: #eee; } .jalousie-value-display { font-size: 20px; font-weight: bold; margin-top: 10px; margin-bottom: 20px; } </style> <div class="jalousie-widget-container" id="dg-buero-jalousie-widget"> <div class="jalousie-widget-header"> <svg id="dg-buero-jalousie-icon" class="jalousie-widget-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="cursor: pointer;"> <rect x="4" y="4" width="16" height="16" rx="2" /> <line x1="4" y1="12" x2="20" y2="12" /> <line x1="7" y1="8" x2="17" y2="8" /> <line x1="7" y1="16" x2="17" y2="16" /> </svg> </div> <div class="jalousie-widget-title"> DG Büro </div> <div class="jalousie-widget-status"> <div class="jalousie-status-text"> <span id="dg-buero-jalousie-status">0</span>% </div> <div class="jalousie-widget-buttons"> <button id="dg-buero-btn-up" class="jalousie-widget-button">↑</button> <button id="dg-buero-btn-down" class="jalousie-widget-button">↓</button> </div> </div> </div> <div id="dg-buero-jalousie-popup-overlay" class="jalousie-popup-overlay"></div> <div id="dg-buero-jalousie-popup" class="jalousie-popup"> <div class="jalousie-popup-header"> DG Büro <button id="dg-buero-close-popup" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #333;">×</button> </div> <div class="jalousie-popup-status"> <div class="jalousie-popup-status-item"> <span class="jalousie-popup-status-label">Jalousie:</span> <span class="jalousie-popup-status-value" id="dg-buero-popup-status-jalousie">0%</span> </div> <div class="jalousie-popup-status-item"> <span class="jalousie-popup-status-label">Lamellen:</span> <span class="jalousie-popup-status-value" id="dg-buero-popup-status-lamellen">0%</span> </div> </div> <div class="jalousie-popup-content"> <div class="jalousie-sliders-container"> <div class="jalousie-slider-column"> <div class="jalousie-slider-wrapper"> <svg class="jalousie-slider-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="6" y="4" width="12" height="16" rx="1" /> <line x1="6" y1="8" x2="18" y2="8" /> <line x1="6" y1="12" x2="18" y2="12" /> <line x1="6" y1="16" x2="18" y2="16" /> </svg> <div class="jalousie-value-display"><span id="dg-buero-popup-jalousie-value">0</span>%</div> <input type="range" min="0" max="100" value="0" id="dg-buero-slider-jalousie" class="jalousie-slider-vertical" orient="vertical" /> <div class="jalousie-slider-label">Position Jalousie</div> <div class="jalousie-slider-buttons"> <button id="dg-buero-btn-jalousie-up" class="jalousie-slider-button">↑</button> <button id="dg-buero-btn-jalousie-down" class="jalousie-slider-button">↓</button> </div> </div> </div> <div class="jalousie-slider-column"> <div class="jalousie-slider-wrapper"> <svg class="jalousie-slider-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M12 3 L4 10 L20 10 Z" /> <path d="M12 10 L4 17 L20 17 Z" /> </svg> <div class="jalousie-value-display"><span id="dg-buero-popup-lamellen-value">0</span>%</div> <input type="range" min="0" max="100" value="0" id="dg-buero-slider-lamellen" class="jalousie-slider-vertical" orient="vertical" /> <div class="jalousie-slider-label">Position Lamellen</div> <div class="jalousie-slider-buttons"> <button id="dg-buero-btn-lamellen-up" class="jalousie-slider-button">↑</button> <button id="dg-buero-btn-lamellen-down" class="jalousie-slider-button">↓</button> </div> </div> </div> </div> </div> <div class="jalousie-popup-footer"> <button id="dg-buero-btn-stop" class="jalousie-popup-button">STOP</button> <div> <button id="dg-buero-btn-preset-1" class="jalousie-popup-button">50%</button> <button id="dg-buero-btn-preset-2" class="jalousie-popup-button">100%</button> </div> </div> </div> <script> // Datenpunkte definieren - Diese müssen an dein ioBroker-System angepasst sein const statusDP = 'openknx.0.Status.Jalousinen_Lamellen.Position_Jalousine_DG_Büro_status'; // For main widget display const statusJalousieDP = 'openknx.0.Status.Jalousinen_Lamellen.Position_Jalousine_DG_Büro_status'; // Jalousie status in popup const statusLamellenDP = 'openknx.0.Status.Jalousinen_Lamellen.Position_Lamellen_DG_Büro_status'; // Lamella status in popup const lamellenDP = 'openknx.0.Schalten.Jalousinen.DG_Büro_Balkon_Lamellen'; const jalousienDP = 'openknx.0.Schalten.Jalousinen.DG_Büro_Balkon_Jalousien'; const absoluteJalousieDP = 'openknx.0.Schalten.Jalousinen.DG_Büro_Absolute_Position_Jalousine'; const absoluteLamellenDP = 'openknx.0.Schalten.Jalousinen.DG_Büro_Absolute_Position_Lamellen'; let holdTimeout; let statusValue = 0; let jalousieValue = 0; // Status value for jalousie let lamellenValue = 0; // Status value for lamella // DOM-Elemente const btnUp = document.getElementById('dg-buero-btn-up'); const btnDown = document.getElementById('dg-buero-btn-down'); const jalousieIcon = document.getElementById('dg-buero-jalousie-icon'); const closePopup = document.getElementById('dg-buero-close-popup'); const jalousiePopup = document.getElementById('dg-buero-jalousie-popup'); const popupOverlay = document.getElementById('dg-buero-jalousie-popup-overlay'); const statusElement = document.getElementById('dg-buero-jalousie-status'); const sliderJalousie = document.getElementById('dg-buero-slider-jalousie'); const sliderLamellen = document.getElementById('dg-buero-slider-lamellen'); // DOM-Elemente für die Popup-Werte const popupJalousieValue = document.getElementById('dg-buero-popup-jalousie-value'); const popupLamellenValue = document.getElementById('dg-buero-popup-lamellen-value'); const popupStatusJalousie = document.getElementById('dg-buero-popup-status-jalousie'); const popupStatusLamellen = document.getElementById('dg-buero-popup-status-lamellen'); const btnStop = document.getElementById('dg-buero-btn-stop'); const btnPreset1 = document.getElementById('dg-buero-btn-preset-1'); const btnPreset2 = document.getElementById('dg-buero-btn-preset-2'); // Additional popup buttons const btnJalousieUp = document.getElementById('dg-buero-btn-jalousie-up'); const btnJalousieDown = document.getElementById('dg-buero-btn-jalousie-down'); const btnLamellenUp = document.getElementById('dg-buero-btn-lamellen-up'); const btnLamellenDown = document.getElementById('dg-buero-btn-lamellen-down'); // Funktion für das Senden von Werten an Datenpunkte function sendToDP(dp, value) { if (typeof vis !== 'undefined') { vis.setValue(dp, value); } else { console.log('Sending to ' + dp + ': ' + value); } } // Funktion für das Halten von Tasten function setupHoldButton(button, shortDP, longDP, value) { button.addEventListener('mousedown', function() { holdTimeout = setTimeout(function() { sendToDP(longDP, value); holdTimeout = null; }, 400); }); button.addEventListener('mouseup', function() { if (holdTimeout) { clearTimeout(holdTimeout); sendToDP(shortDP, value); } }); button.addEventListener('mouseleave', function() { if (holdTimeout) { clearTimeout(holdTimeout); } }); // Touch-Unterstützung für mobile Geräte button.addEventListener('touchstart', function(e) { e.preventDefault(); holdTimeout = setTimeout(function() { sendToDP(longDP, value); holdTimeout = null; }, 400); }); button.addEventListener('touchend', function(e) { e.preventDefault(); if (holdTimeout) { clearTimeout(holdTimeout); sendToDP(shortDP, value); } }); } // Popup öffnen/schließen function openPopup() { jalousiePopup.classList.add('active'); popupOverlay.style.display = 'block'; // Slider immer auf 0 setzen sliderJalousie.value = 0; sliderLamellen.value = 0; // Value-Displays auf 0 setzen popupJalousieValue.textContent = 0; popupLamellenValue.textContent = 0; // Status-Anzeige aus Datenpunkten lesen if (typeof vis !== 'undefined') { let jalousieStatus = vis.states.attr(statusJalousieDP + '.val'); let lamellenStatus = vis.states.attr(statusLamellenDP + '.val'); if (jalousieStatus !== undefined && jalousieStatus !== null) { popupStatusJalousie.textContent = parseInt(jalousieStatus) + '%'; } if (lamellenStatus !== undefined && lamellenStatus !== null) { popupStatusLamellen.textContent = parseInt(lamellenStatus) + '%'; } } } function closePopupFunc() { jalousiePopup.classList.remove('active'); popupOverlay.style.display = 'none'; } // Add touch event handlers to prevent background scrolling on mobile document.getElementById('dg-buero-jalousie-popup-overlay').addEventListener('touchmove', function(e) { e.preventDefault(); }, { passive: false }); document.getElementById('dg-buero-jalousie-popup').addEventListener('touchmove', function(e) { // Only prevent if the touch is not on a slider if (!e.target.classList.contains('jalousie-slider-vertical')) { e.preventDefault(); } }, { passive: false }); // Ensure sliders can be used on mobile ['dg-buero-slider-jalousie', 'dg-buero-slider-lamellen'].forEach(id => { const slider = document.getElementById(id); slider.addEventListener('touchmove', function(e) { e.stopPropagation(); // Allow slider to function normally }, { passive: true }); }); // Event-Listener für Popup jalousieIcon.addEventListener('click', openPopup); closePopup.addEventListener('click', closePopupFunc); popupOverlay.addEventListener('click', closePopupFunc); // Slider-Events mit Echtzeit-Aktualisierung sliderJalousie.addEventListener('input', function() { const value = parseInt(this.value); popupJalousieValue.textContent = value; }); sliderJalousie.addEventListener('change', function() { const value = parseInt(this.value); sendToDP(absoluteJalousieDP, value); // Don't update statusValue directly since it comes from the datapoint }); sliderLamellen.addEventListener('input', function() { const value = parseInt(this.value); popupLamellenValue.textContent = value; // Also update the status bar immediately for better user feedback popupStatusLamellen.textContent = value + '%'; }); sliderLamellen.addEventListener('change', function() { const value = parseInt(this.value); sendToDP(absoluteLamellenDP, value); // The status will be updated via datapoint binding }); // STOP-Button und Preset-Buttons btnStop.addEventListener('click', function() { sendToDP(lamellenDP, true); }); btnPreset1.addEventListener('click', function() { sendToDP(absoluteJalousieDP, 50); }); btnPreset2.addEventListener('click', function() { sendToDP(absoluteJalousieDP, 100); }); // Setup popup arrow buttons with same logic as main widget setupHoldButton(btnJalousieUp, lamellenDP, jalousienDP, false); setupHoldButton(btnJalousieDown, lamellenDP, jalousienDP, true); setupHoldButton(btnLamellenUp, lamellenDP, lamellenDP, false); setupHoldButton(btnLamellenDown, lamellenDP, lamellenDP, true); // Button-Konfiguration setupHoldButton(btnUp, lamellenDP, jalousienDP, false); setupHoldButton(btnDown, lamellenDP, jalousienDP, true); // Initialisierung für ioBroker function init() { // Status-Wert überwachen if (typeof vis !== 'undefined') { // Jalousie-Status überwachen vis.states.bind(statusJalousieDP + '.val', function(e, newVal, oldVal) { if (newVal !== undefined) { // Prüfung hinzugefügt jalousieValue = parseInt(newVal); statusElement.textContent = jalousieValue; sliderJalousie.value = jalousieValue; if (popupJalousieValue) { popupJalousieValue.textContent = jalousieValue; } if (popupStatusJalousie) { popupStatusJalousie.textContent = jalousieValue + '%'; } } }); // Lamellen-Status überwachen vis.states.bind(statusLamellenDP + '.val', function(e, newVal, oldVal) { if (newVal !== undefined) { lamellenValue = parseInt(newVal); sliderLamellen.value = lamellenValue; if (popupLamellenValue) { popupLamellenValue.textContent = lamellenValue; } if (popupStatusLamellen) { popupStatusLamellen.textContent = lamellenValue + '%'; } } }); // Anfangswerte laden if (vis.states[statusJalousieDP + '.val'] !== undefined) { jalousieValue = parseInt(vis.states[statusJalousieDP + '.val']); statusElement.textContent = jalousieValue; sliderJalousie.value = jalousieValue; popupJalousieValue.textContent = jalousieValue; popupStatusJalousie.textContent = jalousieValue + '%'; } if (vis.states[statusLamellenDP + '.val'] !== undefined) { lamellenValue = parseInt(vis.states[statusLamellenDP + '.val']); sliderLamellen.value = lamellenValue; popupLamellenValue.textContent = lamellenValue; popupStatusLamellen.textContent = lamellenValue + '%'; } } else { // Fallback für Testumgebungen jalousieValue = 50; lamellenValue = 50; statusElement.textContent = jalousieValue; sliderJalousie.value = jalousieValue; sliderLamellen.value = lamellenValue; popupJalousieValue.textContent = jalousieValue; popupLamellenValue.textContent = lamellenValue; popupStatusJalousie.textContent = jalousieValue + '%'; popupStatusLamellen.textContent = lamellenValue + '%'; } } // Wenn vis bereits geladen ist if (typeof vis !== 'undefined') { init(); } else { // Falls vis noch nicht geladen ist (sollte in ioBroker nicht passieren) document.addEventListener('iobrokerReady', init); } </script>
-
Das Hauptproblem ist, das die ganzen datenpunkte nicht abonniert wurde.
Vis sammelt beim Start (also bspw neuladen mit f5) zunächst erst einmal alle vorhandenen datenpunkte ein und abonniert diese beim Server.
Erst dann sind die in der vis.states Auflistung vorhanden.
Das passiert bei dir nicht. Daher auch keine DatenDu kannst mal hier nach meiner Funktion bindStates schauen.
Die macht das mit der Angabe des datenpunkt namens.
Du musst aber ein html Element mitgeben. Dort werden dann die trigger für die Änderung gespeichert, das die auch wieder entfernt werden. Ansonsten holst du dir früher oder später ein Speicherwerk, da sich mit der Zeit immer mehr trigger anhäufen und bei einer Änderung deine Funktionen dann zigmal aufgerufen werden.Die variablen mit $ sind jquery Objekte.