PageSourceSearch

https://insights.market/static/js/model.js

js insights.market collected 2026-10-06 13:18:39 UTC 15,842 bytes, 510 lines download raw bytes

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}')">&times;</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.