Embed a Qlik Sense Sheet using Capability API
Contributed by Daniel Pilla
Overview
Note: This tutorial leverages the Capability API , however the same method can be achieved with nebula.js provided that the visualizations are available.
In this tutorial, you are going to learn how to embed a sheet from a Qlik Sense application using the Capability API instead of embedding the sheet using an iframe. While an iframe is an easy mechanism for embedding content, there may be circumstances related to security, responsive design, and more where rendering the sheet with JavaScript is a better fit.
A sheet in a Qlik Sense application 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.
Contents
Configuration
The code example in this tutorial provides a basic, fully functional web page. To use this example page, ensure you have the following:
- An existing application ID and sheet ID to embed.
- A web integration configured which contains the location of the web page that will be embedding the Qlik Cloud application.
- A content security
policy
entry with the
frame-ancestors
directive added for the location of the web page that will be embedding the Qlik Cloud application.
To configure the code, the following variables from the sample code must be filled in:
const TENANT = '<tenant>.<region>.qlikcloud.com';
const WEBINTEGRATIONID = '<web-integration-id>';
const APPID = '<app-id>';
const SHEETID = '<sheet-id>';
const IDENTITY = '<identity';
Note: The
IDENTITY
> parameter is an arbitrary string to establish a separate session state. This can be used across iframes/varying methods of embedding to tie and/or separate sessions accordingly. It is an optional parameter included in this example for illustrative purposes.
Sequence
The code in this sample ultimately does the following:
- Handles logging into Qlik.
- Loads the Capability API resources.
- Connects to the application.
- Gets the layout of the sheet.
- 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 whos
objectId
that matches the correspondingid
.
Sample code
The sample code uses jQuery to showcase sheet rendering with the Capability API, however, it's not a required component for the solution. In addition, the below code includes sample handling for lack of browser support for third-party cookies. Refer to this article for more information.
<html>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
</head>
<body>
<div id="sheet" style="height:800px;">
</div>
<script>
// CONFIGURATION
const TENANT = '<tenant>.<region>.qlikcloud.com';
const WEBINTEGRATIONID = '<web-integration-id>';
const APPID = '<app-id>';
const SHEETID = '<sheet-id>';
const IDENTITY = '<identity>';
// DEFAULT PLACEMENT CONFIGURATION
const qlikDivSelector = '#sheet';
const qlikObjectClass = 'qlikObject';
// MAIN
(async function main() {
const isLoggedIn = await qlikLogin();
const loadedCapabilitiesAssets = await loadCapabilitiesAssets();
const app = await doCapabilities(APPID, qlikDivSelector, qlikObjectClass);
})();
// LOGIN
async function qlikLogin() {
const loggedIn = await fetch(`https://${TENANT}/api/v1/users/me`, {
mode: 'cors',
credentials: 'include',
headers: {
'qlik-web-integration-id': WEBINTEGRATIONID,
},
})
if (loggedIn.status !== 200) {
if (sessionStorage.getItem('tryQlikAuth') === null) {
sessionStorage.setItem('tryQlikAuth', 1);
window.location = `https://${TENANT}/login?qlik-web-integration-id=${WEBINTEGRATIONID}&returnto=${location.href}`;
return await new Promise(resolve => setTimeout(resolve, 10000)); // prevents further code execution
} else {
sessionStorage.removeItem('tryQlikAuth');
const message = 'Third-party cookies are not enabled in your browser settings and/or browser mode.';
alert(message);
throw new Error(message);
}
}
sessionStorage.removeItem('tryQlikAuth');
console.log('Logged in!');
return true;
}
// 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.classList.add(addClass);
gridContainer.appendChild(objectDiv);
})
return true;
}
// LOAD & RENDER CAPABILITIES
async function loadCapabilitiesAssets() {
const cssUrl = `https://${TENANT}/resources/autogenerated/qlik-styles.css`;
const requireUrl = `https://${TENANT}/resources/assets/external/requirejs/require.js`;
return $.when(
$('head').append(`<link rel="stylesheet" type="text/css" href="${cssUrl}">`),
$.getScript(requireUrl)
);
}
async function doCapabilities(appId, qlikDivSelector, objectClass) {
var config = {
host: TENANT,
prefix: "/",
port: 443,
isSecure: true,
webIntegrationId: WEBINTEGRATIONID,
identity: IDENTITY
};
require.config({
config: {
text: { useXhr: function (url, protocol, hostname, port) { return true; } }
},
baseUrl: 'https://' + config.host + (config.port ? ':' + config.port : '') + config.prefix + 'resources',
webIntegrationId: WEBINTEGRATIONID
});
requirejs(["js/qlik"], (qlik) => {
const app = qlik.openApp(appId, config);
const sheetObject = app.getObject(SHEETID)
.then((sheetObject) => {
const sheetLayout = sheetObject.getLayout();
constructSheet(sheetLayout.$$state.value, qlikDivSelector, objectClass);
$(`.${objectClass}`).each(function () {
var chartId = $(this).attr("id");
app.getObject(this, chartId);
});
})
})
}
</script>
</body>
</html>