Export data from an embedded chart
Introduction
This tutorial shows you how to use qlik-api to export data from a chart embedded in a web application with qlik-embed web components.
What you’ll learn
In this tutorial, you’ll learn how to:
- Embed a Qlik chart using
qlik-embedweb components - Set up OAuth2 authentication for secure access
- Export chart data to Excel using qlik-api
- Implement asynchronous report generation and polling
- Handle file downloads in the browser
Prerequisites
To complete this tutorial, you need the following:
- HTML and JavaScript experience
- A Qlik Cloud tenant
- A Qlik Analytics app
- A Qlik Analytics app ID and object ID to export data
- An OAuth client configured in your Qlik Cloud tenant
- A web server to host your application
Step 1: Set up the embedded chart
Configure the qlik-embed connection script
When you use qlik-embed, you need a host configuration (hostConfig) to load
the library and connect it to your Qlik Cloud tenant.
Add the host configuration as a child of the head element in your web page.
<script crossorigin="anonymous" type="application/javascript" src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components@1/dist/index.min.js" data-host="https://<tenant>.<region>.qlikcloud.com" data-client-id="<clientId>" data-redirect-uri="https://your-web-application.example.com/oauth-callback.html" data-access-token-storage="session"></script>This tutorial uses an OAuth2 SPA host configuration which requires an OAuth callback page. For more ways to connect qlik-embed to your tenant, see Connect qlik-embed.
Add the qlik-embed element
In the HTML body of your web application, add a qlik-embed element with an
analytics/chart ui.
<qlik-embed id="visualization" ui="analytics/chart" app-id="a51a902d-76a9-4c53-85d2-066b44240146" object-id="ZxDKp" disable-cell-padding="true"></qlik-embed>- Replace the app ID
a51a902d-76a9-4c53-85d2-066b44240146with your actual app ID in theapp-idproperty. - Replace the object ID
ZxDKpwith your actual chart object ID in theobject-idproperty.
Add the export button
Add the export button to the HTML page. Also, add a loader div element
to show a loading icon when the data export is generating.
<div> <button id="exportData">Export data</button></div><div id="loader" class="loader" style="display:none;"></div>Step 2: Set up the export logic
Exporting data from an embedded chart uses qlik-api, the companion library to qlik-embed.
qlik-api provides TypeScript interfaces to Qlik Cloud’s REST APIs and the Qlik Analytics Engine (also called qix).
Add a script element to your web application and set the type to module.
<script type="module"> //Add javascript here...</script>Import libraries
Add the auth, reports, and tempContents modules from qlik-api using the
import command.
The fileSaver library assists with downloading the export file to your computer or
device.
import { auth, reports, tempContents } from "https://cdn.jsdelivr.net/npm/@qlik/api@2/index.min.js";import fileSaver from 'https://cdn.jsdelivr.net/npm/file-saver@2.0.5/+esm'Configure the qlik-api connection
When you use qlik-embed and qlik-api together, they can share the same authenticated session. However, you must set the host configuration for qlik-api to connect to your Qlik Cloud tenant.
Using the auth.setDefaultHostConfig method, configure the qlik-api connection.
The parameters to use are similar to those found in the qlik-embed script with
slightly different syntax.
auth.setDefaultHostConfig({ host: "<tenant>.<region>.qlikcloud.com", authType: "Oauth2", clientId: "<clientId>", redirectUri: "https://your-web-application.example.com/oauth-callback.html", accessTokenStorage: "session", autoRedirect: true,});Get reference to your qlik-embed object
When you add a qlik-embed object to a web application, you can obtain
access to the source analytics application’s composition and data model.
Access the object from the DOM using the getElementById method. Once you have
a reference to the object, you can access the qlik-embed API reference.
const vizEl = document.getElementById("visualization");const appId = vizEl.getAttribute("app-id");const refApi = await vizEl.getRefApi();The refApi variable represents a connection to the analytics session. You can
now make a getDoc call to access the complete analytics application model,
or make a getObject call to access the genericObject defining the embedded
visualization.
Create references to both the doc and the object. Then, create a reference to the
layout of the object:
const doc = await refApi.getDoc();const theObject = await refApi.getObject();const objLayout = await theObject.getLayout();Step 3: Create and submit the export request
Create temporary bookmark
Temporary bookmarks make it easier to send the current selection state of the analytics application to the reporting services API. The reporting API generates the requested output based upon the temporary bookmark.
Create the temporary bookmark using the doc object, supplying the chart object
ID from the object layout.
const tempB = await doc.createTemporaryBookmark( { qOptions: { qIncludeAllPatches: true, qIncludeVariables: true, qSaveVariableExpressions: true }, qObjectIdsToPatch: [ objLayout.qInfo.qId ] });The function will return the ID for the bookmark so it can be supplied to the data extract request.
Build the request payload
Create the report payload object using values from the previous steps:
The code uses the following variables from earlier steps:
appId: The Qlik Analytics application ID to specify the object source.id: The ID of the object present in the object layout variable.temporaryBookmarkV2.id: The temporary bookmark ID obtained in the Create temporary bookmark section.
const reportPayload = { type: "sense-data-1.0", meta: { exportDeadline:"P0Y0M0DT0H8M0S", tags:["qlik-embed-download"] }, senseDataTemplate: { appId: appId, id: objLayout.qInfo.qId, selectionType: "temporaryBookmarkV2", temporaryBookmarkV2: { id: tempB } }, output: { outputId: "Chart_excel", type:"xlsx"}}For all available payload properties, see the “Queue a new report request generation” endpoint documentation.
Submit and monitor the request using helper functions
When a user makes a report request, the export process is asynchronous and requires polling. In the example code for this tutorial, the helper functions manage the report request lifecycle:
showLoader: Makes visible the DOM element withid="loader".hideLoader: Makes hidden the DOM element withid="loader".extractReportId: Obtains the report request ID from the report status URL.waitUntil: Evaluates the report request status on an interval, stopping when the report generation completes.getDownloadId: Obtains the data extract output ID so the file can be downloaded.createFileName: Formats the name of the downloadable file to guarantee uniqueness.
These helper functions are part of this example to help provide an end-to-end experience. They are not required to execute a report request or download the resulting file.
Handle the download
Use a try-catch code block to perform the report request. Inside the try block,
add a reports.createReport call including the report payload from the Build the request payload section.
Obtain the status URL to monitor the reporting task from the content-location
header returned from the createReport call. Then get the report ID from the
status URL.
Use the waitUntil function to monitor the report request using the report ID.
When the report generation completes, get the download ID.
The download ID is the reference to the downloadable file’s temporary storage location.
Use the tempContents.downloadTempFile function with the download ID to retrieve the
file. Use the fileSaver.saveAs function to open the browser’s save dialog.
try { showLoader(); const reportReq = await reports.createReport(reportPayload); let statusURL = reportReq.headers.get("content-location"); const reportId = extractReportId(statusURL); if (!reportId) { throw new Error("Invalid report ID"); } // Set interval to check status every 5 seconds const wait = await waitUntil(reportId); const downloadId = getDownloadId(wait.location); let dle = await tempContents.downloadTempFile(downloadId, {inline: 1}); hideLoader(); fileSaver.saveAs(dle.data, `${createFileName(wait.filename)}.xlsx`); } catch (err) { console.log(err); }Step 4: Connect the button to export
Now that you have the export logic in place, you need to trigger it when users click the button. Add this event listener to the same module script from Step 2:
document.getElementById("exportData").addEventListener("click", async function() { exportData(doc, objLayout); });When users click the export button, the flow is:
- Event listener triggers
exportData() - A temporary bookmark captures the current selection state
- Report request is submitted to the API
- Loader appears while the report generates
- Status is polled every 5 seconds
- When complete, the file is downloaded and loader disappears
Putting it all together
Combine all code from Steps 2-4 into a single <script type="module"> block in your HTML file in the following order:
The Full code section includes a complete working example with all pieces assembled.
Full code
qlik-embed HTML and export button
<div id="analytics-chart" class="container"> <div class="sub-container"> <div> <button id="exportData">Export data</button> </div> <div id="loader" class="loader" style="display:none;"></div> </div> <div class="sub-container"> <div class="viz"> <qlik-embed id="visualization" ui="analytics/chart" app-id="<app-id>" object-id="<object-id>" disable-cell-padding="true" ></qlik-embed> </div> </div> </div> exportData function
<script type="module"> import { auth, reports, tempContents } from "https://cdn.jsdelivr.net/npm/@qlik/api@2/index.min.js"; import fileSaver from 'https://cdn.jsdelivr.net/npm/file-saver@2.0.5/+esm'
const vizEl = document.getElementById("visualization"); const appId = vizEl.getAttribute("app-id"); const refApi = await vizEl.getRefApi(); const doc = await refApi.getDoc(); const theObject = await refApi.getObject(); const objLayout = await theObject.getLayout();
auth.setDefaultHostConfig({ host: "<tenant>.<region>.qlikcloud.com", authType: "Oauth2", clientId: "<clientId>", redirectUri: "https://your-web-application.example.com/oauth-callback.html", accessTokenStorage: "session", autoRedirect: true, });
document.getElementById("exportData") .addEventListener("click", async function() { exportData(doc, objLayout); });
async function exportData(doc, objLayout) {
const tempB = await doc.createTemporaryBookmark( { qOptions: { qIncludeAllPatches: true, qIncludeVariables: true, qSaveVariableExpressions: true }, qObjectIdsToPatch: [ objLayout.qInfo.qId ] } );
const reportPayload = { type: "sense-data-1.0", meta: { exportDeadline:"P0Y0M0DT0H8M0S", tags:["qlik-embed-download"] }, senseDataTemplate: { appId: appId, id: objLayout.qInfo.qId, selectionType: "temporaryBookmarkV2", temporaryBookmarkV2: { id: tempB } }, output: { outputId: "Chart_excel", type:"xlsx"} }
try { showLoader(); const reportReq = await reports.createReport(reportPayload); let statusURL = reportReq.headers.get("content-location"); const reportId = extractReportId(statusURL); if (!reportId) { throw new Error("Invalid report ID"); } // Set interval to check status every 5 seconds const wait = await waitUntil(reportId); const downloadId = getDownloadId(wait.location); let dle = await tempContents.downloadTempFile(downloadId, {inline: 1}); hideLoader(); fileSaver.saveAs(dle.data, `${createFileName(wait.filename)}.xlsx`); } catch (err) { console.log(err); } }
function showLoader() { document.getElementById("loader").style.display = "block"; }
function hideLoader() { document.getElementById("loader").style.display = "none"; } // Function to create a filename function createFileName(additionalInfo) { const currentDateTime = new Date().toISOString(); return `${additionalInfo}-${currentDateTime}`; }
async function waitUntil(reportId) { return await new Promise(resolve => { const interval = setInterval(() => { return reports.getReportStatus(reportId). then((status) => { console.log(status); console.log(`Current status: ${status.data.status}`); if (status.data.status === "done") { console.log(status); let result = { location: status.data.results[0].location, filename: status.data.results[0].outputId, }; clearInterval(interval); resolve(result);
}; });
}, 5000); }); }
function extractReportId(url) { const regex = /reports\/(.*?)\/status/; const match = url.match(regex); if (match && match[1]) { return match[1]; } return null; }
function getDownloadId(url) { // Define a regular expression to match the last part of the path const regex = /\/([^\/?#]+)(?:[?#]|$)/;
// Execute the regular expression on the URL const matches = url.match(regex); // Return the matched string, or null if no match is found return matches ? matches[1] : null; }
</script> Supporting CSS for HTML
.viz { height: 600px; width: 100%; padding: 16px; border: 1px solid #bbb; border-radius: 3px; box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.2); position:relative;}
.container { padding: 8px; gap: 8px; position: relative; display: flex; flex-direction: column; box-sizing: border-box; margin-top: 50px; padding-top: 50px;}
.sub-container { display: flex; flex: 1 0 auto; flex-direction: row; align-content: stretch; gap: 10px;}
.loader { border: 4px solid #a9a9a9; /* Light grey */ border-top: 4px solid #3498db; /* Blue */ border-radius: 50%; width: 16px; height: 16px; animation: spin 2s linear infinite;}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }}