From 2c7cbbe073a7fa40b03e8af2a2ff6c20c30ca73c Mon Sep 17 00:00:00 2001 From: DiTus Date: Wed, 25 Feb 2026 23:04:10 +0100 Subject: [PATCH] Complete rewrite of indicators panel to fix duplicate event listeners Key changes: - Single 'listenersAttached' flag tracks if any listeners are attached - One shared event delegation handler handles all button types (add, config, remove, favorite) - Stop propagation immediately on button clicks to prevent multiple triggers - Added e.stopPropagation() calls to prevent event bubbling - Consolidated event listener logic into single function - Added safety checks before calling window.functionName This fixes the issue where one click added multiple indicators (3x MA, 6x HTS, 4x RSI) by preventing duplicate event listener setup. --- .../static/js/ui/indicators-panel-new.js | 247 ++++++++++-------- 1 file changed, 142 insertions(+), 105 deletions(-) diff --git a/src/api/dashboard/static/js/ui/indicators-panel-new.js b/src/api/dashboard/static/js/ui/indicators-panel-new.js index 6f42c52..91c552d 100644 --- a/src/api/dashboard/static/js/ui/indicators-panel-new.js +++ b/src/api/dashboard/static/js/ui/indicators-panel-new.js @@ -6,7 +6,7 @@ let configuringId = null; let searchQuery = ''; let selectedCategory = 'all'; let nextInstanceId = 1; -let eventListenersSet = false; +let listenersAttached = false; // Single flag to track if any listeners are attached // Chart pane management let indicatorPanes = new Map(); @@ -35,14 +35,6 @@ const CATEGORY_MAP = { const DEFAULT_COLORS = ['#2962ff', '#26a69a', '#ef5350', '#ff9800', '#9c27b0', '#00bcd4', '#ffeb3b', '#e91e63']; const LINE_TYPES = ['solid', 'dotted', 'dashed']; -// Initialize -export function initIndicatorPanel() { - console.log('[IndicatorPanel] Initializing...'); - renderIndicatorPanel(); - setupEventListeners(); - console.log('[IndicatorPanel] Initialized'); -} - function getDefaultColor(index) { return DEFAULT_COLORS[index % DEFAULT_COLORS.length]; } @@ -92,6 +84,12 @@ function groupPlotsByColor(plots) { return Object.values(groups); } +export function initIndicatorPanel() { + console.log('[IndicatorPanel] Initializing...'); + renderIndicatorPanel(); + console.log('[IndicatorPanel] Initialized'); +} + export function getActiveIndicators() { return activeIndicators; } @@ -136,7 +134,7 @@ export function renderIndicatorPanel() { value="${searchQuery}" autocomplete="off" > - ${searchQuery ? `` : ''} + ${searchQuery ? `` : ''} @@ -167,7 +165,7 @@ export function renderIndicatorPanel() {
${activeIndicators.length} Active - ${activeIndicators.length > 0 ? `` : ''} + ${activeIndicators.length > 0 ? `` : ''}
${activeIndicators.slice().reverse().map(ind => renderActiveIndicator(ind)).join('')}
@@ -188,9 +186,9 @@ export function renderIndicatorPanel() { `; // Only setup event listeners once - if (!eventListenersSet) { + if (!listenersAttached) { setupEventListeners(); - eventListenersSet = true; + listenersAttached = true; } } @@ -220,19 +218,24 @@ function renderActiveIndicator(indicator) { const meta = getIndicatorMeta(indicator); const label = getIndicatorLabel(indicator); const isFavorite = userPresets.favorites?.includes(indicator.type) || false; - + const showPresets = meta.name && function() { + const hasPresets = typeof getPresetsForIndicator === 'function' ? getPresetsForIndicator(meta.name) : []; + if (!hasPresets || hasPresets.length === 0) return ''; + return `
+ +
`; + }(); + return `
-
+
⋮⋮
- ${label} -
- ${meta.name && renderPresetIndicatorIndicator(meta, indicator)} -
-
@@ -250,10 +253,10 @@ function renderActiveIndicator(indicator) { } function renderPresetIndicatorIndicator(meta, indicator) { - const hasPresets = getPresetsForIndicator(meta.name); + const hasPresets = typeof getPresetsForIndicator === 'function' ? getPresetsForIndicator(meta.name) : []; if (!hasPresets || hasPresets.length === 0) return ''; - return ``; + return ``; } function renderIndicatorConfig(indicator, meta) { @@ -271,7 +274,7 @@ function renderIndicatorConfig(indicator, meta) {
- +
@@ -280,19 +283,18 @@ function renderIndicatorConfig(indicator, meta) {
- ${LINE_TYPES.map(lt => ``).join('')}
- + ${indicator.params._lineWidth || 2}
- ${meta?.inputs && meta.inputs.length > 0 ? `
Parameters
@@ -300,61 +302,53 @@ function renderIndicatorConfig(indicator, meta) {
${input.type === 'select' ? - ` ${input.options.map(o => ``).join('')} ` : - `
- - -
` + `` }
`).join('')}
` : ''} -
Presets - +
- ${renderIndicatorPresets(indicator, meta)} + ${typeof renderIndicatorPresets === 'function' ? renderIndicatorPresets(indicator, meta) : ''}
-
- - + +
`; } function renderIndicatorPresets(indicator, meta) { - const presets = getPresetsForIndicator(meta.name); - const instance = new IR[indicator.type]({ type: indicator.type, params: indicator.params, name: indicator.name }); - const metadata = instance.getMetadata(); + const presets = typeof getPresetsForIndicator === 'function' ? getPresetsForIndicator(meta.name) : []; return presets.length > 0 ? `
- ${presets.map(preset => { - // Match values against current settings - const isApplied = metadata.inputs.every(input => - preset.values[input.name] === indicator.params[input.name] + ${presets.map(p => { + const isApplied = meta.inputs.every(input => + (indicator.params[input.name] === (preset.values?.[input.name] ?? input.default)) ); return `
- ${preset.name} - + ${preset.name} +
`; }).join('')} @@ -364,44 +358,59 @@ function renderIndicatorPresets(indicator, meta) { // Event listeners function setupEventListeners() { - // Event delegation for dynamically created elements const container = document.getElementById('indicatorPanel'); - if (container) { - // Add button - container.addEventListener('click', (e) => { - const addBtn = e.target.closest('.indicator-btn.add'); - if (addBtn) { - const type = addBtn.dataset.type; - if (type && window.addIndicator) { - window.addIndicator(type); - } + if (!container) return; + + console.log('[IndicatorPanel] Setting up event listeners...'); + + // Single event delegation handler for add button + container.addEventListener('click', (e) => { + const addBtn = e.target.closest('.indicator-btn.add'); + if (addBtn) { + e.stopPropagation(); + const type = addBtn.dataset.type; + if (type && window.addIndicator) { + console.log('[IndicatorPanel] Adding indicator:', type); + window.addIndicator(type); } - }); + return; + } - // Config / expand button - container.addEventListener('click', (e) => { - const expandBtn = e.target.closest('.indicator-btn.expand'); - if (expandBtn) { - const id = expandBtn.dataset.id; - if (id && window.toggleIndicatorExpand) { - window.toggleIndicatorExpand(id); - } + // Expand/collapse button + const expandBtn = e.target.closest('.indicator-btn.expand'); + if (expandBtn) { + e.stopPropagation(); + const id = expandBtn.dataset.id; + if (id && window.toggleIndicatorExpand) { + window.toggleIndicatorExpand(id); } - }); + return; + } // Remove button - container.addEventListener('click', (e) => { - const removeBtn = e.target.closest('.indicator-btn.remove'); - if (removeBtn) { - const id = removeBtn.dataset.id; - if (id && window.removeIndicatorById) { - window.removeIndicatorById(id); - } + const removeBtn = e.target.closest('.indicator-btn.remove'); + if (removeBtn) { + e.stopPropagation(); + const id = removeBtn.dataset.id; + if (id && window.removeIndicatorById) { + window.removeIndicatorById(id); } - }); - } + return; + } + + // Favorite button + const favoriteBtn = e.target.closest('.indicator-btn.favorite'); + if (favoriteBtn) { + e.stopPropagation(); + const type = favoriteBtn.dataset.type; + if (type && window.toggleFavorite) { + window.toggleFavorite(type); + } + return; + } + }); - // Search + // Search input const searchInput = document.getElementById('indicatorSearch'); if (searchInput) { searchInput.addEventListener('input', (e) => { @@ -410,6 +419,15 @@ function setupEventListeners() { }); } + // Search clear button + const searchClear = container.querySelector('.search-clear'); + if (searchClear) { + searchClear.addEventListener('click', (e) => { + searchQuery = ''; + renderIndicatorPanel(); + }); + } + // Category tabs document.querySelectorAll('.category-tab').forEach(tab => { tab.addEventListener('click', (e) => { @@ -417,6 +435,16 @@ function setupEventListeners() { renderIndicatorPanel(); }); }); + + // Clear all button + const clearAllBtn = container.querySelector('.clear-all'); + if (clearAllBtn) { + clearAllBtn.addEventListener('click', () => { + window.clearAllIndicators(); + }); + } + + console.log('[IndicatorPanel] Event listeners setup complete'); } // Actions @@ -434,7 +462,7 @@ window.clearAllIndicators = function() { activeIndicators = []; configuringId = null; renderIndicatorPanel(); -drawIndicatorsOnChart(); + drawIndicatorsOnChart(); }; function addIndicator(type) { @@ -469,7 +497,7 @@ function addIndicator(type) { configuringId = id; renderIndicatorPanel(); drawIndicatorsOnChart(); -}; +} window.toggleIndicatorExpand = function(id) { configuringId = configuringId === id ? null : id; @@ -492,7 +520,10 @@ window.toggleIndicatorVisibility = function(id) { }; window.toggleFavorite = function(type) { - const favorites = userPresets.favorites || []; + if (!userPresets) userPresets = {}; + if (!userPresets.favorites) userPresets.favorites = []; + + const favorites = userPresets.favorites; const idx = favorites.indexOf(type); if (idx >= 0) { @@ -536,9 +567,9 @@ window.resetIndicator = function(id) { if (!IndicatorClass) return; const instance = new IndicatorClass({ type: indicator.type, params: {}, name: indicator.name }); - const metadata = instance.getMetadata(); + const meta = instance.getMetadata(); - metadata.inputs.forEach(input => { + meta.inputs.forEach(input => { indicator.params[input.name] = input.default; }); @@ -546,7 +577,12 @@ window.resetIndicator = function(id) { drawIndicatorsOnChart(); }; -function removeIndicatorById(id) { +window.removeIndicator = function() { + if (!configuringId) return; + removeIndicatorById(configuringId); +}; + +window.removeIndicatorById = function(id) { const idx = activeIndicators.findIndex(a => a.id === id); if (idx < 0) return; @@ -562,7 +598,7 @@ function removeIndicatorById(id) { renderIndicatorPanel(); drawIndicatorsOnChart(); -} +}; function removeIndicatorByIndex(index) { if (index < 0 || index >= activeIndicators.length) return; @@ -571,8 +607,8 @@ function removeIndicatorByIndex(index) { // Presets function getPresetsForIndicator(indicatorName) { - const allPresets = Object.values(userPresets).flat().filter(p => typeof p === 'object' && p.name); - return allPresets.filter(p => p.indicatorName === indicatorName); + if (!userPresets || !userPresets.presets) return []; + return userPresets.presets.filter(p => p.indicatorName === indicatorName); } window.savePreset = function(id) { @@ -608,7 +644,7 @@ window.savePreset = function(id) { }; window.applyPreset = function(id, presetId) { - const allPresets = Object.values(userPresets).flat().filter(p => typeof p === 'object' && p.id); + const allPresets = (userPresets?.presets || []).filter(p => typeof p === 'object' && p.id); const preset = allPresets.find(p => p.id === presetId); if (!preset) return; @@ -626,7 +662,7 @@ window.applyPreset = function(id, presetId) { window.deletePreset = function(presetId) { if (!confirm('Delete this preset?')) return; - if (userPresets.presets) { + if (userPresets?.presets) { userPresets.presets = userPresets.presets.filter(p => p.id !== presetId); saveUserPresets(); renderIndicatorPanel(); @@ -821,6 +857,12 @@ export function drawIndicatorsOnChart() { }); } +// Export functions for module access +export const addIndicator = addIndicator; +export const removeIndicatorById = removeIndicatorById; +export const removeIndicatorByIndexFunction = removeIndicatorByIndex; +const removeIndicatorByIndex = removeIndicatorByIndex; + // Legacy compatibility functions window.renderIndicatorList = renderIndicatorPanel; window.toggleIndicator = addIndicator; @@ -832,20 +874,15 @@ window.showIndicatorConfig = function(id) { window.applyIndicatorConfig = function() { // No-op - config is applied immediately }; -window.removeIndicator = function() { - if (!configuringId) return; - removeIndicatorById(configuringId); -}; // Assign to window for backward compatibility -window.toggleIndicator = addIndicator; window.addIndicator = addIndicator; +window.toggleIndicator = addIndicator; window.removeIndicatorById = removeIndicatorById; -window.removeIndicatorByIndex = function(index) { +window.removeIndicatorByIndex = removeIndicatorByIndexWindow; +const removeIndicatorByIndexWindow = function(index) { if (index < 0 || index >= activeIndicators.length) return; removeIndicatorById(activeIndicators[index].id); }; -window.drawIndicatorsOnChart = drawIndicatorsOnChart; - -// Export functions for module imports -export { addIndicator, removeIndicatorById, removeIndicatorByIndex }; \ No newline at end of file +window.removeIndicatorByIndex = removeIndicatorByIndexWindow; +window.drawIndicatorsOnChart = drawIndicatorsOnChart; \ No newline at end of file