1let allColumns, currentModalSource = null; 2let lockedMetrics = new Set(); 3let paidMetrics = new Set(); 4const urlforsymbol = decodeURIComponent(Utils.captureUrl()); // "/details/CIPLA" 5// if this is watchlist page then empty string else Extract symbol from URL (last segment) 6// this might cause bugs if URL structure changes, ideally symbol should be passed explicitly when opening modal for other pages 7const symbol = urlforsymbol.split("/").pop() === 'watchlist' ? '' : urlforsymbol.split("/").pop(); 8const SEARCH_RESULT_TAB = "Search Result"; 9let baseTabs = [], selectedTab = "Price", searchResultColumns = []; 10let selectedColumnsByModal = { ratioModelBtn: [], peerModalBtn: [], indModalBtn: [], addMetricBtn: [] }; 11 12/* ------------------ UTILS ------------------ */ 13const getSelectedColumns = () => selectedColumnsByModal[currentModalSource] || []; 14const setSelectedColumns = cols => { selectedColumnsByModal[currentModalSource] = cols; }; 15 16const typeMap = { 17 ratioModelBtn: "ratio", 18 peerModalBtn: "peer", 19 indModalBtn: "ind-sec", 20 stockFinderBtn: "stock-finder", 21 addMetricBtn: "watchlist-metric" 22}; 23 24/* ------------------ FETCH ------------------ */ 25async function fetch_data(type) { 26 try { 27 28 const url = Utils.getAllUrls().getallmodelparam 29 const payload = { type, symbol }; 30 console.log(type, 'type in fetch data', payload); 31 const response = await Utils.ajaxPost(url, payload); 32 if (response && response.columns) { 33 allColumns = response.columns; 34 paidMetrics = new Set(response.paid_metrics || []); 35 lockedMetrics = new Set(response.locked_metrics || []); 36 } else { 37 allColumns = response; 38 paidMetrics = new Set(); 39 lockedMetrics = new Set(); 40 } 41 baseTabs = Object.keys(allColumns); 42 } catch (err) { console.error("Error loading deals data:", err); } 43} 44// fetch_data(); This Will One Once The 45async function fetchDataFor(type, cols = []) { 46 try { 47 const url = Utils.getAllUrls().getAllModelChip; 48 const payload = { columns: cols, symbol, type }; 49 console.log(payload) 50 const response = await Utils.ajaxPost(url, payload); 51 if (response?.status === "success") { 52 53 const currentType = typeMap[currentModalSource]; 54 if (currentType === "peer") { 55 if (typeof window.fetchPeerData === "function") { 56 window.fetchPeerData(); 57 closeModal(); 58 } else { 59 console.error(" fetchPeerData() not found"); 60 } 61 } 62 63 else if (currentType === "ind-sec") { 64 if (typeof window.fetchIndSecStocksTable === "function") { 65 window.fetchIndSecStocksTable(); 66 closeModal(); 67 } else { 68 console.error(" fetchIndSecStocksTable() not found"); 69 } 70 } 71 72 else if (currentType === "watchlist-metric") { 73 closeModal(); 74 (async () => { 75 try { 76 const module = await import("./watchlist/watchlist.js"); 77 78 if (typeof module.getWatchListReport === "function") { 79 module.getWatchListReport({ watchlistId: module.currentWatchlist, tab: module.currentStockParameterTab }); 80 } else { 81 console.error("getWatchListReport() not found in watchlist.js"); 82 } 83 } catch (err) { 84 console.error("Failed to load watchlist.js:", err); 85 } 86 })(); 87 88 } 89 90 else if (currentType === "ratio") { 91 if (typeof window.loadOverviewData === "function") { 92 await window.loadOverviewData(symbol); // reload API 93 window.renderOverviewData(); // rerender active tab 94 closeModal(); 95 } else { 96 console.error(" loadOverviewData() not found in global scope"); 97 } 98 } 99 100 else { 101 console.warn(" Unknown modal type:", currentType); 102 } 103 104 } else { 105 console.warn("API did not return success status"); 106 } 107 108 } catch (err) { 109 console.error("fetchDataFor() error:", err); 110 } 111} 112 113async function setOverviewColumnsAsSelected() { 114 try { 115 const url = Utils.getAllUrls().geOveraltAllModelChip 116 const tableMap = { 117 ratioModelBtn: "user_ratio", 118 peerModalBtn: "user_peer_ratio", 119 indModalBtn: "user_ind_sec", 120 addMetricBtn: "user_watchlist_metric" 121 }; 122 const tablename = tableMap[currentModalSource]; 123 const type = typeMap[currentModalSource]; 124 const response = await Utils.ajaxPost(url, { tablename, symbol: symbol, type: type }); 125 if (response) { 126 setSelectedColumns(Array.isArray(response) ? response : Object.keys(response)); 127 renderChips(); 128 renderCheckboxes(); 129 } 130 } catch (err) { console.error("Error setting overview columns:", err); } 131} 132 133/* ------------------ RENDER ------------------ */ 134function renderTabs() { 135 const tabBar = document.getElementById('tabBar'); 136 tabBar.innerHTML = ""; 137 138 const tabsToRender = searchResultColumns.length > 0 139 ? [SEARCH_RESULT_TAB, ...baseTabs] 140 : baseTabs; 141 142 tabsToRender.forEach(tab => { 143 const btn = document.createElement('button'); 144 btn.textContent = tab; 145 btn.type = "button"; 146 147 btn.className = ` 148 min-w-max px-4 py-2 text-sm font-semibold rounded-t-lg transition cursor-pointer 149 ${selectedTab === tab 150 ? "bg-blue-600 text-white shadow-md border border-blue-600" 151 : "bg-gray-100 text-gray-700 border border-gray-300 hover:bg-blue-100 hover:text-blue-700"} 152 `; 153 154 if (selectedTab === tab) btn.style.zIndex = 10; 155 156 btn.onclick = () => { 157 selectedTab = tab; 158 document.getElementById("columnSearch").value = ""; 159 searchResultColumns = []; 160 renderTabs(); 161 renderCheckboxes(); 162 adjustModalWidth(); 163 }; 164 165 tabBar.appendChild(btn); 166 }); 167} 168 169 170function renderCheckboxes() { 171 const container = document.getElementById("columnsContainer"); 172 const searchVal = document.getElementById("columnSearch").value.trim().toLowerCase(); 173 let cols = selectedTab === SEARCH_RESULT_TAB ? searchResultColumns : allColumns[selectedTab] || []; 174 if (searchVal) cols = cols.filter(c => c.toLowerCase().includes(searchVal)); 175 176 if (!cols.length) { 177 container.innerHTML = `<div class="text-gray-400 col-span-3">No columns found.</div>`; 178 return; 179 } 180 181 const selected = getSelectedColumns(); 182 container.innerHTML = ""; 183
184 cols.forEach(col => { 185 const id = selectedTab + "_" + col.replace(/\W+/g, "_"); 186 const isPaidMetric = paidMetrics.has(col); 187 const isLockedMetric = lockedMetrics.has(col); 188 189 const wrapper = document.createElement("label"); 190 wrapper.setAttribute("for", id); 191 192 wrapper.className = ` 193 flex items-start gap-3 194 p-2 rounded-md 195 ${isLockedMetric ? "cursor-not-allowed text-gray-300" : "hover:bg-gray-50 cursor-pointer"} 196 `; 197 198 // if (isLockedMetric) { 199 // wrapper.style.filter = "blur(1px)"; 200 // } 201 202 wrapper.innerHTML = ` 203 <input 204 type="checkbox" 205 id="${id}" 206 value="${col}" 207 ${selected.includes(col) ? "checked" : ""} 208 ${(isLockedMetric || (selected.length >= 5 && !selected.includes(col))) ? "disabled" : ""} 209 class="h-5 w-5 mt-1 shrink-0 accent-blue-600" 210 /> 211 <span class="${isLockedMetric ? 'text-gray-300' : 'text-gray-800'} text-sm leading-snug break-words"> 212 ${col}${isPaidMetric ? ' <i class="fa-solid fa-crown" style="color: #fdcd21;"></i>' : ''} 213 </span> 214 `; 215 216 wrapper.querySelector("input").addEventListener("change", e => { 217 let cols = getSelectedColumns(); 218 if (e.target.checked) { 219 if (cols.length < 5) cols.push(col); 220 else { 221 e.target.checked = false; 222 alert("You can select up to 5 columns only."); 223 } 224 } else { 225 cols = cols.filter(x => x !== col); 226 } 227 228 setSelectedColumns(cols); 229 renderChips(); 230 renderCheckboxes(); 231 }); 232 233 if (isLockedMetric) { 234 wrapper.addEventListener("click", e => { 235 e.preventDefault(); 236 Utils.showToast("This parameter is available for subscribed users only", "error"); 237 }); 238 } 239 240 container.appendChild(wrapper); 241 }); 242} 243 244function renderChips() { 245 const container = document.getElementById("chipContainer"); 246 const limitMsg = document.getElementById("chipLimitMsg"); 247 248 container.innerHTML = ""; 249 250 const selected = getSelectedColumns(); 251 252 selected.forEach(col => { 253 container.innerHTML += ` 254 <span class="bg-gray-100 px-4 py-2 rounded-lg text-gray-800 flex items-center text-sm font-medium mr-2 mb-2 inline-flex"> 255 <span>${col}</span> 256 <button style="margin-left:6px; color:#666; font-weight:bold; font-size:16px; cursor:pointer;" 257 onclick="removeColumnChip('${col}')">×</button> 258 </span>`; 259 }); 260 261 if (limitMsg) { 262 if (selected.length >= 5) { 263 limitMsg.classList.remove("hidden"); 264 } else { 265 limitMsg.classList.add("hidden"); 266 } 267 } 268 269 updateActionButtons(); 270} 271 272 273/* ------------------ ACTIONS ------------------ */ 274function removeColumnChip(col) { 275 setSelectedColumns(getSelectedColumns().filter(c => c !== col)); 276 renderChips(); renderCheckboxes(); 277} 278 279async function clearColumns() { 280 setSelectedColumns([]); 281 renderChips(); 282 renderCheckboxes(); 283 284 const tableMap = { 285 ratioModelBtn: "user_ratio", 286 peerModalBtn: "user_peer_ratio", 287 indModalBtn: "user_ind_sec", 288 addMetricBtn: "user_watchlist_metric" 289 }; 290 291 292 const tablename = tableMap[currentModalSource]; 293 const currentType = typeMap[currentModalSource]; 294 295 if (!currentModalSource || !currentType) return; 296 if (currentModalSource === "stockFinderBtn") { 297 return; 298 } 299 try { 300 const url = Utils.getAllUrls().clearAllModelChip; 301 const payload = { type: currentType, symbol, tablename }; 302 const response = await Utils.ajaxPost(url, payload); 303 304 if (response?.status === "success") { 305 closeModal(); 306 307 /* =============================== 308 Reload based on type 309 =============================== */ 310 311 if (currentType === "peer") { 312 if (typeof window.fetchPeerData === "function") { 313 console.log(" Reloading Peer Table"); 314 window.fetchPeerData(); 315 } else { 316 console.error(" fetchPeerData() not found"); 317 } 318 } 319 320 else if (currentType === "ind-sec") { 321 if (typeof window.fetchIndSecStocksTable === "function") { 322 console.log("Reloading Industry Table"); 323 window.fetchIndSecStocksTable(); 324 } else { 325 console.error(" fetchIndSecStocksTable() not found"); 326 } 327 } 328 329 else if (currentType === "ratio") { 330 if (typeof window.loadOverviewData === "function") { 331 console.log(" Reloading Ratio Overview"); 332 await window.loadOverviewData(symbol); // reload API 333 window.renderOverviewData(); // rerender active tab 334 } else { 335 console.error("loadOverviewData() not found in global scope"); 336 } 337 } 338 339 else if (currentType === "watchlist-metric") { 340 const module = await import("./watchlist/watchlist.js"); 341 342 if (typeof module.getWatchListReport === "function") { 343 module.getWatchListReport({ watchlistId: module.currentWatchlist, tab: module.currentStockParameterTab }); 344 } else { 345 console.error("getWatchListReport() not found in watchlist.js"); 346 } 347 } 348 349 } else { 350 console.warn(" Clear API did not return success"); 351 } 352 353 } catch (err) { 354 console.error(" clearColumns() error:", err); 355 } 356} 357 358async function saveColumns() { 359 const btn = document.getElementById("saveColumnsBtn"); 360 361 if (btn) { 362 btn.disabled = true; 363 btn.innerText = "Adding..."; 364 } 365 366 try { 367 const cols = getSelectedColumns(); 368 369 if (currentModalSource === "stockFinderBtn") return; 370 371 // If user saves with no selection, treat it as full cleanup. 372 if (!cols.length) { 373 await clearColumns(); 374 return; 375 } 376 377 if (cols.length > 5) return; 378 379 await fetchDataFor(typeMap[currentModalSource], cols); 380 381 } catch (err) { 382 console.error("saveColumns() error:", err); 383 } finally { 384 updateSaveButtonLabel(); // restore button text 385 if (btn) btn.disabled = false;
386 } 387} 388 389 390/* ------------------ MODAL ------------------ */ 391function adjustModalWidth() { 392 const modal = document.getElementById("modal"); 393 if (!modal) return; 394 395 // 20% less than full width â 80% width 396 const width = window.innerWidth * 0.8; 397 398 modal.style.width = `${width}px`; 399 modal.style.maxWidth = `${width}px`; 400 modal.style.margin = "0 auto"; 401} 402 403async function openModal(source) { 404 currentModalSource = source; 405 updateSaveButtonLabel() 406 const type = typeMap[source]; 407 // console.log(source) 408 if (source === "stockFinderBtn") { 409 await fetch_data(type); 410 if (saved_query_data && saved_query_data?.extra_cols && saved_query_data?.extra_cols.length > 0) { 411 const extra_cols = saved_query_data.extra_cols; 412 setSelectedColumns(Array.from(new Set(extra_cols))); 413 renderChips(); 414 renderCheckboxes(); 415 } 416 417 } else { 418 await fetch_data(type); await setOverviewColumnsAsSelected(); 419 } 420 421 document.getElementById('modalOverlay').style.display = 'flex'; 422 renderTabs(); renderChips(); renderCheckboxes(); requestAnimationFrame(adjustModalWidth); 423} 424const closeModal = () => { document.getElementById('modalOverlay').style.display = 'none'; currentModalSource = null; }; 425 426/* ------------------ EVENTS ------------------ */ 427document.addEventListener("DOMContentLoaded", () => { 428 ["ratioModelBtn", "peerModalBtn", "indModalBtn", "stockFinderBtn", "addMetricBtn"].forEach(id => { 429 const btn = document.getElementById(id); 430 if (btn) { 431 btn.addEventListener("click", () => { 432 if (Utils.isLoggedIn()) { 433 openModal(id); 434 } else { 435 window.location.href = `${Utils.getAllUrls().loginUrl}?redirecturl=${Utils.captureUrl()}` 436 } 437 }); 438 } 439 }); 440 441 const closeModalBtn = document.getElementById("closeModalBtn"); 442 if (closeModalBtn) { 443 closeModalBtn.setAttribute("type", "button"); 444 closeModalBtn.setAttribute("aria-label", "Close modal"); 445 closeModalBtn.setAttribute("title", "Close modal"); 446 closeModalBtn.addEventListener("click", closeModal); 447 } 448 449 const searchInput = document.getElementById("columnSearch"); 450 if (searchInput) { 451 searchInput.setAttribute("aria-label", "Search columns"); 452 searchInput.setAttribute("title", "Search columns"); 453 searchInput.addEventListener("input", e => { 454 if (!Utils.isLoggedIn()) { 455 alert("Please log in to use search."); 456 return; 457 } 458 459 const query = e.target.value.toLowerCase(); 460 searchResultColumns = []; 461 462 if (query) { 463 Object.values(allColumns).forEach(cols => 464 cols.forEach(c => { 465 if (c.toLowerCase().includes(query)) searchResultColumns.push(c); 466 }) 467 ); 468 selectedTab = SEARCH_RESULT_TAB; 469 } else { 470 selectedTab = "Price"; 471 } 472 473 renderTabs(); 474 renderCheckboxes(); 475 }); 476 } 477}); 478 479 480function updateActionButtons() { 481 const saveBtn = document.getElementById("saveColumnsBtn"); 482 const clearBtn = document.getElementById("clearColumnsBtn"); 483 484 if (saveBtn) { 485 saveBtn.disabled = false; 486 saveBtn.classList.remove("opacity-50", "cursor-not-allowed"); 487 } 488 489 if (clearBtn) { 490 clearBtn.disabled = false; 491 clearBtn.classList.remove("opacity-50", "cursor-not-allowed"); 492 } 493} 494 495function updateSaveButtonLabel() { 496 const saveBtnLabels = { 497 ratioModelBtn: "Save", 498 peerModalBtn: "Add Column", 499 indModalBtn: "Add Column", 500 stockFinderBtn: "Add Column", 501 addMetricBtn: "Save" 502 }; 503 504 const btn = document.getElementById("saveColumnsBtn"); 505 const btn2 = document.getElementById("headingrationame"); 506 if (!btn) return; 507 508 btn.textContent = saveBtnLabels[currentModalSource] || "Save"; 509 btn2.textContent = saveBtnLabels[currentModalSource] || "Save"; 510}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.