1 2 Previous Next

Qlik Design Blog

25 Posts authored by: Francis Kabinoff

What days of the week, or hours of the day do you most often post to Facebook? Is it different depending on what month it is? Has it changed over the years? Find out by trying out this demo Qlik Facebook Session App Demo.

 

I built it to try out halyard.js, which 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, and which includes a mixin for enigma.js for loading your halyard representation into the QIX engine. halyard.js and enigma.js makes grabbing some data, generating a Qlik load script, and create an app on the fly pretty simple.

 

The app I built 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. Check out the video below, or try it out for yourself.



You can grab the source code below and try it out on your own machine if you'd like. 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

UPDATE -  There is no longer any reason to do it this way. Beginning with Qlik Sense 3.0, we now have the noSelections option - App API and Viz API noSelections option.

 

 

I've come across the need a few times now to embed a chart or two into a website with selections disabled. This is the default behavior when embedding Qlik Sense Cloud charts, but what about if you want to do it using charts from a Qlik Sense app using the Capability APIs? Let's take a look at two different methods for achieving this.

 

 

Method 1: Using the noInteraction option in the getObject() method

The getObject() method takes an optional options parameter which allows you to set noInteraction to true.

 

app.getObject("QV01", "objID", {"noInteraction": true});



 

This will disable selections on the chart, however, it will also disable tooltips and scrolling, which is often undesired. Take a look at an example here http://webapps.qlik.com/no-interaction-chart-example/method1.html and notice how you are unable to scroll to see the whole chart, and the chart's tooltips do not work.

 

 

Method 2: Select all and lock field

So what I do to disable selections in a chart embedded with the Capability APIs is to use the selectAll() and lock() methods from the Field API on the field of the dimension in the chart. For instance, if the chart's dimension is the Date field, I would do the following

 

app.getObject("QV01", "hRZaKk");
app.field("Date").selectAll();
app.field("Date").lock();



 

This effectively disables selections on the chart by not allowing any selections on the field this chart is using as a dimension. Scrolling and tooltips still work, as you can see in the example http://webapps.qlik.com/no-interaction-chart-example/method2.html

 

There is a drawback to this method. If, for example, you wanted to embed 2 objects on a page that both used the same dimension, but disable selections in only one of them, this method would disable selections on both. But for many cases, this method will work just fine.

The problem, the solution

 

Recently, while making a mashup in Angular.js using the Capability APIs I noticed that my UI was performing very poorly. In particular, making a selection in one of my custom selection boxes was basically freezing the up the entire UI for up to a few seconds! After poking around a bit, I discovered the problem: there was just too many dom elements being watched for changes.

 

You may have seen this problem yourself if you've ever tried to use Angular.js to create a mashup. And this problem isn't inherent to only mashups; the same problem applies to extensions. So how do we deal with it? We can use virtual scrolling.

 

Virtual scrolling is a way to render and watch only the dom elements that are currently in view, and changing those elements to other elements as we scroll, as opposed to actually rendering and watching all of the elements. The Qlik Sense client already does this, which, in a minute, you'll see we're going to use to our advantage.

 

Here's two examples to show you just what I mean. You can see that the render time is much faster on the page that implements virtual scrolling, and that the UI responsiveness when making selections on the Case IDs is drastically better.

 

Virtual Scrolling Example               No Virtual Scrolling Example

 

So how can you implement virtual scrolling in your mashup or extension? I'm going to walk through an example implementing virtual scrolling in a custom listbox for a mashup, but the same idea can be applied to extensions as well.

 

 

Using what's already there

 

Remember I said that the Qlik Sense client already implements virtual scrolling, and that we can use that to our advantage? We're simply going to make use of the same library that Qlik Sense objects already use to implement virtual scrolling. When building mashups or extensions, it's possible to use any library the client is already using. To see the full list, you can go to the "Menu" in the Qlik Sense client and click "About," then "Third-party Software."

 

2016-05-13 12_07_40-Qlik Sense.png2016-05-13 12_08_34-Qlik Sense.png

 

We can see in the images above that something called "angular-vs-repeat" is used by Qlik Sense, and, in this case, that's what we're after. The angular-vs-repeat library is an angular directive for virtual scrolling.

 

It's super easy to include this in your Angular.js based mashup.

var app = angular.module('app', ['vs-repeat']);



 

That's all you need to include it, since it's already loaded with the Capability APIs.

 

Implementing virtual scroll

 

Using it is pretty simple, too. In the most simple of cases, all you need to do is include the vs-repeat directive on the direct parent of an element with ng-repeat. In our example, that looks like this:

<ul vs-repeat class="cases-container">
     <li ng-repeat="case in cases" ng-click="selectCase(case)" class="{{case.qState}} case">{{case.qText}}</li>
</ul>



 

Resources and wrap-up

 

And that's basically the idea. The entire code for the example mashup is attached, and the link to the angular-vs-repeat repository on github is below. Hope this helps you improve the performance of your mashups and extensions

 

https://github.com/kamilkp/angular-vs-repeat

You may be familiar with the search functionality in the Qlik Sense client, and also available through the "CurrentSelections" object with the App API using app.getObject(<elementID>, "CurrentSelections"). But it's also possible to build your own custom search using methods in the App API of Qlik Sense 2.2, in particular the searchResults() method and the selectAssociations() method.

note - if you have a version of Qlik Sense earlier than 2.2, the searchResults() method is not available to you. However, the searchAssociations() method can be used instead.

 

The first method we need to be familiar with, the searchResults() method, does basically what you think it would. It takes an array of terms to search for, has a few useful options that can be passed to it, and has a callback which provides results. An example below

 

app.searchResults(
  ["it"],  //array of search terms
  {qOffset: 0, qCount: 15}, //how many results to return, and offset
  {qSearchFields: 'Case Owner Group', qContext: 'CurrentSelections'}, //options object
function(reply) {
     //do something with reply here
});



 

The array of search terms and number of results are relatively straightforward. The options object gives you the ability to define which fields to search (leaving this out will search all fields) and what context to search in, including the ability to search through values not excluded by current selections, or searching through all values, regardless of selections.

 

 

The next method you want to be familiar with is the selectAssociations() method. This method makes selections based on an array of search terms, and the position of the result. An example below

 

app.selectAssociations(
     0,  //position of result to select
     ["it"],  //array of search terms
     {qSearchFields: 'Case Owner Group', qContext: 'CurrentSelections'} //options object
);



 

So in the above, the first result (index of 0) would be selected from the results returned from searching the "Case Owner Group" field for the search term "it" in the context of current selections.

 

There's a million cool ways that these methods could be used, but I'm going to walk through a really super basic example by creating an input box and using the input to search through all fields in the app in the context of current selections, and display results which you can make selections from.

 

The first step is simply to create a container for the input field and the results div, as well as the the input field and results div themselves in HTML

<div class="qs-search-container">
     <input class="qs-search" type="text" placeholder="Search">
     <div class="qs-search-results"></div>
</div>


 

The next step is to listen for input in the input field using javascript

$(".qs-search").keyup(function() {
     //the rest of our code will go in here
});


 

Now comes the bulk of the code. I'm going to include it below, along with comments

// Use value of input field as search term in searchResults method
app.searchResults([$(this).val()],
  {qOffset: 0, qCount: 15},
  {qContext: 'CurrentSelections'},
function(reply) {
//assign searchGroupArray of results to variable named searchResults for readability
var searchResults = reply.qResult.qSearchGroupArray;

  // clear any old results
  $(".qs-search-results").html("");
  // append new list to hold results
  $(".qs-search-results").append("<ul>");

  //loop through results and add to dom
  searchResults.forEach(function (result, i) {
    result.qItems.forEach(function (item) {
     //append list element for result
      $(".qs-search-results ul").append("<li data-index='" + i + "'>");
     //append identifier to result list element
      $(".qs-search-results ul li:last-child")
      .append("<div class='qs-search-item-identifier'>" + item.qIdentifier + "</div>");
     //initialize string for matches
      var matchString = "";
     //loop through matches
      item.qItemMatches.forEach(function (match) {
       //add match to match string
        matchString += match.qText;
      });
     //add match string to result list element
      $(".qs-search-results ul li:last-child").append("<div class='qs-search-item-string'>" + matchString + "</div>");
    });
  });

  //Add click handler to each result list item that will make selection
  $(".qs-search-results li").click(function() {
    app.selectAssociations($(this).data("index"), [$(".qs-search").val()], {qContext: 'CurrentSelections'});
   //clear results after selection is made since old results are no longer valid
    $(".qs-search").val("");
    $(".qs-search-results").html("");
  });

});


 

With a little added CSS, we should end up with something like below.

2016-04-15 17_50_02-Qlik Sense Mashup.png

That's the basic idea. Obviously, the example above could be taken a lot further, but this is a decent starting point for creating your own custom search functionality with the Capability APIs.

 

some useful links -

searchResults method ‒ Qlik Sense

selectAssociations method ‒ Qlik Sense

 

Code for the example created above is attached, and can be downloaded and put into the extensions folder of your Qlik Sense Desktop installation to play around with.

Filter Blog

By date:
By tag: