Accessing Tree Prompts with Javascript (on Cognos 10.1.1)

There are many reasons why you may want to interact with a Tree Prompt with JavaScript. Maybe you want to enable the finish button if a member on the bottom level is selected, or to select the last member, or to ensure only 5 members are selected.

This post isn’t to detail every possible scenario, but to detail the some functions available and how to use them. It’s important to note that I am hardly a JavaScript expert, so there may be better ways to do anything I say here.

First you need to identify your tree prompt. Unlike most prompt controls, where the identifier changes based on the type viewer being used, the tree prompt can be called using window.treePROMPTNAME. Unfortunately we can’t apply an onmousedown event to the tree prompt, so we have to wrap it in a div.

Createa tree prompt and give it the name “Time”. Drag an HTML item to the left of the tree prompt

<div id='myTree'>

and an HTML item to the right

</div>

.

Now we can attach an event to capture the clicks:

<script>
document.getElementById('myTree').onmousedown=function(){runTree('Time')};
</script>

Any click inside that div will now trigger the runTree function passing ‘Time’ as an argument.

Because there are a number of JavaScript functions are run upon selecting an element we can’t immediately get the value of the element. So we can use the setTimeout function to wait 200 milliseconds before getting the data.

<script>
function runTree(id)
{
 t=setTimeout('checkTree("'+id+'")',200);
}
</script>

After 200 milliseconds the checkTree function will run, also passing Time as the argument.

<script>
function checkTree(id)
{
  selectedTreeNode  = window['tree'+id].getLastSelectedNode();
  if(!selectedTreeNode) {return}
  alert(selectedTreeNode.getName());
  alert(selectedTreeNode.getValue());
  alert(selectedTreeNode.getLevel());

}
</script>

The checkTree function will now alert the selected elements Name, MUN, and Tree level. Note the Tree Level is from the tree prompt, not the member’s hierarchy level. But knowing these, we can then call other functions. You could check the level number of the selected element and enable or disable the tree prompt while popping up a message.

You can programmatically set the default value of the tree prompt using JavaScript. Unfortunately it appears it is only possible to do this on the first level.

<script>
var node = window.treeTime.getRootNode().getChildren()[window.treeTime.getRootNode().getChildren().length-1];
node.setSelected(true);
node.updateNodeSelection();
node.updateParent();
window.treeTime.setLastSelectedNode(node);
</script>

This will only effect the prompt after the page has been loaded. Prompt pages should be fine, but prompts on the report page will need to have a default value set in the prompt macro.

I learned about these functions by going through the js file associated with tree prompts. Check out ..webcontentpromptingCTreeIE5NS6.js for more Tree Prompt functions.

It worth noting that these functions are written by IBM, and are liable to change on upgrade. I’d be interested in hearing if these work in any of the previous versions of Cognos.

Layout Component References

Zephyr, author of the blog Cognos and Me, wrote an excellent (but brief) article on what Layout Components are, and why you’d want to use them. I’m going to expand briefly on what he wrote, and give an example of my own.

In his example, he created a generic header that authors would use in their reports. This allows the author of the components report to control the header in all of the reports referencing it. Very useful for maintaining a corporate look and feel.

There is another use. While the Cognos authors that I work with excel in SQL and MDX, many of them are lacking knowledge in JavaScript. One of the most common requests for help that I receive is to code a simple container that allows users to switch between viewing a table or a graph.

In order to accomplish this, you would need to use JavaScript. So, instead of having to help each author individually, I find it easier to create a single component library report. To create the component library, simply create a new report. Since this is post is about components, and not about JavaScript I’ll give you the XML of an example components library.

<report xmlns="http://developer.cognos.com/schemas/report/3.0/" expressionLocale="en-us"><!--RS:8.2-->
	<modelPath>/content/package[@name='GO Sales']/model[@name='model']</modelPath>
	<layouts>
		<layout>
			<reportPages>
				<page class="pg" name="Page1">
					<pageBody class="pb">
						<contents><table class="tb" name="Switch Container"><tableRows><tableRow><tableCells><tableCell><contents><textItem name="SW - Object Name"><dataSource><staticValue>Object Name</staticValue></dataSource></textItem></contents><style><CSS value="padding-left:3px;padding-top:2px;padding-bottom:2px;font-weight:bold;background-color:#FAFAFA;border-top:1pt solid silver;border-bottom:1pt solid silver;border-left:1pt solid silver"/></style></tableCell><tableCell><contents><HTMLItem description="span">
														<dataSource>
															<staticValue>&lt;span onclick="
table = this.parentNode.parentNode.parentNode;
divArr = table.getElementsByTagName('Div');
for (var i = 0; i &lt; divArr.length; i++) {
  if (divArr[i].getAttribute('id') != null &amp;&amp; divArr[i].getAttribute('id') == 'First' ) {divArr[i].style.display = 'block';}
  if (divArr[i].getAttribute('id') != null &amp;&amp; divArr[i].getAttribute('id') == 'Second' ) {divArr[i].style.display = 'none';}
}
this.style.fontWeight='bold';
this.nextSibling.nextSibling.style.fontWeight='normal';"
style="font-weight:bold"
&gt;</staticValue>
														</dataSource>
													</HTMLItem>
													<textItem name="SW - First Label"><dataSource><staticValue>Graph</staticValue></dataSource></textItem><HTMLItem description="span">
														<dataSource>
															<staticValue>&lt;/span&gt; | &lt;span onClick="
table = this.parentNode.parentNode.parentNode;
divArr = table.getElementsByTagName('Div');
for (var i = 0; i &lt; divArr.length; i++) {
  if (divArr[i].getAttribute('id') != null &amp;&amp; divArr[i].getAttribute('id') == 'First' ) {divArr[i].style.display = 'none';}
  if (divArr[i].getAttribute('id') != null &amp;&amp; divArr[i].getAttribute('id') == 'Second' ) {divArr[i].style.display = 'block';}
}
this.style.fontWeight='bold';
this.previousSibling.previousSibling.style.fontWeight='normal';"
style="font-weight:normal"
&gt;</staticValue>
														</dataSource>
													</HTMLItem>
													<textItem name="SW - Second Label"><dataSource><staticValue>Table</staticValue></dataSource></textItem><HTMLItem description="/span">
														<dataSource>
															<staticValue>&lt;/span&gt;</staticValue>
														</dataSource>
													</HTMLItem>
												</contents><style><CSS value="text-align:right;padding-right:3px;padding-top:2px;padding-bottom:2px;background-color:#FAFAFA;border-top:1pt solid silver;border-bottom:1pt solid silver;border-right:1pt solid silver"/></style></tableCell></tableCells></tableRow><tableRow><tableCells><tableCell colSpan="2"><contents><block>
														<contents><HTMLItem description="div First">
														<dataSource>
															<staticValue>&lt;div id="First" &gt;</staticValue>
														</dataSource>
													</HTMLItem><textItem name="SW - First"><dataSource><staticValue>First</staticValue></dataSource></textItem><HTMLItem description="div Second">
														<dataSource>
															<staticValue>&lt;/div&gt;&lt;div id="Second" style="display:none"&gt;</staticValue>
														</dataSource>
													</HTMLItem><textItem name="SW - Second"><dataSource><staticValue>Second</staticValue></dataSource></textItem><HTMLItem description="/div">
														<dataSource>
															<staticValue>&lt;/div&gt;
</staticValue>
														</dataSource>
													</HTMLItem></contents>
														<style><CSS value="width:350px;height:350px;overflow:auto;text-align:center"/></style></block>

												</contents><style><CSS value="text-align:center;background-color:#FAFAFA;vertical-align:middle;border-bottom-style:none;border-top:1pt solid silver;border-left:1pt solid silver;border-right:1pt solid silver"/></style></tableCell></tableCells></tableRow><tableRow><tableCells><tableCell><contents><textItem name="SW - More Data Left"><dataSource><staticValue>More Data Left</staticValue></dataSource></textItem></contents><style><CSS value="padding-left:3px;background-color:#FAFAFA;border-bottom:1pt solid silver;border-left:1pt solid silver"/></style></tableCell><tableCell><contents><textItem name="SW - More Data Right"><dataSource><staticValue>More Data Right</staticValue></dataSource></textItem></contents><style><CSS value="text-align:right;padding-right:3px;background-color:#FAFAFA;border-bottom:1pt solid silver;border-right:1pt solid silver"/></style></tableCell></tableCells></tableRow></tableRows><style><CSS value="border-collapse:collapse"/></style></table></contents>
					</pageBody>
				</page>
			</reportPages>
		</layout>
	</layouts>
</report>

It was written in 8.2, but it upgrades perfectly. As the report has no queries, you can simply point it to any package you have. When the report loads, you should see the following:

It’s important to note the names. The table is named Switch Container. This is what the report author will select when he uses it from the Layout Component. Each text item is also named. The author will be able to use the component override to replace those items as needed. If the author doesn’t need links for more data below the graph/table, he simply overrides the two bottom items without replacing them. The author can also use the same component multiple times in the same report. When writing JavaScript functions, you should take this possibility into account.

The library needs to be saved in a location that both the author and users can traverse and execute. Any report will fail if the user cannot access the original components library. The components library also needs to exist in the same location in the production environment.

There are many other possible uses for layout component. Any time you need complex functionality in multiple reports components should be considered.

JavaScript stopped working in Cognos portal

Today I got a call from a friend. Shortly before going to production it was noticed that a report that works flawlessly from Report Studio, and from clicking on the report link in the portal, doesn’t work in the Cognos Portal.

The report relies on JavaScript to alter the prompts. When he runs the report from RS, everything is beautiful. Prompt headers are removed, default options are set, and everything behaves as expected. However, once the report was placedin a Cognos Viewer portlet in the portal an “Object Expected” error would be thrown every time the report was run.

It turns out there are several significant differences between the Report Viewer (from RS) and the Cogons Viewer (what’s used in the portlets). I cordially invite (okay, I’m begging. I really don’t want to go through a few thousand lines of JS.) any actual Cognos Devs to explain the differences between RV and CV.

An example of the original code:

<script language="javascript">
var f = getFormWarpRequest();
var list = f._oLstChoicesPrompt_Years;

list.remove(1);
list.remove(0);
list.removeAttribute("hasLabel");

canSubmitPrompt();
</script>

It removes the first two rows from the prompt (prompt label and the —), then removes the “hasLabel” attribute (which prevents those two rows values from being selected).

The corrected code is:

<script language="javascript">
var fW = (typeof getFormWarpRequest == "function" ? getFormWarpRequest() : document.forms["formWarpRequest"]);
if ( !fW || fW == undefined)
   { fW = ( formWarpRequest_THIS_ ? formWarpRequest_THIS_ : formWarpRequest_NS_ );}

var list = fW._oLstChoicesPrompt_Years;

list.remove(1);
list.remove(0);
list.removeAttribute("hasLabel");

canSubmitPrompt();
</script>

It was a very simple adaptation of the code found at IBM here.

This method will work in all versions from 8.3 and above (at least until they change the engine again).