JavaScript in Cognos Analytics
Date: 21/04/2017

By Paul Mendelson

In every new release of Cognos, there are some ups, and there are some downs. And while some people may have a lot to complain about in Cognos Analytics, there are some shining advances that force me to forgive all the questionable design decisions (even the loss of the menu and button bars in Report Studio).

This article is the first in a series that I’m writing around Javascript.  Over the next few months I will focus in depth on JavaScript. After all, they don't call me JavaScriptPaul for nothing… (Nobody does yet, but someone might some day).

JavaScript and Cognos have always been a touchy subject.  They go together like oil and water.  Historically unsupported, incompatible with most libraries, and with a cryptic undocumented internal API, JavaScript has been a major challenge to implement in a Cognos report.  In Cognos BI 10.2, IBM started officially recognizing that people wanted more, creating the basic Prompt API.  While limited, it was a start to making truly interactive reports. 

JavaScript for Cognos_CCognos Viewer.png

And now, in Cognos Analytics 11, we finally have a fully supported JavaScript control. 

The new JavaScript control is for use with the new Interactive Mode.  Non-interactive mode appears to work the same way as Cognos 10.  Inline JavaScript will only work with non-interactive mode.  The big problem I have with this is you have to save a JavaScript file onto a server somewhere. This makes development a problem, especially if you're a lowly developer who doesn't have direct access to save files on the server. On the flip side, if you are a lowly developer, all you need to know is where these JavaScript files are and what to pass to them. 

The Interactive Mode will dynamically download the files and cache them in the browser.  This makes for a slightly faster user experience. 

Unlike the Cognos 10 API everything available is documented.  On the positive side, this means that all the JavaScript functions are fully supported.  On the negative side, this does mean that there aren't a lot of them yet.  All of the undocumented and unsupported functions, like: oCV_NS_.getSelectionController().isDrillLinkOnCrosstabCell() (Yes, this is a real function and yes I've used it) have been compiled into a random string of letters of numbers.

JavaScript for Cognos_documented functions.png

I'll touch on a few of the new features briefly, then show a working example.

In Cognos 10 and previous there were three ways of getting data into a JavaScript Object.  The easiest way is to associate a value prompt with one, but then we're limited to only two attributes.  The second way is to dump everything into a list, but then we need to loop through a table - slow and annoying.  The third way is to use repeaters to inline the JavaScript.  The big problem with this is there's no formatting option for numbers, and some strings are problematic. 

In Cognos Analytics the JavaScript controls can be assigned to a specific dataset from a query.  This circumvents the problem with excess data AND the issue with invalid characters. In addition to datasets, we can pass a JSON string to the control containing additional configuration information.

JavaScript for Cognos_datasets.png

Calling specific report elements, such as blocks and lists, can be done with a simple call to the page, stacking .getControlByName.  Once you have the control, there are only a few basic things you can do - setting visibility, width, height, colors.  But you CAN get the HTML element - and with that you can do a lot.

An often requested function is the ability to select visible columns in a list. In fact, IBM even has an example of this on their demo server.

JavaScript for Cognos_report elements.gif

Personally I don't like that solution. End users don't want to type the column index, and when they page down it doesn't remember the selection. I solved that using sessionStorage, but let's focus on the Cognos centric code.

The JavaScript starts by defining the function.

define( function() {
"use strict";
function columnSelector(){};

Next, we initialize the function.

columnSelector.prototype.initialize = function(
oControlHost, fnDoneInitializing )
{
 var o = oControlHost.configuration;
      this.m_sListName = o ? o["List name"] : "List1";
  this.m_aStatic = o ? o["Static choices"] : [];

 if(!window.sessionStorage.getItem(this.m_sListName+'SelCols')) window.sessionStorage.setItem(this.m_sListName+'SelCols','[]');

  if(!window.sessionStorage.getItem(this.m_sListName+'SelColsFR')) window.sessionStorage.setItem(this.m_sListName+'SelColsFR','1');

  fnDoneInitializing();

};

oControlHost is the object passed to the script from Cognos. It's a unique identifier that includes any extra configuration data defined in Report Studio. The List name is optional, so long as you have List1 in the output. The static choices also, optional. Next we have sessionStorage. This is what lets the page navigation remember what the user selected.

I believe fnDoneInitializing instructs Cognos that it's actually ready to go to the next step.

Next we can actually start building the control on the page. Notice these functions are attaching themselves to the parent. This allows us to use other variables attached to it, like this.m_sListName, across the various functions.

columnSelector.prototype.draw = function( oControlHost )
{
      var elm = oControlHost.container,
      list = oControlHost.page.getControlByName( this.m_sListName ).element,
      listHeaders = list.rows[0].childNodes,
      listHCount = listHeaders.length,
      selArr =
eval(window.sessionStorage.getItem(this.m_sListName+'SelCols')),
      firstRun = window.sessionStorage.getItem(this.m_sListName+'SelColsFR'),
      sel = document.createElement('select');

  sel.multiple=true;
  sel.style.height="100%";
  sel.style.width="100%";

  for (var i = 0;i<listHCount;++i){
var selected = listHeaders[i].style.display=='none'?false:true,
        opt = document.createElement('option');

    opt.value = i;
    opt.text = listHeaders[i].innerText;

if(window.sessionStorage.getItem(this.m_sListName+'SelColsFR')==0){
      if(selArr.includes(i)) {
          opt.selected=true;
          oControlHost.page.getControlByName( this.m_sListName ).setColumnDisplay(i,true)
        } else {opt.selected=false

        oControlHost.page.getControlByName( this.m_sListName ).setColumnDisplay(i,false)
        }
    }
    else{opt.selected=selected};

        sel.appendChild(opt);
  };

      elm.appendChild(sel);

window.sessionStorage.setItem(this.m_sListName+'SelColsFR',0);
      this.elm = sel;
      this.elm.onchange = this.onChange.bind( this, oControlHost );
};

We define the select prompt, find the selected list, and loop through the first row. If the cell style is set to display:none, then it's hidden and the option in the select prompt should not be selected. The important thing though is the select is defined using the list as a source. This makes it easier for the developer.

The sessionStorage bit is to ensure the first run works as expected, and the page down remembers what's selected.

Next we have to define what happens when the select is changed.

columnSelector.prototype.onChange = function( oControlHost ){

      var ctrl = oControlHost.page.getControlByName( this.m_sListName ),
      selOpts = this.elm.options,
      selArr = [],
      selLen = selOpts.length;

for (var i=0;i<selLen;++i){

    if(this.m_aStatic.includes(i)) {
      selOpts[i].selected=true;
    };

    if(selOpts[i].selected) selArr.push(i);
    ctrl.setColumnDisplay( i, selOpts[i].selected );
  };

window.sessionStorage.setItem(this.m_sListName+'SelCols','['+selArr+']')

};

And finally, let's close off the function.

return columnSelector;
});

Using this in Cognos is fairly easy. First, we need to make sure it's saved somewhere accessible. In this case I'm keeping it \cognos\analytics\webcontent\javascript. Referencing it in the report isn't as smooth as I'd like, the developer will actually have to enter the path to the file.

JavaScript for Cognos_module path.png

Next we define the configuration object manually.

JavaScript for Cognos_configuration.png

The last bit here is the UI Type.

JavaScript for Cognos_ui type.png

For a control like this where we're creating an input, we'd want to use "UI without event propagation". If we were setting up a Prompt API script, one that interacts with the page without creating an object on the page, we'd use "None". The last option, "UI with event propagation" is when we would want event bubbling or capturing to occur.

And now when we run it, everything works! As an added bonus, when a user pages down to a new page, it will use the previous page's columns. When paging up, it remembers the state of that page.

JavaScript for Cognos_pages.gif

 

Conclusion

I hope you find this article useful.  Stay tuned for more JavaScript content coming your way. If you would like to learn more please reach out to us at:


Australia Singapore, Philippines, Thailand     United States
Cornerstone PMsquare | A Cornerstone Group Company     PMsquare

Call +61 1300 840 048 or
email Piers Wilson
Call +65 6635 1700 or
email Carsten Brandt
    Call +1 (708) 575 2092 or
    email Chris Loechel


Blog post shared courtesy of PMsquare LLC