patchwork

One of the more powerful calls in the QIX Api is the ApplyPatches call. It allows you to dynamically add, change or remove basically any property of a generic object. With it, you can allow users to dynamically change visualizations: reorder data, change dimensions and measures, add limitation etc etc. But using it in your implementation can be tricky.

Parameters

The ApplyPatches call only has two parameters (plus the handle, identifying which object we are patching:

NameDescriptionMandatoryType
qPatchesArray of patches.YesArray of NxPatch
qSoftPatchPatches are not savedNoBoolean

So it’s an arry and a boolean (true/false) flag. Let’s start with the flag. Setting the qSoftPatch flag to true does mean that the patched property is not saved, but it also has some other consequences:

  • you can change objects that you otherwise cannot, like published objects, or objects in apps you cannot change
  • the changes will only affect you, other users will not see them, they might very well apply a completely different patch to the same object
  • the changes will only affect the current session. If you disconnect and connect again, they will be lost and you will start over with the original version of the object

So if your goal is to allow users to dynamically change visualizations you would want qSoftPatch to be true. While the default value false works OK in Qlik Sense Desktop, it’s generally not something you would want to use in a server environment.

The array of patches

The array is a bit more difficult. It’s an array so you can change several properties with one call. For example if you change dimension or measure you should consider changing the title too, and probably do that in the same call.  Every entry(almost) in the array has three parameters:

NameDescriptionType
qOpOperation to perform. Add, remove or replace.String
qPathPath to the property.String
qValueThe value of the propertyString

The first parameter, qOp, is an easy one. In most cases you want to change a property, use ‘replace’. Occasionaly you want to add a property, use ‘add’. Rarely you want to remove a property, in that case use ‘remove’. 

The second parameter, qPath, is the path to the property, using / between the property names (not . as in javascript). Remember:

  • you’re patching properties, not the layout, so it’s /qHyperCubeDef/… not qHyperCube
  • for arrays you need to include the position in the array, and numbering starts with zero, so first dimension is /qHyperCubeDef/qDimensions/0/ etc
  • property structures can be complicated, speciallly the hypercube..
  • you can use my Chrome extension Add Sense to create the path argument. Show properties for the objects, switch to patch mode, copy the path

The second parameter, qPath, is the path to the property, using / between the property names (not . as in javascript). Remember:

  • you’re patching properties, not the layout, so it’s /qHyperCubeDef/… not qHyperCube
  • for arrays you need to include the position in the array, and numbering starts with zero, so first dimension is /qHyperCubeDef/qDimensions/0/ etc
  • property structures can be complicated, speciallly the hypercube..
  • you can use my Chrome extension Add Sense to create the path argument. Show properties for the objects, switch to patch mode

Some examples

A common case is when you want your users to be able to change the ordering of for example a table dynamically. Ordering of a hypercube is determined by the qInterColumnSortOrder property, an array with the column numbers in the order they should be used for sorting. If you want say the third column (which has number 2, since numbering starts with 0) you move the number 2 to the beginnging of the array and call applyPatches:

model.applyPatches( [{
  qPath: '/qHyperCubeDef/qInterColumnSortOrder',
  qOp: 'replace',
  qValue: JSON.stringify(sortorder)
}], true );

You can find a more complete example in the mashup I showed at Qonnections back in 2015.

Another example is when you want to reverse the ordering of a hypercube. You do this by toggling the boolean value of qReverseSort in the dimension or measure where you want to change the order. For the first dimension this will be:

model.applyPatches( [{
  qPath: '/qHyperCubeDef/qDimensions/0/qDef/qReverseSort',
  qOp: 'replace',
  qValue: JSON.stringify( !reversesort )
}], true );

Nucleus

A while ago I did my first test project with Qlik’s new open source library nebula.js (it’s available here). That time I tested the visualization part, corresponding to extensions in the current product (but hopefully used also for visualizations delivered by Qlik). Now it’s time to try it from the other side, building a web app with these new kind of visualizations.

Terminology first: the term mostly used in Qlik Sense is mashup. That’s because the current javascript API was originally designed for a mashup scenario, where you wanted to add Qlik Sense objects to an existing web page. But what it’s mostly used for today is building web apps, that is pages that include only Qlik Sense objects. And that is what I intend to do in this small test project.

Setting up

Nebula.js includes a small example app. You find it here. It’s just a few files, since it actually downloads the libraries (enigmajs, nebulajs) over the internet. So if we just copy those files to a local directory we have got a starting point.

The example uses a docker installation and a session app, but I want to use my Qlik Sense Desktop and the Consumer Sales app. To fix that I revised (and simplified) the connect.js file so it looks like this:

window.connect = function connect(host, id) {
	return fetch('https://unpkg.com/enigma.js/schemas/3.2.json').
	then(response => response.json()).
	then(schema => window.enigma.create({
		schema,
		url: `ws://${host}/app/${id}`,
	}).open().then(qix => qix.openDoc(id)));
};

And in the index.js file add host and app:

connect('localhost:4848','Consumer Sales.qvf').then((app) => {

and we have got something that’s working. Start Qlik Sense desktop, make sure your test project is served (I use serve), and open the file in the browser. You’ll se a selection toolbar, and an area that displays the simple supernova that’s included:

If you wonder how come my selection toolbar shows selections, that’s because I opened the same app with the same url(use http://localhost:4848/sense/app/Consumer%20Sales.qvf ) and made a selection. Session sharing does the rest.

Load nova-table

OK, so now we have got a page using nebulajs and connecting to an app, and the selction toolbar works. But how do we use supernovas like the one I made in my first test drive? Well first we need to make it available in our project. Just copy the files from the dist directory in nova-table and put it in your project. I have used a sub-directory called novas, since there is actually nothing super about nova-table. You can then load it in index.html:

  <script src="./novas/nova-table.js"></script>
  <script src="connect.js"></script>
  <script src="index.js"></script>

It needs to be before index.js for this to work.

Unlike the current Qlik Sense javascript libraries, nebulajs is not bundled with a module management library like requirejs. Instead it will see if there is a module manager available and use it if there is. If not it will simply add the module as a global function. So we can access it simply as window[‘nova-table’]. In a real project we would not really want everything defined globally, but this is just a quick test.

To make a solution that can be extended with more visualization, we create a novas object, where we can put all our visualizations, and then grab the right one using the type, with the sn from the initial example as the default:

const novas = {
 'nova-table': window['nova-table']()
};
const sn = {
 component: {
  mounted(element) {
   element.textContent = 'Hello';
  },
 },
};

const nebbie = window.nucleus(app, {
 load: (type, config) => config.Promise.resolve(novas[type.name] || sn),
});

Add a table

So now we have got the nova-table module loaded, let’s create one and add to the page. We’ll make a session object, with the definition in the javascript file, so first we change the HTML file to:

  <div class="content">
    <div class="toolbar"></div>
    <div class="object" id="nova01"></div>
  </div>

And in the javascript file we use the create method to create a nova-table object, with properties that contain a hypercube and inject it into the HTML element ‘nova01’:

nebbie.create({
 type: 'nova-table',
}, {
 element: document.getElementById('nova01'),
 properties: {
  qHyperCubeDef: {
   qDimensions: [{
    qDef: {
     qFieldDefs: ['Customer']
    }
   }],
   qMeasures: [{
    qDef: {
     qDef: 'Sum([Sales Amount])'
    }
   }],
   qInitialDataFetch: [{
    qWidth: 5,
    qHeight: 1000
   }]
  }
 }
});

And we get something like this:

A very basic page with a working app connection and displaying some data from our app using the visualization we developed earlier. The project is available here.