Knowledge Base Article

How to apply widget filters through code with SisenseJS [Linux/Windows]

This article shows how to programmatically apply widget filters in Sisense dashboards using SisenseJS, enabling dynamic, per-widget filtering based on user input. Tested in W2025.3 and L2025.4. It works for both cloud and on-premises deployments.

Step-by-Step Guide

Overview

We will demonstrate how to build a UI that allows end-users to filter individual dashboard widgets by year. The solution leverages SisenseJS, JavaScript, and HTML. See the provided complete code sample below.

Step 1: Prepare Your Environment

Ensure that SisenseJS is available from your Sisense deployment (both Linux and Windows supported), and that you know your dashboard ID and host URL.

Screenshot Example:
To help you get started, here’s how your UI might look after implementation:


The dashboard displays widgets, each with its own year filter control.

Step 2: Implement the HTML & Script

Below is a complete HTML file (HTML + JavaScript).

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
  <div id="sisenseApp" style="display: flex; width: 100%">
    <div id="main" style="display: flex; flex-direction: column; justify-content: center; width: 80%;"></div>
    <div id="filters" style="width: 20%;"></div>
  </div>
<script type="text/javascript">
const url = ""; // Your host URL
const dashboardId = ""; // Your Dashboard ID

let app, dash;

// Store per-widget initial year options
let widgetInitialYears = {};

const silentLogin = async () => {
  let isauth = await fetch(url + "/api/auth/isauth", { credentials: "include" });
  let isauthJson = await isauth.json();
  if (!isauthJson.isAuthenticated && isauthJson.ssoEnabled) {
    var iframe = document.createElement("iframe");
    iframe.src = isauthJson.loginUrl + "?return_to=/api/auth/isauth";
    iframe.style.display = "none";
    document.body.append(iframe);
    iframe.onload = function () {
      document.body.removeChild(iframe);
      startSisense();
    };
  } else {
    startSisense();
  }
};

const startSisense = async () => {
  const sisensejs = document.createElement("script");
  sisensejs.src = url + "/js/sisense.v1.js";
  sisensejs.onload = async () => {
    await renderdash();
  };
  document.head.append(sisensejs);
};

const renderdash = async () => {
  const main = document.getElementById("main");
  main.innerHTML = '';
  app = await Sisense.connect(url, true);
  dash = await app.dashboards.load(dashboardId);

  // Fetch widgets info
  const widgetsRaw = await fetch(
    url +
      "/api/v1/dashboards/" +
      dashboardId +
      "/widgets?fields=oid,title",
    { method: "GET", credentials: "include" }
  );
  let widgets = await widgetsRaw.json();

  if (widgets.length) {
    widgets.forEach((w) => {
      const widgetRow = document.createElement("div");
      widgetRow.style.display = "flex";
      widgetRow.style.alignItems = "flex-start";
      widgetRow.style.marginBottom = "32px";

      const widgetSection = document.createElement("div");
      widgetSection.style.width = "84%";
      let title = document.createElement("p");
      title.innerText = w.title || w.oid;
      widgetSection.append(title);

      const widgetElement = document.createElement("div");
      widgetElement.style.width = "100%";
      widgetElement.style.height = "400px";
      widgetElement.setAttribute("id", "widget_" + w.oid);
      widgetSection.append(widgetElement);
      widgetRow.append(widgetSection);

      const filterSection = document.createElement("div");
      filterSection.style.width = "16%";
      filterSection.style.paddingLeft = "32px";

      let filterLabel = document.createElement("div");
      filterLabel.innerText = "Filter by year:";

      // Multi-select: create the select control ONCE
      let yearSelect = document.createElement("select");
      yearSelect.multiple = true;
      yearSelect.size = 8;
      yearSelect.style.minWidth = "80px";

      let applyBtn = document.createElement("button");
      applyBtn.innerText = "Apply";
      applyBtn.style.marginLeft = "8px";

      filterSection.append(filterLabel);
      filterSection.append(yearSelect);
      filterSection.append(applyBtn);

      widgetRow.append(filterSection);
      main.append(widgetRow);

      // Sisense widget object
      const widget = dash.widgets.get(w.oid);
      widget.container = widgetElement;

      // Populate dropdown only on the first domready, only if filter is NOT present
      let hasPopulated = false;

      widget.$$model.on("domready", () => {
        widgetElement.style.border = "2px grey solid";
        let filtersPanel = widget.metadata.panel('filters');
        let filterExists = false;
        // Check if any "Commerce.Date" filter is present
        for (let f of filtersPanel.items) {
          if (f.jaql && f.jaql.table === "Commerce" && f.jaql.column === "Date") {
            filterExists = true;
            break;
          }
        }

        // Only populate dropdown ONCE, and only if no filter is present (first run)
        if (!hasPopulated && !filterExists) {
          // Years from rawQueryResult.value
          let queryValues =
            widget.$$model.rawQueryResult &&
            widget.$$model.rawQueryResult.values
              ? widget.$$model.rawQueryResult.values
              : [];

          yearSelect.innerHTML = "";
          let years = [];

          if (queryValues && queryValues.length) {
            years = queryValues.map(row => {
              const y = row[0];
              if (!y) return undefined;
              if (y.text && y.text.length === 4) return y.text;
              if (y.data && typeof y.data === "string" && y.data.length >= 4) return y.data.substring(0, 4);
              return undefined;
            }).filter(val => val);

            years = Array.from(new Set(years)).sort((a, b) => b - a);
          }

          // Add "All" option
          years = ["", ...years];

          // Store these initial years for later use
          widgetInitialYears[w.oid] = years;

          years.forEach(val => {
            let option = document.createElement("option");
            option.value = val;
            option.text = val || "All";
            yearSelect.append(option);
          });
          yearSelect.size = years.length > 1 ? years.length : 2;
          hasPopulated = true;
        }
      });

      // Apply year filter logic
      applyBtn.onclick = async function () {
        const filtersPanel = widget.metadata.panel('filters');

        // Remove all existing Commerce.Date filters
        for (let i = filtersPanel.items.length - 1; i >= 0; i--) {
          const f = filtersPanel.items[i];
          if (f.jaql && f.jaql.table === "Commerce" && f.jaql.column === "Date") {
            filtersPanel.items.splice(i, 1);
          }
        }

        // Collect selected years, ignoring "All"
        const selectedYears = Array.from(yearSelect.selectedOptions)
          .map(opt => opt.value)
          .filter(val => val);

        if (selectedYears.length) {
          filtersPanel.items.push({
            jaql: {
              table: "Commerce",
              column: "Date",
              dim: "[Commerce.Date (Calendar)]",
              datatype: "datetime",
              level: "years",
              title: "Years in Date",
              filter: {
                explicit: true,
                members: selectedYears.map(year => year + "-01-01T00:00:00")
              }
            }
          });
        }
        widget.refresh();

        // ON SUBSEQUENT REFRESHES, always re-populate with original initial values
        // if the widget gets re-rendered (filters change), don't update options
        yearSelect.innerHTML = "";
        // Use widgetInitialYears[w.oid] to rebuild the select
        let years = widgetInitialYears[w.oid] || [];
        years.forEach(val => {
          let option = document.createElement("option");
          option.value = val;
          option.text = val || "All";
          yearSelect.append(option);
        });
        yearSelect.size = years.length > 1 ? years.length : 2;
      };
    });
  }
  dash.refresh();
};

silentLogin();
</script>
</body>
</html>

Replace url and dashboardId with your actual Sisense deployment details.

Step 3: Filter Logic

The script dynamically loads all widgets in the dashboard and creates a per-widget filter dropdown for years. When you select one or more years, clicking 'Apply' will update the filter(s) for that widget only.

Screenshot Example:

Code explanation:

  • Silent authentication ensures users are logged in via Sisense or SSO.
  • SisenseJS is loaded dynamically.
  • Widgets and UI elements are drawn programmatically.
  • When filters are applied, the script updates the widget filter panel in-place and refreshes the widget.

Conclusion

This approach provides a robust way to let users filter widget data by year. This direct control improves interactivity and allows for more precise data exploration, all without modifying dashboard or widget definitions in MongoDB. The knowledge in this article can be used to build your own custom solution, customizing the filters UI and adapting the logic accordingly.

References/Related Content


Disclaimer: This post outlines a potential custom workaround for a specific use case or provides instructions regarding a specific task. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

Published 12-02-2025
No CommentsBe the first to comment