Tab Solution That’s Easy to Maintain Without Code – Guest Post

“How corrupting boredom is, everyone recognizes also with regard to children. As long as children are having a good time, they are always good. This can be said in the strictest sense, for if they at times become unmanageable even while playing, it is really because they are beginning to be bored[…]Adam was bored alone; then Adam and Eve were bored en famille. After that, the population of the world increased and the nations were bored en masse. To amuse themselves, they hit upon the notion of building a tower so high that it would reach the sky. This notion is just as boring as the tower was high and is a terrible demonstration of how boredom had gained the upper hand. Then they were dispersed around the world, just as people now travel abroad, but they continued to be bored” (S. Keirkegaard, the Rotation of Crops).

I like writing nifty solutions for Cognos, which look good, work well, and add a necessary functionality. Tabs for Cognos reports (Not Active Reports, where tabs exist out of box) are a great demonstration for such functionality: It’s a necessary functionality, and when done properly, they make a report look wonderful.

However, I get bored doing the same thing over and over. When writing a tabbing solution, the principal is always the same: There are the tabs themselves, which are essentially links to be clicked on, and when clicked, there are the contents which need to be either hidden or shown based on the tab that was clicked.

Every now and again a developer who may not be very well versed in HTML, JavaScript and/or CSS would ask me to add, remove or change a tab, or change the style of a tabbed menu, or any such thing. To me, this is boring, and as Kierkegaard noted, boredom is corrupting – indeed, it is the root of all evil.

What needed to be done, then, was to come up with a tabbing solution that allows developers to add, remove and edit tab contents and tab styles without needing to write a single letter of code. The scripted solution needed to be generalized so that developers would be able to use Cognos built-in features to control all aspects of tabs. In other words, I needed to write a tabbing solution that would be a piece of cake to maintain, leaving me with more free time to get bored productively in Cognoise forums.

POCT (=Piece of Cake Tabs) is just that solution. It requires no coding to add, remove, or edit the content a tab. It allows the user to style the tabs using nothing but Cognos built in features. Basically, once you set the solution up, you never have to open an HTML item again.

Let’s look at the page structure:

POCT

Under “Tab Headers” HTML Item, one would simply drop in text items one after the other, each containing the name of the tab (The text that would be written in the tab).

Under “Tab Content” HTML Item, one would create a block for each tab, and drop in the necessary objects for each tab. The uppermost block is the content for the leftmost tab header, and so on – tab headers from left to right, content blocks from top to bottom.

Under “Style” HTML Item there are 3 table cells. One can style them as one wish. The leftmost cell represents how a selected tab would look. The middle one represents the styling of a tab which isn’t currently selected. The rightmost cell controls how a tab looks on mouse over. You can also control the look of the entire tab row, by highlighting the blue table cell where the tab headers are and changing its design to modify the design of the tab row.

And that’s it.

Want to add a tab? Add a text item and a block accordingly. Want to change the content of a tab? Just change the content of a block. Want to remove a tab? Remove the header text item and the corresponding block. And styling is truly piece of cake.

Report XML (10.1) – you can either use that as base or copy the main block and anything in it to any report. This was tested on all Cognos 10.x versions, and should work fine on 8.4.x.

POCT-XML-101.txt (1330 downloads)

Nimrod (Rod) Avissar is a BI Front-End Specialist, with a penchant for specialized UX solutions (LinkedIn).

Checking an “All” option in a checkbox prompt

One of my readers sent me an interesting problem. They need a checkbox prompt in which the top option is “All” and checking on any other option would automatically uncheck the “All” choice. Similarly, checking “All” should uncheck the other choices. Taking it to the conclusion, when checking or unchecking all of the options, the “All” should be checked.

Since they are still on 10.1, I have not used the Prompt API, meaning this should work on all versions since 8.4. To begin, the prompt itself.
check this out

In this case, the prompt is based on the Retailers hierarchy in the Sales and Marketing cube. Other times you might want to add a static value.

The JavaScript itself is not that difficult. The JS will loop through the prompt each time an option is clicked. If the first option is clicked, it will check it and uncheck the other options. If any other option is click it will loop through the prompt, counting the number of checked options and act accordingly. If 0 or all of the options are checked, it will check the first option, otherwise it will simply uncheck it.

When working with Checkbox prompts in JavaScript, the thing to remember is that, for whatever reason, the checkboxes that we see are actually images. To show the prompt as checked, the input needs to have the class “dijitCheckBoxChecked”.

Now the JS:

<script>
/* 
  * Function: addEvent
  * Author: Dan Fruendel
  * Attachs an event or adds an event listener depending on the browser.
  */
var addEvent = function(element, event, func){
    if(element.addEventListener){
      addEvent = function(element, event, func) {
        element.addEventListener(event, func, false);
        return true;
      };
    }
    else if(element.attachEvent) {
      addEvent = function(element, event, func) {
        return element.attachEvent("on" + event, func);
      };
    }
    else {
      addEvent = function(element, event, func) {
        var oldEventHandler = element['on' + event];
        element['on' + event] = function() {
         //using .apply to pass on anything this function gets.
          if(typeof(oldEventHandler) === "function") {
            oldEventHandler.apply(element, arguments);
          }
          func.apply(element, arguments);
        }
        return true;
      };
    }
    addEvent(element, event, func);
  }


// Cognos form and namespace identifier. Don't touch.
var fW = (typeof getFormWarpRequest == "function" ? getFormWarpRequest() : document.forms["formWarpRequest"]); 
if ( !fW || fW == undefined)   {
       fW = ( formWarpRequest_THIS_ ? formWarpRequest_THIS_ : formWarpRequest_NS_ );
   } 
 var preFix = "";
 if (fW.elements["cv.id"]) {     preFix = fW.elements["cv.id"].value;   }
     var nameSpace = "oCV" + preFix;
 
function attacher(elm,prompt,clicked){
  var func = function() {
    //if all is selected, zero out everything else.
    if(clicked==0) {prompt[0].selected=true;prompt[0].checked=true;prompt[0].parentNode.className="dijitInline dijitCheckBox dijitCheckBoxChecked";for(var i=1;i<prompt.length;++i){prompt[i].selected=false;prompt[i].checked=false;prompt[i].parentNode.className="dijitInline dijitCheckBox"}}

    //if individual, count number of selected
    if(clicked>0) {var c=0;
      for(var i=1;i<prompt.length;++i){
        if(prompt[i].checked){++c}
      }
      //if the count of checked is 0, then set the all to checked
      if(c==0) {setter=false;prompt[0].selected=true;prompt[0].checked=true;prompt[0].parentNode.className="dijitInline dijitCheckBox dijitCheckBoxChecked"}
      //if the count of checked is equal to the length of the prompt, then set the all to checked and uncheck everything else
      else if(c==prompt.length-1) {setter=false;prompt[0].selected=true;prompt[0].checked=true;prompt[0].parentNode.className="dijitInline dijitCheckBox dijitCheckBoxChecked";        for(var i=1;i<prompt.length;++i){prompt[i].selected=false;prompt[i].checked=false;prompt[i].parentNode.className="dijitInline dijitCheckBox"}}
      //if the count is one and less than the length of the prompt then just set all to unchecked;
      else if(c>0&&c<prompt.length-1){prompt[0].checked=false;prompt[0].selected=false;prompt[0].parentNode.className="dijitInline dijitCheckBox"}
    }

    canSubmitPrompt();
    }
  addEvent(elm,'click',func)

}

var prompt=fW._oLstChoicesCountries;
for (var i=0;i<prompt.length;++i){
  attacher(prompt[i],prompt,i);
}

</script>

EDIT: An eagle-eyed reader noticed that the appearance of the checks are actually slightly different than they are when first rendered. The solution was to put a parentNode after prompt[i] when calling the className. Thanks Sue!

Checkbox Prompt - All option (678 downloads)

Automatically looping through charts in Active Reports

This is the first of a series of articles on extending the usability of Active Reports.

Active Reports makes a wonderful addition to the Cognos suite. Locally processed interactivity (my Brio alter ego is shouting, “so what!”; just ignore him) means complex, though predefined, data exploration scenarios can be offered to ever more demanding users. Just by clicking on a row in a list, the entire report can be refreshed instantly. Standard web-based reports are simply incapable of that behavior.

Periodically I get requests for things Active Reports can’t do. For some reason, saying that something is not possible completely renders all of the other features moot. One such request was: “I want an animated chart that will loop through all of the months in the database”. The user wanted a chart that would show 12 months at a time, incrementing one month per second, looping back to the start once it hits the last month.

As a mockup, I built an example chart in a data deck that shows that behavior.
Chart in Data Deck

I stuck a Data Iterator under it. By pressing the next button you can make it look like it’s animated. Sadly, this wasn’t enough. The needed it to be automatic.

Now we can start playing with JavaScript. This presents a few interesting problems, some of which I’m still trying to solve.

Problem 1. Cognos rewrites IDs.
To demonstrate, I create a text item and an HTML item – a div with text inside it:
HTML Item In RS

But when I run it, and examine the HTML:
HTML Item Div ID Changed

That’s interesting! The ID of the div changed from myDiv to v9! This means that any JavaScript written will have to not use getElementById(). There is no guarantee that the div will be v9 tomorrow. Especially if there are data changes in the report. That’s fine though. I could do a getElementsByTagName(‘div’) and loop through them until I find one that has the correct name, or some other attribute I set. It’ll be slow, but better than nothing.

Let’s see what happens if I use JavaScript to count the number of divs. It should be a simple:

<script>alert(document.getElementsByTagName('div').length)</script>

I just add it to the existing HTML item:
script tag

And yet when I run it, I’m not getting the alert. The JS is sound, and if I copy it to the script console in IE Dev Toolbar I get back the expected value. So what is going on?

Unfortunately this is still one of the things that I’m still trying to figure out. My guess is that every element (list, chart, HTML item) is stored in an object and loaded into the page after the page has been loaded as needed. When running a report with a data deck, we can see that the individual cards aren’t loaded until another control references them. This allows the page to be significantly smaller on the first load, and far more responsive than if everything was rendered. Of course, this is just a guess and I could be completely wrong (but that hardly ever happens).

Effectively this means that onload events are out. And predefined functions are out. Everything will have to be inline events on elements hand written with HTML items.

Getting back to the original problems, the user said that he would be okay with a start/stop button. He presses it to start the “animation”, and clicks it again to stop it whenever he wants. This means we can define the function in the onclick event. And once again I’ve turned to my good friend Dan Freundel for help. And once again he turned 20 lines of spaghetti code into 5 lines of pure JS genius.

Since we can’t loop through divs that don’t yet exist, we’ll have to find a way to recreate the action of clicking on the next/previous buttons on the iterator. One way is to set the parameter directly. Any control that uses that param will be effected. Unfortunately, that way requires referencing minified JS functions. If we do it this way, any upgrade or fix pack will kill the report. Instead, I opted to literally click on the next/prev buttons in the loop.

First, the JS:

<button onclick="
  if(!document.runAnim)
  {

runAnim = function(buttonElement)
{
 var intervalTracker, doStuff = function() {
  var iter = buttonElement.previousSibling
  , ibuttons = iter.getElementsByTagName('button');
  if(ibuttons[2].getAttribute('disabled')) {ibuttons[0].setAttribute('disabled',false);ibuttons[0].click();} else {ibuttons[2].click()}
}
 buttonElement.onclick = function()
 {
    if(intervalTracker)
   {
     clearInterval(intervalTracker);
     intervalTracker = false;
   }
   else
   {
     intervalTracker = setInterval(doStuff,1000);
   }
 };
 intervalTracker = setInterval(doStuff,1000);

};}
runAnim(this);
this.value='stop';
" value="Start animation">Start</button>

First this defines the function. Since we can’t use inline HTML functions will have to be defined like this. On the first run, if the runAnim function doesn’t exist, it will create it. Next it will call that function in the scope of the button. That means the setInterval timer will exist only for that button, and it will not interfere with any other timers on the page. As mentioned before, because the IDs get rewritten, it is impossible to easily reference an element on the page. For this, I simply placed the button directly after the iterator. Next, when an iterator is first loaded, it has no value set and you can’t click any of the buttons. But if, somehow, the disabled=true was removed from the First button, clicking it would select the first item in the list.

So we have the script. The timer function is created the first time the button is clicked, that function and all associated variables are invoked in the scope of the button and it immediately starts working.
Loopy Charts

The list on the right acts both as a highlighter for the current row, and as a control to select which month you want to see in the chart.

Update: Some people have reported issues with the previously attached XML. I’ve updated the report with 10.2.2.
AR-JS.txt (227 downloads)