Embed a Qlik Sense sheet using qlik-embed web components
Overview
In this tutorial, you’ll learn how to embed a sheet from a Qlik Sense app using qlik-embed, rather than the Capability API or iframe. While embedding a sheet is simpler using Capability API, this example demonstrates additional capability provided with the qlik-api.
A sheet in a Qlik Sense app is a container for one-to-many visualizations, and the sheet’s layout is accessible metadata in JSON that you can read to recreate the sheet programmatically. The benefit of this approach is that the layout of the sheet is created dynamically in the client and propagated to your web application without using an iframe.
Prerequisites
To use the example page in this guide, ensure that you have the following:
- An existing app ID and sheet ID to embed. This example uses this demo app that you can freely download and use.
- A SPA OAuth2 client configured.
Variable substitution and vocabulary
Throughout this tutorial, variables will be used to communicate value placement.
The variable substitution format is VARIABLE_NAME
or <VARIABLE_NAME>
. Here is a list of
variables that appear in the sample code.
Variable | Description |
---|---|
QLIK_TENANT_URL | The domain for the tenant you are accessing. Equivalent to tenant.region.qlikcloud.com . |
QLIK_OAUTH_CLIENT_ID | The clientId of the OAuth SPA you configured. |
QLIK_REDIRECT_URI | The Redirect URL for the OAuth2 client to provide an access token. |
QLIK_APP_ID | The unique identifier of the application. |
QLIK_SHEET_ID | The unique identifier of the sheet. |
This example loads these values from process.env
, which you can set in a .env
file
and reference when running the example with node.js 20+ using node --env-file=.env server.js
.
Sequence
The code example in this tutorial provides a basic, fully functional web page that executes the following sequence:
- Handles logging in to Qlik.
- Loads the qlik-embed web components and qlik-api resources.
- Connects to the app.
- Retrieves the sheet layout.
- Sets a target div with a CSS grid that matches the sheet’s layout.
- Iterates over each object within the sheet layout, creating a div for each and
setting it with the same grid position as in the layout. This process also
sets the
id
of each div to theobjectId
of the Qlik object. - Iterates over each div and injects each Qlik object whose
objectId
matches the correspondingid
.
Full example code
Below is an example built using qlik-api and qlik-embed web components.
Style sheet: web-component-sheet.css
body,
html {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 14px;
background-color: #f5f5f5;
color: #333;
height: 100%;
width: 100%;
}
div.flex-container {
display: flex;
flex-wrap: wrap;
margin: 0 45px 45px 0;
}
div.qvobject {
flex: 1 1 auto;
height: 300px;
min-width: 400px;
margin: 45px 0 0 45px;
}
JavaScript: web-component-sheet.js
import { auth, qix } from "https://cdn.jsdelivr.net/npm/@qlik/api/index.js";
// Retrieve settings from environment
const QLIK_TENANT_URL = process.env.QLIK_TENANT_URL;
const QLIK_OAUTH_CLIENT_ID = process.env.QLIK_OAUTH_CLIENT_ID;
const QLIK_APP_ID = process.env.QLIK_APP_ID;
const QLIK_SHEET_ID = process.env.QLIK_SHEET_ID;
const QLIK_REDIRECT_URI = process.env.QLIK_REDIRECT_URI;
// OAuth authentication settings
const hostConfig = {
host: QLIK_TENANT_URL,
authType: "oauth2",
clientId: QLIK_OAUTH_CLIENT_ID,
redirectUri: QLIK_REDIRECT_URI
};
auth.setDefaultHostConfig(hostConfig);
// DEFAULT PLACEMENT CONFIGURATION
const qlikDivSelector = "#sheet";
const qlikObjectClass = "qlikObject";
// MAIN
(async function main() {
const app = await doCapabilities(qlikDivSelector, qlikObjectClass);
})();
// CREATE GRID
function createGrid(querySelector, columns, rows) {
const gridContainer = document.querySelector(querySelector);
gridContainer.style.display = "grid";
gridContainer.style.gridTemplateRows = `repeat(${rows}, minmax(0, 1fr)`;
gridContainer.style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
return gridContainer;
}
// CONSTRUCT SHEET
function constructSheet(sheetLayout, querySelector, addClass) {
const gridContainer = createGrid(querySelector, sheetLayout.columns, sheetLayout.rows);
sheetLayout.cells.forEach(function (object) {
const objectId = object.name;
const colStart = object.col + 1;
const rowStart = object.row + 1;
const colSpan = object.colspan;
const rowSpan = object.rowspan;
const objectDiv = document.createElement("div");
objectDiv.id = objectId;
objectDiv.style["grid-column"] = `${colStart} / span ${colSpan}`;
objectDiv.style["grid-row"] = `${rowStart} / span ${rowSpan}`;
objectDiv.style.border = "2px solid #0000";
objectDiv.style.margin = "5px";
objectDiv.style.width = "100%";
objectDiv.classList.add(addClass);
gridContainer.appendChild(objectDiv);
});
return true;
}
// LOAD & RENDER CAPABILITIES
async function doCapabilities(qlikDivSelector, objectClass) {
try {
const session = qix.openAppSession(QLIK_APP_ID);
const app = await session.getDoc();
if (app) {
const sheetObject = await app.getObject(QLIK_SHEET_ID);
const sheetLayout = await sheetObject.getLayout();
constructSheet(sheetLayout, qlikDivSelector, objectClass);
const elements = document.querySelectorAll(`.${objectClass}`);
elements.forEach(async (element) => {
const qlikdiv = document.createElement("qlik-embed");
qlikdiv.setAttribute("ui", "analytics/chart");
qlikdiv.setAttribute("app-id", QLIK_APP_ID);
qlikdiv.setAttribute("object-id", element.getAttribute("id"));
document.querySelector(`#${element.getAttribute("id")}`).appendChild(qlikdiv);
});
}
} catch (error) {
console.error("Error in doCapabilities:", error);
}
}
HTML: web-component-sheet.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components@1/dist/index.min.js"></script>
<link rel="stylesheet" href="web-component-sheet.css" />
</head>
<body>
<div id="sheet" style="height: 1200px;"></div>
<script type="module" src="web-component-sheet.js"></script>
</body>
</html>