Export data from an embedded chart

Introduction

In this tutorial, you will learn how to use qlik-api to export data from a chart embedded in a web application with qlik-embed web components.

Requirements

To complete the tutorial you will 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

1.0.0 Add qlik-embed to the web application

1.0.1 Configure qlik-embed connection script

When you use qlik-embed, a host configuration (hostConfig) is needed 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>

Note: This tutorial uses an OAuth2 SPA host configuration which requires an OAuth callback page. Review the Connect qlik-embed tutorial for more ways to connect qlik-embed to your tenant.

1.0.2 Add 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>

Enter the ID for the analytics application where the chart exists into the app-id property.

Enter the ID for the chart into the object-id property.

1.0.3 Add export button

Add the export button to the HTML page. In addition, 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>

2.0.0 Add export logic to the web application

Exporting data from an embedded chart uses the companion library to qlik-embed named qlik-api. qlik-api provides typescript interfaces to Qlik Cloud’s REST APIs and the Qlik Analytics Engine, also known as qix.

Add a script element to your web application and set the type to module.

<script type="module">
  //Add javascript here...
</script>

2.0.1 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@1/index.min.js";
import fileSaver from 'https://cdn.jsdelivr.net/npm/file-saver@2.0.5/+esm'

2.0.2 Configure qlik-api connection

When you use qlik-embed and qlik-api together, they can share the same authenticated session. However, you do need to set the host configuration for qlik-api so that it can 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,
});

2.0.3 Get reference to qlik-embed object

When you add a qlik-embed object to a web application, you can easily 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 handle on 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();

2.1.0 Request export report task

2.1.1 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.

2.1.2 Create reporting request payload

Create the data extract payload. Copy the code snippet and update the following properties:

Note: variables for these properties are referenced in the code.

  • 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 step 2.1.1.
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"}
}

Note: See the reporting service request generation endpoint for all available payload properties.

2.1.3 Helper functions

When a user makes a report request, it kicks off a reporting lifecycle that you will want your web application to handle gracefully. In the example code for this tutorial, there are a number of helper functions for managing the report request lifecycle.

  • showLoader: Makes visible the DOM element with id="loader".
  • hideLoader: Makes hidden the DOM element with id="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 ensure uniqueness.

Note: 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.

2.1.4 Request and handle the data export

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 step 2.1.2.

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 get the file. Use the fileSaver.saveAs function to invoke the save dialog box in the browser.

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);
    }

3.0.0 Connect export logic to the button

Connect the exportData function to the button you added to the HTML in step 1.0.3 by adding a click event listener.

document.getElementById("exportData").addEventListener("click", async function() {
    exportData();
  });  

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, qix, users, reports, tempContents } from "https://cdn.jsdelivr.net/npm/@qlik/api@1/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); }
}
Was this page helpful?