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.