Adding an expand widget button in SisenseJS embedding [Linux]
Summary: Ivan Amoshyi, Ivan Amoshyi, provides a detailed guide on enhancing SisenseJS embedded dashboards by introducing an "Expand" widget button. This feature allows users to better utilize space by embedding smaller widget versions while still being able to view detailed charts in a modal, thereby improving data readability and user experience. The guide includes steps to set up HTML and CSS for modal styling, connect to SisenseJS, and manage widget rendering and modal functionality. Ivan emphasizes that while this feature benefits most chart types, it may not work for Pivot and Indicator widgets, and includes a disclaimer indicating the need for environment testing prior to deployment.
Introduction:
An article demonstrates a way to enhance SisenseJS embedded dashboards by adding an "Expand" widget button. This allows to use the space more efficiently by embedding smaller versions of widgets but granting users with an option to view larger, detailed versions of charts in a modal, improving data readability and user experience.
Step-by-Step Guide:
Step 1: HTML layout
A simple structure with a main container to render widgets in, div elements for overlay, a modal, a widget container inside a modal, and a button as a close icon.
<body>
<div id="sisenseApp" style="display: flex; width:100%">
<main id="main" style="display: flex; flex-direction: column; justify-content: center; width: 80%">
<!-- The Modal Overlay -->
<div class="overlay">
<div class="modal">
<div class="modalWidgetContainer"></div>
<button class="close-btn">
<svg>...</svg> <!-- Close Icon -->
</button>
</div>
</div>
</main>
</div>
</body>Step 2: Basic styles for the Modal
Add CSS to handle the visibility of the modal window and define the sizing for widget containers. The .isOpen class will be toggled dynamically later.
CSS
<style>
.overlay {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.2);
visibility: hidden;
z-index: 3;
}
.modal {
position: absolute;
inset: 5%;
background-color: white;
}
.close-btn {
position: absolute;
top: 5px;
right: 0;
cursor: pointer;
z-index: 5;
}
.isOpen {
opacity: 1;
visibility: visible;
}
.primaryWidgetContainer {
width: 400px;
height: 400px;
border: 1px solid sandybrown;
}
.modalWidgetContainer {
width: 100%;
height: 100%;
border: 1px solid sandybrown;
}
</style>Step 3: Connect to SisenseJS and Render Widgets
Dynamically load the Sisense.v1.js library, connect to Sisense instance, and fetch the widgets associated with a specific dashboard ID. For each widget loaded, we generate an "Expand" button.
javascript
const url = document.location.origin;
const dashboardId = "697aa04c63601f8c9e5a478c"; // Replace with your dashboard ID
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");
let app = window.Sisense.app;
if (!app) {
app = await Sisense.connect(url, true);
window.Sisense.app = app;
}
let dash = new Dashboard();
app.dashboards.add(dash);
// Fetch widgets from the dashboard
const widgetsRaw = await fetch(url + "/api/v1/dashboards/" + dashboardId + "/widgets?fields=oid", {
method: 'GET',
credentials: 'include'
}).then(r => r.json());
if (widgetsRaw.length) {
const promises = widgetsRaw.map(w => dash.widgets.load(w.oid));
const widgetsResolved = await Promise.all(promises);
widgetsResolved.forEach(widget => {
const groupWrapper = document.createElement('div');
const widgetContainerEl = document.createElement('div');
widgetContainerEl.classList.add('primaryWidgetContainer');
widgetContainerEl.setAttribute("id", "widget_" + widget.$$model.oid);
const expandBtnEl = document.createElement('button');
expandBtnEl.textContent = "Expand";
// Bind the modal open event with the current widget context
expandBtnEl.addEventListener('click', () => handleModalOpen(widget));
main.append(groupWrapper);
groupWrapper.append(widgetContainerEl, expandBtnEl);
// Render widget into primary container
widget.container = document.getElementById("widget_" + widget.$$model.oid);
});
dash.refresh();
}
}
startSisense();Step 4: Handling the Modal State and Widget Re-rendering
A SisenseJS widget cannot be simultaneously rendered in two different elements in the DOM. Therefore, when opening the modal, we must first destroy() the widget in the main view and initialize() it inside the modal container. We reverse this process when the user closes the modal.
const overlayEl = document.querySelector('.overlay');
const closeModalBtn = document.querySelector('.close-btn');
const modalWidgetContainer = document.querySelector('.modalWidgetContainer');
let activeWidget = null;
const handleModalOpen = (w) => {
overlayEl.classList.add('isOpen');
// Destroy the widget and repopulate inside the modal
w.destroy();
w.initialize();
w.container = modalWidgetContainer;
closeModalBtn.addEventListener('click', handleModalClose);
activeWidget = w;
}
const handleModalClose = () => {
overlayEl.classList.remove('isOpen');
if (activeWidget) {
// Reverse the process to push the widget back to the main view
activeWidget.destroy();
activeWidget.initialize();
activeWidget.container = document.getElementById("widget_" + activeWidget.$$model.oid);
}
closeModalBtn.removeEventListener('click', handleModalClose);
activeWidget = null;
}(Full index.html can be found in the comments section)
Note: This feature may be beneficial for all native chart types, such as Column, Line, Bar Charts, Scatter Plot/Map, etc. The exceptions are Pivot and Indicator widgets, which don’t get properly expanded.
Note: The example doesn’t include authentication and is designed to be tested within the Sisense itself. Upload the HTML file into /opt/sisense/storage/plugins and it will be accessible at your_sisense_url/plugins/sisensejs.html
Conclusion:
Adding an expansion feature is an effective way to improve the user experience with embedded dashboards, allowing clients to examine deeper chart details. By understanding that SisenseJS widgets require destruction and re-initialization before being assigned to a new DOM container, you can successfully move them freely across your application while maintaining high interactivity and performance.
References/Related Content
https://developer.sisense.com/guides/embeddingCharts/sisense.js/
https://developer.sisense.com/guides/embeddingCharts/jsGettingStarted.html
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.
Ivan Amoshyi
OP3 months ago<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <style> .overlay { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.2); opacity: 1; visibility: hidden; z-index: 3; } .modal { position: absolute; inset: 5%; background-color: white; } .close-btn { position: absolute; top: 5px; right: 0; background-color: transparent; border: none; cursor: pointer; z-index: 5; } .isOpen { opacity: 1; visibility: visible; } .primaryWidgetContainer { width: 400px; height: 400px; outline: 1px solid sandybrown; } .modalWidgetContainer { width: 100%; height: 100%; border: 1px solid sandybrown; } #expandIcon { cursor: pointer; display: flex; justify-content: flex-end; padding-block: 8px; } #expandIcon.hidden { display: none; } </style> </head> <body> <div id="sisenseApp" style="display: flex; width:100%"> <main id="main" style="display: flex; flex-direction: column; justify-content: center; width: 400px"> <div class="overlay"> <div class="modal"> <div class="modalWidgetContainer"></div> <button class="close-btn"><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="20" height="20" viewBox="0 0 50 50"> <path d="M 9.15625 6.3125 L 6.3125 9.15625 L 22.15625 25 L 6.21875 40.96875 L 9.03125 43.78125 L 25 27.84375 L 40.9375 43.78125 L 43.78125 40.9375 L 27.84375 25 L 43.6875 9.15625 L 40.84375 6.3125 L 25 22.15625 Z"> </path> </svg> </button> </div> </div> </main> <div id="expandIcon" class="hidden"> <svg viewBox="0 0 24 24" width="20" height="20" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title></title> <g id="Complete"> <g id="expand"> <g> <polyline data-name="Right" fill="none" id="Right-2" points="3 17.3 3 21 6.7 21" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> <line fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="10" x2="3.8" y1="14" y2="20.2"></line> <line fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="14" x2="20.2" y1="10" y2="3.8"></line> <polyline data-name="Right" fill="none" id="Right-3" points="21 6.7 21 3 17.3 3" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></polyline> </g> </g> </g> </g></svg> </div> </div> <script type="text/javascript"> const url = document.location.origin; const dashboardId = "69e842c1112197dde5420834"; // Add dashboard ID const overlayEl = document.querySelector('.overlay'); const closeModalBtn = document.querySelector('.close-btn'); const modalWidgetContainer = document.querySelector('.modalWidgetContainer'); let activeWidget = null; 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 handleModalOpen = (w) => { overlayEl.classList.add('isOpen'); w.destroy(); w.initialize(); w.container = modalWidgetContainer; closeModalBtn.addEventListener('click', handleModalClose); activeWidget = w; } const handleModalClose = () => { overlayEl.classList.remove('isOpen'); if (activeWidget) { activeWidget.destroy(); activeWidget.initialize(); activeWidget.container = document.getElementById("widget_" + activeWidget.$$model.oid); } closeModalBtn.removeEventListener('click', handleModalClose); activeWidget = null; } const renderdash = async () => { const main = document.getElementById("main") let app = window.Sisense.app; if (!app) { try { app = await Sisense.connect(url, true) window.Sisense.app = app } catch (error) { console.error(error) } } if (!app) return; let dash = new Dashboard(); app.dashboards.add(dash); window.Sisense.widgets = []; const widgetsRaw = await fetch(url + "/api/v1/dashboards/" + dashboardId + "/widgets?fields=oid", { method: 'GET', credentials: 'include' }).then(r => r.json()); if (widgetsRaw.length) { const promises = widgetsRaw.map(w => ( dash.widgets.load(w.oid) )) const widgetsResolved = await Promise.all(promises); widgetsResolved.forEach(widget => { const groupWrapper = document.createElement('div'); const widgetContainerEl = document.createElement('div'); widgetContainerEl.classList.add('primaryWidgetContainer'); widgetContainerEl.setAttribute("id", "widget_" + widget.$$model.oid); const titleEl = document.createElement('p') titleEl.innerText = widget.title const expandIcon = document.getElementById('expandIcon'); const expandBtnEl = expandIcon.cloneNode(true); expandBtnEl.classList.remove('hidden'); expandBtnEl.addEventListener('click', () => handleModalOpen(widget)) main.append(groupWrapper); groupWrapper.append(titleEl, expandBtnEl, widgetContainerEl); widget.container = document.getElementById("widget_" + widget.$$model.oid); }) dash.refresh(); } } startSisense() </script> </body> </html>