1 2 Previous Next

Qlik Design Blog

28 Posts authored by: Francis Kabinoff

I've seen a few people asking recently if and how they can use webpack to build their mashup. The answer is yes, and I'm gonna discuss a few implementation details and provide some example code.

 

So to use the Qlik Capability APIs, you're probably already aware that you need to load the Qlik Capability API code, which includes loading a custom require.js file, and then requiring the qlik.js file through the loaded instance of require.js. The thing is, there's really no way to get around this, that's how the Qlik Capability APIs are loaded.

 

But you can still use webpack for all of your own project code, you just have to decide how you are going to load the custom require.js file and where you'll use the require.js instance to require qlik.js. This is how I've been doing it.

 

First, I load the Qlik custom require.js file in a script tag in the head of the document. Can you get fancier if you'd like with something like the script-loader, yea sure you can, but the goal here is to load that Qlik custom require.js file in a global context, and to me the simplest way to do it is to just include it in a script tag in the head of my html document.

 

Then, you'll need to set the require.js config and require the qlik.js file through require.js somehow. The trick here is that the require.js instance can be accessed with window.require. Also, since requiring files with require.js is asynchronous, and you'll almost certainly want to return some stuff when qlik.js is done loading, it's useful to use a promise here. This is what my module looks like for this in ES2015 -

 

webpackcapabilityconfig.png

 

You'll notice the config object which you should be used to for mashups, then how I'm using window.require.config to set the require.js config. Also, I explicitly set the path for 'qlik' because I find this helps avoid some errors, especially with regards to loading extensions. Then, I export a promise which resolves with the app from the openApp() method as the value. You can resolve this promise with the 'qlik' object, or multiple apps, or whatever your needs are, but for myself, most of the time, I'm just opening 1 app and I just resolve the app.

 

So in summary, if you want to use webpack with the Capability APIs then the Qlik custom require.js file will need to be loaded in a global context in some way, and then you'll be able to access the require.js instance on 'window.require' (but not just simply 'require' since webpack will use that keyword).

With the Qlik Sense June 2017 release comes server side extensions, which allow you to extend the Qlik built-in expression library with functionality from external calculation engines. We've released an open source server side extension that enables interaction with R, and if you use the R-Plugin, you know that you have to start Rserve.exe and SSEtoRserve.exe, then restart the Qlik Sense Engine Service. What I'm going to walk you through is how to setup Rserve.exe and SSEtoRserve.exe to run as services, and then make the Qlik Sense Engine Service depend on those services so that they start whenever the Qlik Sense Engine Service starts.

This post is for people who have already installed the open source R-Plugin and would like to run the plugin as a Windows service instead of starting it manually. If you haven't gotten started with the open source R-Plugin, and would like to, please check out this guide sse-r-plugin - get started.

 

The first thing we need is NSSM. Download and unzip, then open a command prompt with admin privileges and change directory to  <path-to-nssm>\nssm-{x}.{x}{x}\win64.

nssm.png

 

Now we need to install our 2 new services. In the command prompt, enter nssm install RserveService. An interface will pop up where we can define the service. Enter <directory>\Rserve.exe into "Path", and set "Startup directory" to the directory Rserve.exe is in. Then, go to the "Environment" tab, and enter PATH=%Path%;C:\R\R-3.4.1\bin\x64 (assuming you have R-3.4.1 installed in the C:\R directory, otherwise make adjustments as necessary). Now click the "Install service" button.

rservepath.pngrserveenv.png

 

Next, we need to install a service for SSEtoRserve.exe. So, again, at the command prompt enter nssm install SSEtoRserveService, and then enter <directory>\SSEtoRserve.exe into "Path", and set "Startup directory" to the directory SSEtoRserve.exe is in. Now click "Install service".

ssetorserve.png

 

Now that the two services are installed, we can set the Qlik Sense Engine Service to depend on them, so that any time the Qlik Sense Engine Service starts, these services will be available. Back at the command prompt enter nssm edit QlikSenseEngineService. Go to the "Dependencies" tab and add RserveService and SSEtoRserveService, each on their own new lines under the already existing QlikSenseRepositoryService.

engineservice.png

 

All of you have to do now is restart the Qlik Sense Engine Service and you should be good to go.

If you're creating a mashup that includes embedded Qlik Sense charts, there's a couple issues you may run into that you'll want to be aware of.

 

First, if your mashup scrolls, and you scroll down the page, you will notice that the tooltips on the Qlik Sense charts are not positioned correctly, like below:

2017-06-07 11_20_25-Marató Barcelona.png

The tooltips will get more and more displaced the further down the page you scroll. For the tooltips to be positioned correctly, you'll have to adjust how scrolling happens on your page a little bit.

 

The easiest option that you can implement with just some CSS is to set the html and body tags to a height of 100%, hide the overflow on the html tag, and add use overflow: auto on the body tag. That looks like this:

html, body {
height: 100%;
overflow: hidden;
}
body {
overflow: auto;
}









 

Now the tooltips will no longer be displaced when you scroll your mashup. However, there are times when it may be necessary to watch the scroll position of the page, or be able to set the scroll position of the page programmatically, and for some reason, when using the above method the scrollTop attribute of the body tag never actually updates, so there is no way to observe when the page scrolls or set the scroll position programmatically.

 

In instances like this, just a simple extra step will fix the issue. What you'll need to do is add a wrapper element that wraps the entire content of your mashup, and also set that to a height of 100%, and put the overflow on that element. That element's scrollTop will be set correctly, and you can observe or set it programmatically. So, it would be something like this:

<head>
<style>
  html, body, #page-content {
    height: 100%;
    overflow: hidden;
  }
  #page-content {
    overflow: auto;
  }
</style>
</head>
<body>
<div id="page-content">
  <!-- All of your content -->
</div>
</body>









 

The other issue you may run into is the chart tooltip not be styled correctly, since it may be affected by the CSS in your mashup. The most common example I see of this is if using Bootstrap v4. Bootstrap v4 adds some negative margins to the .row class, and the tooltip also uses the row class, and it makes the text in the tooltip get cut off, like this:

2017-06-07 12_11_20-Marató Barcelona.png

To fix this, and any other styling issues you may have with the tooltips, it's helpful to be able to inspect it. A div with the class .qs-chart-tooltip will be appended near the end of the body. If you inspect the page and find that element, then expand it, it's first child element is a div that has display: none set, just uncheck that style and you'll be able to view the tooltip in it's last location. Then you can continue to expand those child elements and inspect the element, looking for any issues. In the case I show above, as I stated, the problem is with Bootstrap v4 and it's negative margins on the .row class. So the css below fixed the tooltip:

.qv-chart-tooltip-inner .row {
margin-left: 0;
margin-right: 0;
}


 

So, now you should be able to address these Qlik Sense chart tooltip issues I often see in mashups. If you have any questions, let me know!

Halyard.js is a new open source library that simplifies the Qlik Sense data load experience as it abstracts away the need to write a load script. Halyard.js includes a mixin for enigma.js for loading your halyard representation into the QIX engine. Using halyard.js and enigma.js, it’s now pretty trivial to generate a load script and create an app on the fly.

 

I built an app that uses Facebook’s API to grab user’s posts, halyard.js to generate a load script, enigma.js to create a session app and objects, and then displays a few filters and a visualization just to test out the whole flow, and it was awesome. I made a video of the results below, check it out.



I want to put this app up live so everyone can try it out because it's so cool, but I’m waiting on Facebook approval. In the meantime, you can grab the source code below and try it out on your own machine.


There’s a few steps to do that. First, download the source code and run npm install. Then go to the qapp.js file and enter session info for your Qlik Sense server in the config variable on line 8. Next, you’ll have to go to https://developers.facebook.com/ and create an app. Once your Facebook app is created, grab the App ID and enter it in the fb.js file, on line 26.

For your Facebook App to be able to authorize users, you’ll need to add a platform and app domain in the “Settings” page of the Facebook app.

It’s up to you how to host this. I’ve included a .qext file if you just want to use Qlik Sense Desktop.

 

Once you’ve done that, run npm run webpack to build the project, and it should be good to give it a go!

Did you know that you can register extensions on the fly in mashups? That's right, you can register an extension in a mashup to use in that mashup, regardless of whether the extension is already loaded into your Qlik Sense environment. That means you can distribute your mashup with any extensions it uses as one package, and you have total control of the extension version your mashup is using.

 

Doing it is pretty straightforward. You just need to load the extension code into your mashup, then register it. It'll look something like this.

require(["js/qlik"], function (qlik) { //load qlik module

  require(["path-to-my-extension/my-extension.js"], function(myExtension) { //load extension entry point
   qlik.registerExtension( 'my-extension', myExtension ); //register extension
    //do stuff with extension
  });

});








Notice that I loaded the extension entry point after loading the qlik module. That's because many extensions use the qlik module, and if your extension loads the qlik module but you try to load your extension code before loading the qlik module in your mashup, you'll end up with errors. So better just to load the extension after the qlik module has been loaded in your mashup.

Once the extension has been registered you can do stuff with it, like use it with the Visualization API. An interesting use case is if you are loading objects that use an extension from an app into your mashup. The version of the extension you register with the mashup will override the extension loaded into your Qlik Sense environment, which can be really useful.

 

You can read more about it and see a few examples here Creating extensions on the fly.

If you don't already know, enigma.js is an open source library for communicating with Qlik Sense backend services. The QIX Service of enigma.js provides an API to communicate with a QIX Engine, giving you the ability to build applications powered by Qlik.

 

Vega calls itself a visualization grammar. It is a declarative language for creating visualizations. You just describe the appearance and behavior of the visualization you want to create, and Vega does the rest. And it can render with canvas, avoiding costly dom manipulations.

 

I'm going to demonstrate using enigma.js and Vega to create a simple bar chart. I'll be reusing the qApp.js module and qSessionObject class I introduced in Getting started with enigma.js, so check that out first if you haven't already.

 

Getting Setup

You can review Getting started with enigma.js, and follow the "Setup", "Connecting to an app", and "Creating Session Objects" sections. You'll also want to load jQuery and Vega (https://cdnjs.cloudflare.com/ajax/libs/vega/3.0.0-beta.25/vega.min.js).

 

Alternatively, you can download the getting-started.zip file below. The getting-started.zip file includes package.json and webpack.config.js files, the qApp.js and qSessionObject.js files, as well as an index.html file, and a main.js file. There's also a .qext file, in case you want to use Qlik Sense as your server for your static files.

 

For this demonstration, I'm using fields from the Helpdesk Management app, so make sure your qApp.js file is connecting to a copy of the Helpdesk Management app.

 

The Project

Now that you're setup, we can start the project. We'll do everything in main.js for this demonstration. So open up main.js. We'll need to import qSessionObject.js and create our session object.

import qSessionObject from "./qSessionObject";

let chartCube = new qSessionObject({
  qInfo: {
    qType: "visualization"
  },
  qHyperCubeDef: {
    qDimensions: [{
      qDef: {
        qFieldDefs: ["[Case Owner Group]"]
      },
      qNullSuppression: true
    }],
    qMeasures: [{
      qDef: {
        qDef: "Avg([Case Duration Time])"
      }
    }],
    qInitialDataFetch: [{
      qWidth: 2,
      qHeight: 1000
    }]
  }
});






 

Now that our qSessionObject has been created, let's define the Vega spec for our bar chart. The spec is roughly based on the spec from Vega's tutorial Vega: Let's Make A Bar Chart Tutorial, so feel free to check that out if you like. I simplified it a bit, and removed the data values so that we can stream the values in using data returned from enigma.js. It looks like this:

let barchartSpec = {
  "$schema": "https://vega.github.io/schema/vega/v3.0.json",
  "width": 400,
  "height": 200,
  "padding": 5,
  "data": [
    {
      "name": "table"
    }
  ],
  "scales": [
    {
      "name": "xscale",
      "type": "band",
      "domain": {"data": "table", "field": "category"},
      "range": "width"
    },
    {
      "name": "yscale",
      "domain": {"data": "table", "field": "amount"},
      "nice": true,
      "range": "height"
    }
  ],
  "axes": [
    {
      "orient": "bottom",
      "scale": "xscale",
      "encode": {
        "labels": {
          "update": {
            "angle": {"value": -50},
            "align": {"value": "right"},
            "baseline": {"value": "middle"},
            "radius": {"value": -2}
          }
        }
      }
    },
    {
      "orient": "left",
      "scale": "yscale"
    }
  ],
  "marks": [
    {
      "type": "rect",
      "from": {"data":"table"},
      "encode": {
        "enter": {
          "x": {"scale": "xscale", "field": "category", "offset": 1},
          "width": {"scale": "xscale", "band": 1, "offset": -1},
          "y": {"scale": "yscale", "field": "amount"},
          "y2": {"scale": "yscale", "value": 0}
        },
        "update": {
          "fill": {"value": "steelblue"}
        },
        "hover": {
          "fill": {"value": "red"}
        }
      }
    }
  ]
}





 

With our qSessionObject and our bar chart spec created, we can create the bar chart. After the dom is ready, we'll initialize the Vega view (Vega: View API), open the qSessionObject, get the layout of the qSessionObject, reformat the matrix to work with Vega, insert the values into our Vega view, setup updating the Vega view when the qSessionObject changes, and add an event listener to the Vega view to enable selections. It all looks like this:

$(() => {

  //initialize vega view
  let view = new vega.View(vega.parse(barchartSpec))
    .renderer('canvas')
    .initialize('#view')
    .hover();

  //open cube
  chartCube.open().then(() => {

    //get object layout and insert data into vega view
    chartCube.object.getLayout().then((layout) => {
      let values = layout.qHyperCube.qDataPages[0].qMatrix.map((row) => {
        return {"category": row[0].qText, "qElemNumber": row[0].qElemNumber, "amount": row[1].qNum}
      });
      view.insert('table', values).run();
    });

    //when object data changes, update data in vega view
    chartCube.object.on("changed", function() {
      chartCube.object.getLayout().then((layout) => {
        let values = layout.qHyperCube.qDataPages[0].qMatrix.map((row) => {
          return {"category": row[0].qText, "qElemNumber": row[0].qElemNumber, "amount": row[1].qNum}
        });
        view.remove('table', (d) => { return true; }).run();
        view.insert('table', values).run();
      });
    });

    //add event listener to make selections on hypercube when a bar is clicked
    view.addEventListener('click', function(event, item) {
      if(item){
        chartCube.object.selectHyperCubeValues("/qHyperCubeDef", 0, [item.datum.qElemNumber], true);
      }
    });

  });
});




 

And that's it. Don't forget npm run webpack, and check out the results. Here's what it should look like - Vega bar chart.

I've attached the full project in case you'd prefer to just download that and play around too.

enigma.js is Qlik’s open source library for communicating with Qlik Sense backend services. I’m going to show you how to get started using enigma.js, as well as a couple general patterns I’ve been using.

 

I’m going to use ES2015, Babel, and webpack. These are not necessarily requirements for building an app with enigma.js, but I recommend them.

 

Setup

Create a new folder for your project. Run npm init or just create your own package.json file, however you prefer. Now run npm install babel-loader babel-core babel-preset-es2015 webpack --save-dev. This will install our development dependencies and save them to our package.json file. Next, run `npm install enigma.js --save`. This will install enigma.js, and save it to our package.json file.

 

For convenience, we’ll add webpack to the scripts property of our package.json file. This will let us run npm run webpack instead of having to supply the entire path to webpack. Open up the package.json file and add ”webpack”: “webpack” to the scripts property. Your package.json file should now look something like this:

{
  "name": "enigmapatterns",
  "version": "1.0.0",
  "description": "",
  "dependencies": {
    "enigma.js": "^1.0.1"
  },
  "devDependencies": {
    "babel-core": "^6.23.1",
    "babel-loader": "^6.3.1",
    "babel-preset-es2015": "^6.22.0",
    "webpack": "^2.2.1"
  },
  "scripts": {
    "webpack": "webpack"
  }
}






 

We’ll also need to create a webpack.config.js file, which should look like this:

module.exports = {
  entry: './main.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  devtool: 'source-map',
  module: {
    loaders: [
      {
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015']
        }
      }
    ]
  }
}






 

Finally, we’ll want to create a main.js file which will be our entry point, and an index.html file which loads the bundle.js file that our build will output.

 

Connecting to an app

The pattern I’ve been using to connect to the app is to create a qApp.js file, and export the promise to connect to the app. This way, anywhere I need to use the app I can simply import this file and do something like getApp.then((app) => { //code here }).

 

So create a file named qApp.js, which looks like this:

import enigma from 'enigma.js';
const qixSchema = require('./node_modules/enigma.js/schemas/qix/3.1/schema.json');

const config = {
  schema: qixSchema,
  session: {
    host: 'localhost',
    prefix: '',
    port: 4848,
    unsecure: true
  }
};

export default enigma.getService('qix', config).then((qix) => {
  return qix.global.openApp('Helpdesk Management.qvf').then((app) => {
    return app;
  });
});






 

Creating session objects

I like to have a class for creating session objects. That way I can have an open() and close() method for each session object. The open() method calls createSessionObject on the app and returns the promise if the object does not exist in the app, and simply returns otherwise, so that I can always check to see if an object is available, without inadvertently creating a duplicate object in the app. The close() method calls destroySessionObject on the app, passing along the object’s id.

 

So create a file named qSessionObject.js, which looks like this:

import getApp from "./qApp";

class qSessionObject {

  constructor(properties) {
    this.properties = properties;
    this.object = null;
  }

  open() {
    if (!this.object) {
      return getApp.then((app) => {
        return app.createSessionObject(this.properties).then((object) => {
          this.object = object;
        });
      });
    }
  }

  close() {
    if (this.object) {
      return getApp.then((app) => {
        return app.destroySessionObject(this.object.id).then(() => {
          this.object = null;
        });
      });
    }
  }

}

export default qSessionObject;





 

You can also put other methods in this class which may be helpful. For example, I recently built an app that had 2 alternate states, and when the states changed I needed to call applyPatch on each object and change the state of the object. Having a class for the session objects made this really easy.

 

Usage example

So now we have our project setup, a qApp.js file for connecting to the app, a qSessionObject.js file for creating session objects, let’s cover a simple use.

 

We’ll create 3 lists objects, and 3 filters in our UI with those list objects. For this, we’ll use the Filter class which you can find at the end of this post, along with the rest of the source code. You'll need the filter.js file from the zip below.

You’ll also find a .qext file in the source code. That’s so we can put this project in the Extensions folder of Qlik Sense Desktop to use it as a server, but you can serve this any way you’d like, just make sure that your config in qApp.js is pointing to your Qlik Sense server.

 

So first, let’s get our html ready. The Filter class relies on jQuery and Bootstrap 4 alpha v6, so we’ll load those. Also, we'll need to load our bundle.js and main.css files. Then we can add the html we’ll need to the body. Your index.html should look like this:

<!doctype html>
<html>
<head>
  <title>Enigma patterns</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
  <!-- jQuery -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <!-- Tether -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/css/tether.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
  <!-- Bootstrap 4 -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
  <!--Project code -->
  <script src="bundle.js"></script>
  <link rel="stylesheet" href="main.css">
</head>
<body>
  <div id="loading">Loading...</div>
  <div id="loaded" style="display: none">
    <div class="filters row">
      <div class="col-auto">
        <div class="filter" id="case-number-filter"></div>
      </div>
      <div class="col-auto">
        <div class="filter" id="subject-filter"></div>
      </div>
      <div class="col-auto">
        <div class="filter" id="case-owner-filter"></div>
      </div>
    </div>
  </div>
</body>
</html>



 

Grab the main.css file from the attached source code and save it in your project folder.

 

Now open main.js. We’ll import the qSessionObject class and the Filter class, create 3 list objects, create 3 filters, open the 3 list objects, and then initialize the filters. This is what that should look like:

import qSessionObject from "./qSessionObject";
import Filter from "./filter";

let caseNumberList = new qSessionObject({
  qInfo: {
    qType: "visualization"
  },
  qListObjectDef: {
    qDef: {
      qFieldDefs: ["[CaseNumber]"],
      qFieldLabels: ["Case Number"]
    },
    qAutoSortByState: {
      qDisplayNumberOfRows: 1
    },
    qShowAlternatives: true,
    qInitialDataFetch: [{
      qWidth: 1,
      qHeight: 1000
    }]
  }
});
let subjectList = new qSessionObject({
  qInfo: {
    qType: "visualization"
  },
  qListObjectDef: {
    qDef: {
      qFieldDefs: ["[Subject]"],
      qFieldLabels: ["Subject"]
    },
    qAutoSortByState: {
      qDisplayNumberOfRows: 1
    },
    qShowAlternatives: true,
    qInitialDataFetch: [{
      qWidth: 1,
      qHeight: 1000
    }]
  }
});
let caseOwnerList = new qSessionObject({
  qInfo: {
    qType: "visualization"
  },
  qListObjectDef: {
    qDef: {
      qFieldDefs: ["[Case Owner]"],
      qFieldLabels: ["Case Owner"]
    },
    qAutoSortByState: {
      qDisplayNumberOfRows: 1
    },
    qShowAlternatives: true,
    qInitialDataFetch: [{
      qWidth: 1,
      qHeight: 1000
    }]
  }
});

$(() => {
  let caseNumberFilter = new Filter(caseNumberList, "#case-number-filter");
  let subjectFilter = new Filter(subjectList, "#subject-filter");
  let caseOwnerFilter = new Filter(caseOwnerList, "#case-owner-filter");

  Promise.all([caseNumberList.open(), subjectList.open(), caseOwnerList.open()]).then(() => {
    $("#loading").hide();
    $("#loaded").show();
    caseNumberFilter.init();
    subjectFilter.init();
    caseOwnerFilter.init();
  });
});


 

Don’t forget to run npm run webpack to build your bundle.js file.

 

If you’ve followed along, you should have 3 dropdowns on your page now that look like this - Enigma patterns. You can also just download the source code below, put it in the Extensions folder of your Qlik Sense Desktop installation, run npm install and then npm run webpack, and check out the project that way.

If you haven't yet heard, Qlik recently released 3 open source software projects, now available for use and open to contributions!

 

enigma.js

enigma.js is a framework that communicates with Qlik Sense backend services. The Rest Service allows communication with Qlik REST APIs, while the Qix Service allows communication with the Qix Engine. enigma.js is now my preferred method for communicating with Qlik Sense backend services, and if you're a developer that works with javascript, I strongly suggest you check it out.

 

leonardo-ui

leonardo-ui is a UI component library that has a Qlik Sense look and feel. It works well with popular CSS frameworks such as Bootstrap and Foundation. Using leonardo-ui can speed up your UI development process, and keeps styles in sync with other Qlik Sense assets.

 

after-work.js

after-work.js is a unified testing framework capable of performing unit, component, integration, and end-to-end tests. It is the testing framework used by enigma.js and leonardo-ui.

 

Contributing

You can contribute to any of the Qlik open source projects. Each project has its own contribution guidelines, linked below.

Contributing to enigma.js

Contributing to leonardo-ui

Contributing to after-work.js

It's almost the end of 2016, so I figured I'd look back at some of the stuff the Qlik developer community has created this year. This list does not attempt to be a "best of 2016" list, there were so many great things created this year that I wouldn't even know where to begin ranking them. Rather, this is just a few highlights I selected. Hope you enjoy!

 

 

Qlik Playground

Qlik Playground lets you quickly test out all kinds of stuff with the Qlik Engine and APIs. It gives you a sandbox to play in, and also has some cool showcase projects you can play around in, as well as learning resources. It's very cool.

 

RxQAP

RxQAP is a reactive JS wrapper that currently supports the Qlik Engine API. Observables are super awesome, and while I haven't had a chance to build a project with this yet, it's very high on my to-do list.

 

Sense Search Components

Sense Search Components lets you easily embed search in a web app. And it works with the App API or the Engine API!

 

Lars Mashup Template

Full disclosure, I built this one. It's the template I've been using for most of the year to quickly spin up boilerplate for any App API projects.

 

qSocks snippets extension for VSCode

Adds snippets for qSocks to VSCode. If you're not using VSCode, you should. If you haven't tried qSocks, check it out. And if you're already using both, this project is for you.

 

Google Annotation Chart for Qlik Sense

Google annotation charts are sharp. This is an integration of google annotation charts for Qlik Sense.

 

Climber Selection bar

An extension that allows you to add selection capabilities to your app that look sharp and have some cool functionality.

 

Dynamic Table with Sparklines

This extension allows you to create tables that include sparklines. It looks really good.

 

sxBlockRain

This. Is. Necessary.

 



You can check out Qlik Branch for even more awesome Qlik developer community created projects, and check out the comments to see recommendations by others and add your own!

Recently I've been working on building a mashup template that will work in the dev-hub that will have a few more advanced features than many of the available dev-hub templates. One of these features that I thought was important was a card container for Qlik Sense objects that has fullscreen capability built in. I'd like to share that with you today.

 

Here's an example of a really simple mashup with just the selection toolbar, and 2 Qlik Sense objects that use the qliksense-card web component I've built - Qlik Sense Mashup.

 

To use the qliksense-card web component yourself, you have a couple of options. You can either install the dev-hub template and create a new mashup from the dev-hub using the template, or you can just grab the necessary files and load them into your project.

 

Installing dev-hub template

You can download the dev-hub template here - GitHub - qliksense-card-template. After downloading, install the template as you would install any extension in Qlik Sense. After the template is installed, you can go to the dev-hub, create a new mashup, and select "Card template" from the dropdown list of templates.

 

The template is really kind of bare. There's not much going on besides the qliksense-card web component and a bit of bootstrap, but it's a good way to check out how the qliksense-card web component works.

 

Including the files in your own project

You don't have to use the dev-hub template to make use of the qliksense-card web component. What you will need is the qliksense-card.html file and the webcomponents-lite.min.js file. The easiest way to include them is to use the RawGit CDN, example below.

<script src="https://cdn.rawgit.com/download/polymer-cdn/1.7.0.2/lib/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="https://cdn.rawgit.com/fkabinoff/qliksense-card-template/master/qliksense-card.html">


 

Using the qliksense-card component

After you're all setup, whether you've generated a new project using the dev-hub template, or included the necessary files in your own project, using the qliksense-card web component is easy. All you want to do is wrap your Qlik Sense object container with the qliksense-card tag, and set the height of the Qlik Sense object with the content-height attribute of the qliksense-card tag. You'll also want to set the width and height of your Qlik Sense object container to 100%. If you've generated a project using the template, this stuff has already been done, and you can check it out and just reuse as you'd like. If you've just included the necessary files in your own project, you can still check out the template files to see how it's done. Generally, it'll look something like below

<qliksense-card content-height="300px">
     <div class="qvobject" id="QV01"></div>
</qliksense-card>


Sometimes you may want to use a chart in a mashup that does not allow selections. Before Qlik Sense 3.0, you basically had three options. One of those options I blogged about before Disable selections in charts using Capability APIs. With this approach, you basically locked the field used in the chart to not allow any selections on that field. This had the side effect of not allowing a selection on that field anywhere in the mashup. The other two options were very similar. You could "mask" the chart with a div that sat over top of the chart, or you could use the noInteraction option that basically did the same thing. The problem with these approaches was that you could not scroll the chart or see the tooltips.

 

A better solution

Introduced with Qlik Sense 3.0, the noSelections option is a much better solution to this problem than any that previously existed. It disallows selections in the chart, without affecting the tooltips, ability to scroll, or the field used in the chart. And it's really easy to use.

 

Using noSelections option with the App API

To use the noSelections option with the getObject() method of the App API, simply set it in the options parameter like below

 

app.getObject(<htmlElement>, <objId>, {noSelections: true});


 

It's really that simple.

 

Using the noSelections option with the Visualization API

Using the noSelections option with the Visualization API is just a tiny bit trickier. I didn't realize I could even do it at first, because it's a little buried in the docs. But all you have to do is set the noSelections option in the options parameter of the show() method, like below.

 

qlik.app.visualization.create(type, cols, options).then(function(viz) {
 viz.show(<htmlElement>, {noSelections: true});
});



 

And that's it. I find that this can often be really useful, because many there are many use cases where I want to embed just a chart or two into a page, and it makes no sense to allow selections on those charts, and now that we have the noSelections option is definitely the best way of accomplishing this.

Recently, I needed to make an extension that required multiple hypercubes with dimensions and measures that were able to be set by the user. The following is the solution I came up with. It could be a bit more generalized, and there's some other things to consider such as making selections, but it's a good starting point and an interesting topic, so I wanted to share with you.

 

Step 1 - Add the hypercubes to initialProperties

The hypercubes need to be added to initialProperties. They can each be added within their own object in initialProperties, as below.

initialProperties: {
  cube1: {
    qHyperCubeDef: {
      qDimensions: [],
      qMeasures: [],
      qInitialDataFetch: [{
        qWidth: 2,
        qHeight: 5000
      }]
    }
  },
  cube2: {
    qHyperCubeDef: {
      qDimensions: [],
      qMeasures: [],
      qInitialDataFetch: [{
        qWidth: 2,
        qHeight: 5000
      }]
    }
  }
}


 

Step 2 - Add ability for user to define dimensions and measures in definition

Now, you'll need to add the ability for users to define the dimensions and measures for each cube into the properties panel.

definition: {
  type: "items",
  component: "accordion",
  items: {
    cube1props: {
      label: "Cube 1",
      type: "items",
      items: {
        dimension: {
          label: "Dimension",
          type: "string",
          expression: "always",
          expressionType: "dimension",
          ref: "cube1props.dimension"
        },
        measure: {
          label: "Measure",
          type: "string",
          expression: "always",
          expressionType: "measure",
          ref: "cube1props.measure"
        },
      }
    },
    cube2props: {
      label: "Cube 2",
      type: "items",
      items: {
        dimension: {
          label: "Dimension",
          type: "string",
          expression: "always",
          expressionType: "dimension",
          ref: "cube2props.dimension"
        },
        measure: {
          label: "Measure",
          type: "string",
          expression: "always",
          expressionType: "measure",
          ref: "cube2props.measure"
        }
      }
    }
  }
}


 

Step 3 - Updating the hypercube with user defined properties

This is where the interesting stuff happens. When the user updates one of the properties associated with a hypercube, we need to actually update the hypercube to reflect that. So in the extension's controller, we're going to watch for changes to the props for a cube, and then use the Backend API ApplyPatches method to update the cube.

//Set cube1
$scope.$watchCollection("layout.cube1props", function(props) {
  $scope.backendApi.applyPatches([
    {
      "qPath": "/cube1/qHyperCubeDef/qDimensions",
      "qOp": "replace",
      "qValue": JSON.stringify([{qDef: {qFieldDefs: [props.dimension]}}])
    },
    {
      "qPath": "/cube1/qHyperCubeDef/qMeasures",
      "qOp": "replace",
      "qValue": JSON.stringify([{qDef: {qDef: props.measure}}])
    }
  ], false);
});


//Set cube2
$scope.$watchCollection("layout.cube2props", function(props) {
  $scope.backendApi.applyPatches([
    {
      "qPath": "/cube2/qHyperCubeDef/qDimensions",
      "qOp": "replace",
      "qValue": JSON.stringify([{qDef: {qFieldDefs: [props.dimension]}}])
    },
    {
      "qPath": "/cube2/qHyperCubeDef/qMeasures",
      "qOp": "replace",
      "qValue": JSON.stringify([{qDef: {qDef: props.measure}}])
    }
  ], false);
});


 

 

That'll do it. Now the user can define a dimension and measure for each hypercube, and the hypercube will be patched accordingly. There's still some more to think about and some nice-to-have's with this approach, such as the ability to add variable numbers of dimensions and measures, allowing the user to set other properties of the hypercube, selections, and more. But, I think this pattern is a decent starting point.

 

Here's a link to a github repo I started around this idea, just in case you try this out and have anything cool to add.

GitHub - fkabinoff/qliksense-multicube-extension-template

If you're familiar with iframes, you're probably familiar with the fact that they don't handle dynamic height very well. In other words, when you use an iframe you have to define it's height, and it does not change based on the height of it's content.

 

This can be problematic. For instance, if I use the single configurator to embed a large pie chart, and define the iframe's height so that the entire pie chart is displayed without scrolling, if the pie chart shrinks in height on a smaller device it will leave extra empty space in the iframe. Another common time I run into this problem is when embedding mashups that are responsive with content that wraps on mobile, or with multiple views that have different heights.

 

You may run into this problem, too, when using the single configurator or embedding mashups, as I often do. What can you do about it?

 

Using Pym.js

Pym.js is a super useful library when dealing with iframes. With pym.js, your iframes will respond to the height of their content, making their integration into your page much more seamless by avoiding either extra empty space or inner-page scrollbars, and it's pretty easy to use, too!

 

So how do you actually use it?

 

Well, first thing to know is that pym.js must be placed on both the child site and the parent site, so if you'd like to use this with an object from the single configurator, for instance, you'll have to put together a quick mashup. Super simple. Let's do that as an example, and then you should be able to apply the same idea to any situation you'd like to use pym.js.

 

Step 1 - Add necessary code to your mashup

Require pym.js, and include var pymChild = new pym.Child()

require( ["js/qlik", "https://pym.nprapps.org/pym.v1.min.js"], function ( qlik, pym ) {
  var pymChild = new pym.Child();

//rest of your code...



 

Step 2 - Override html and body height 100%

Qlik Sense mashups set html and body height to 100%, this will not work with pym.js. Just add the following css

html, body {
  height: auto !important;
}



 

That's all that needs to be done in the mashup.

Step 3 - Embed mashup in website with pym.js

Include the following in your website to embed the mashup with pym.js

<!-- This adds pym.js to your page -->
<script src="https://pym.nprapps.org/pym.v1.min.js"></script>


<!-- This is the container for the frame -->

<div id="example"></div>


<!-- This is the pym.js method that adds the frame to the page. -->
<!-- Make sure the first param matches the container id, and the second param is the url to the mashup -->

<script>
  var pymParent = new pym.Parent('example', 'example.html', {});
</script>



 

And that's it.

 

I've put up an example to show you the difference between using pym.js and not. This page https://demos.qlik.com/extensions/pym-example/iframe.html includes a large pie chart that is set to have it's width equal its height, and its width equal to 30% of the page, and is simply iframed in to fit nicely at full screen, but make the screen smaller and notice all the empty space between the pie chart and the "Some other content" string that sits outside of the iframe.

 

This page https://demos.qlik.com/extensions/pym-example/pym.html uses pym.js, and when you resize, the iframe height also adjusts, so that it's not taking up a bunch of unnecessary space.

Who this project template is for

Developers creating Qlik Sense mashups with Qlik Sense 3.0 or above, who have at least a decent understanding of Angular.js and Require.js and would like to use a project template to speed up development.

 

Who this isn't for

Anyone only looking for a predesigned layout in which to simply add Qlik Sense objects

 

 

Project Template Overview

This project template helps organize all your config code in one place, and provides you with a decent architecture for a component based approach to an angular app. It has a few useful prebuilt components specific to qlik sense mashups, which use a nice pattern which can be borrowed to create more components.There's also a gruntfile to compile less and build your app using the r.js optimizer. I'll go over the parts of the template now.

 

main.js

The project template has a top level main.js file that contains the config code for the Qlik Sense Root API and Require.js, and acts as the entry point for most of the proejct, requiring the Qlik Sense API code as well as all the project app code.  It's here, for instance, that you can change the the app id you would like to connect to, and add paths and shims to require. The default Require.js context is used for loading the Qlik Sense API code, while a second context, named "requireApp", is used to require the app code from the app folder.

 

app.js

This file is found under the app folder, and is the entry point for the app. The main app angular module is created here, the templates are loaded, and the angular app is bootstrapped with the project app and Qlik Angular API app.

 

qlikApp

This is an angular service which opens a Qlik Sense app using the Root API with the app id specified in the main.js file, and provides access to it throughout the rest of your app. The service also has methods which can be used in place of the App API createCube, createGenericObject, createList, and createTable methods, which act just like the corresponding App API methods, but allow you to pass $scope as a parameter, which then takes care of the destruction of session objects when their scope is destroyed.

 

senseObject

This component gives you the ability to use the App API getObject method as a directive, with the added functionality of closing the object when its scope is destroyed.

 

search

This component exposes the App API getResults method as an easy to use directive, using the typeahead from UI Bootstrap to display results, and allowing selections from those results.

 

dropdown

This is a directive for a dropdown component, which allows you to add a field from the Qlik Sense app and make selections. It displays the name of single selections, or the count of multiple selections, as well as indicating the state of each individual value in the dropdown. It also makes use of virtual scrolling, to prevent too many watchers.

 

examples

This is a view component which is simply here as an example. It requires the dropdown, search, and senseObject components and uses them, and is itself required by the app.js file and injected into the main angular app module.

 

gruntfile

The gruntfile is configured to compile all of the less in each individual component, as well as the main.less file located in css/less folder, compile the templates.js file from all html files found in components, and run the r.js optimizer from the app entry point.

 

How to get it

You can find this project on Qlik Branch @ Qlik Lars Project Template

What is Leonardo UI?

Leonardo UI was introduced with Qlik Sense 3.0.  Here's a description from the documentation -

Leonardo UI is a UI component framework, designed to be used as a complement to other frameworks. It is non-obtrusive in the sense that it contains no global styling but will use your page defaults. You can also use it standalone with a CSS reset to get consistent styling of your components.


 

How can I use Leonardo UI?

Leonardo UI is available to use in Qlik Sense mashups, extensions, and widgets as of Qlik Sense 3.0, and you do not have to do anything extra to begin using it in those contexts.

 

The documentation for it is here - Leonardo UI, and I've added to that by providing examples of the currently available components and listing all available icons here - Leonardo UI Examples and Icons

Filter Blog

By date:
By tag: