Skip to main content
Announcements
Join us at Qlik Connect for 3 magical days of learning, networking,and inspiration! REGISTER TODAY and save!
Francis_Kabinoff
Former Employee
Former Employee

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.

15 Comments
xyz1
Creator III
Creator III

very nice!! really

3,683 Views
Lech_Miszkiewicz
Partner Ambassador/MVP
Partner Ambassador/MVP

Does it work on mobile?

I looked at example on my laptop and everything was ok, tried an iPhone and was stuck with "Loading...."

regards

Lech

3,683 Views
rbecher
MVP
MVP

How can we assign the right QIX schema version to the actually Qlik Sense server version we connect to?

Also, is this downward compatible? Means, will it work if I always use the latest schema version?

3,683 Views
Francis_Kabinoff
Former Employee
Former Employee

Hey Ralf,

In the qApp.js file, line 2 is

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

You could just change this to point to whatever schema you'd like. You can find all of them in node_modules/enigma.js/schemas/qix.

And do you mean if you use the latest schema version but are connecting to an older server version? That's a good question. I haven't tested it. My intuition is that most of the methods should work, except for any that were updated between those versions.

0 Likes
3,683 Views
Francis_Kabinoff
Former Employee
Former Employee

Hi Lech,

This should most definitely work on mobile. Possible you hit a cached version? (I initially uploaded the sample pointing to localhost instead of a Qlik Sense server). Try to clear cache and load again. Also, possible that the Qlik Sense server I used for the demo was momentarily down. If you're still seeing issues let me know.

0 Likes
3,683 Views
rbecher
MVP
MVP

Hi Francis,

thanks, but how can I know the version of the server upfront to chose the right schema version? So, let's say I deliver a mashup or component someone needs to connect to her server without changing the JavaScript code..

0 Likes
3,683 Views
Alexander_Thor
Employee
Employee

currently the QIX definition is only available via the external schema. In the, hopefully near, future QIX will be able to expose this nativly so by connecting to QIX a library should be able to ask for it's schema/api definition.

I would not rely on the schema to be backward compatible as the API changes with methods being deprecated and finally removed.

0 Likes
3,312 Views
rbecher
MVP
MVP

So at the moment, there is no way to require the fitting schema. You would need to know the version upfront..

0 Likes
3,312 Views
Alexander_Thor
Employee
Employee

Yes, same way that enigma.js does it. It makes no assumptions about your current implementation.

0 Likes
3,312 Views
AlexOmetis
Partner Ambassador
Partner Ambassador

Thanks for that - nice to have a good walkthrough.

Spotted a couple of issues as I ran through it before sharing it more widely.

  1. There's a typo in the command 
    npm install babel-loader babel-core babel-presets-es2015 webpack --save-dev
    it shouldn't have the s on presets, so should be:
    npm install babel-loader babel-core babel-preset-es2015 webpack --save-dev
  2. There's no mention of creating / copying the filters.js file that is imported by main.js - perhaps just needs another note about copying this from the zip or creating it?

That said, it's always good to have a couple of small hurdles to cross to increase understanding - not much point in just copying & pasting the whole thing...

3,312 Views