Dynamic Time Groups in an OLAP Cube

One of the challenges presented to me while I was helping out at the DHHS in New Hampshire was to find a way to make user defined year groups in the report. While it’s trivial to create year groupings in a cube, it becomes impossible when you need to account for every possible combination reports require. Sometimes you may need sets of 5 years starting at 1991. Other times the requirement may be every 2 years starting at 1980. Sometimes the users may want to see each year, 1991-1995, 1992-1996, 1993-1997; while other times the overlap is unnecessary, 1991-1995, 1996-2000, 2001-2006. Each measure would have to be aggregated for each grouping, increasing the size of the cube.

At first glance my solution is a bit complex. It uses JavaScript to control the appearance of the prompt and OLAP functions to set up the groups. Finally report expressions are used to control the labels in the chart and crosstab. Since the Cognos PowerCube samples have a limited set of years, I’ve adapted the technique to work on months instead.

In this example, the user is presented with three prompts. The first and second prompts allow the users to select the months they want. The first prompt has no overlap, if the group size selected is 6, it will show Jan-Jun and Jul-Dec. The second prompt will show Jan-Jun, Feb-Jul, Mar-Aug and so on. The third prompt shows the group size, defaulting to 6.
Prompts

The non-overlapping month prompt takes some work to get working. It is, essentially, filtering the month level where mod(monthNumber,groupSize) is 0. It is a little more complex, as that alone won’t work. First, the mod function isn’t supported by the cube, and will result in local processing. Second, it should be monthNumber – 1:

(((total(
    [One] within set periodsToDate(
        [sales_and_marketing].[Time].[Time].[Time]
      , currentMember([sales_and_marketing].[Time].[Time])
    )
  )-1)
  / #prompt('Group Size','integer','6')#)
  -
  floor((( total(
    [One] within set periodsToDate(
        [sales_and_marketing].[Time].[Time].[Time]
      , currentMember([sales_and_marketing].[Time].[Time])
    )
  )-1)
   / #prompt('Group Size','integer','6')#)))
  * #prompt('Group Size','integer','6')#

If you want the group to have a different start month, change the -1.

That will give us a set of every sixth month. 2010/Jan, 2010/Feb, 2011/Jan, 2011/Feb. But now we need to modify the appearance of the prompt values. For that we need JavaScript.

This JS will loop through the prompt, convert the month name into a numeric value, add the selected group size, then convert that number back into a month and concatenate it onto the label again.

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

function parseMonthName(name) {
  var Months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  for (i=0;i<12;i++){ if(Months[i]==name) {return i+1; break;}}
}

function addMonths(month,add){
  add=add?add:0;
  month = (month + add)%12;
  month=month==0?12:month;
  return month;
}

function getMonthName(month) { 
  var Months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  return Months[month-1];
}


function setMonthNames(startPrompt,endPrompt){

var Size
  , Start = startPrompt
  , End = endPrompt
  , Month , newMonth, newYear

// Loop through the groupSize list to set the size variable. Subtract 1 from the value since the value includes the current month.
  for(var i =0;i<fW._oLstChoices_groupSize.length;i++){if(fW._oLstChoices_groupSize[i].selected) Size=fW._oLstChoices_groupSize[i].value -1}

  for (var i = 0;i<Start.length;i++){
    if(!Start[i].getAttribute('Orig')) {Start[i].setAttribute('Orig' , Start[i].getAttribute('dv')); };
    Month = Start[i].getAttribute('Orig');

    // If the group size is set to 0, use the original label. This is for on the fly changes.
    if(Size==0) {
      Start[i].setAttribute('dv' , Month);
    }
    else {
      newMonth = parseMonthName(Month.substr(5,3)) ; 
      newYear = addMonths(newMonth,Size)<Size?parseInt(Month.substr(0,4))+1:Month.substr(0,4);
      Start[i].setAttribute('dv' , Month + '-' +  newYear + '/' + getMonthName(addMonths(newMonth,Size)));
    }
    Start[i].innerHTML= Start[i].getAttribute('dv') ;
    //if(i>=Size-1) {Start[i].display='none';} else {Start[i].display='';} //Really, IE? Really?!
  }
}
</script>

So far this has only served to make it easier for the end user to understand how he’s filtering his report. The prompts themselves are still passing single member unique names to the query. Another calculated data item will be needed to group the years.

member(
  total(
    currentMeasure 
    within set 
    lastPeriods(
      0-#prompt('Group Size','integer','5')#
      ,currentMember([sales_and_marketing].[Time].[Time])
    )
  )
  ,'Grouped Total'
  ,'Grouped Total'
  ,[sales_and_marketing].[Time].[Time])

The lastPeriods will return the next Group Size members on the same level. This calculated member can then be used as a tuple on any measure that needs to be grouped. In the attached report I simply added it as the default measure in the charts and crosstabs.

That will create the groupings, but the labels on the chart and crosstab will still only show a single month. To fix that we can use a report expression with the same logic as the JavaScript from the prompt page:

case 
  when ParamValue('Group Size') = '1' then [Report].[No Overlap] 
  else 
    [Report].[No Overlap]  + '-' 
    + number2string(
      string2int32(substring([Report].[No Overlap],1,4))
      + case 
        when mod (
          case substring([Report].[No Overlap],6,3)
            when 'Jan' then 1
            when 'Feb' then 2
            when 'Mar' then 3
            when 'Apr' then 4
            when 'May' then 5
            when 'Jun' then 6
            when 'Jul' then 7
            when 'Aug' then 8
            when 'Sep' then 9
            when 'Oct' then 10
            when 'Nov' then 11
            when 'Dec' then 12
          end 
          + string2int32(ParamValue('Group Size'))-1,12) between 1 and (string2int32(ParamValue('Group Size'))-1) 
        then 1 
        else 0 
      end
    )
    +'/'+
    case mod (
      case substring([Report].[No Overlap],6,3)
        when 'Jan' then 1
        when 'Feb' then 2
        when 'Mar' then 3
        when 'Apr' then 4
        when 'May' then 5
        when 'Jun' then 6
        when 'Jul' then 7
        when 'Aug' then 8
        when 'Sep' then 9
        when 'Oct' then 10
        when 'Nov' then 11
        when 'Dec' then 12
      end + string2int32(ParamValue('Group Size'))-1,12)
      when 1 then 'Jan'
      when 2 then 'Feb'
      when 3 then 'Mar'
      when 4 then 'Apr'
      when 5 then 'May'
      when 6 then 'Jun'
      when 7 then 'Jul'
      when 8 then 'Aug'
      when 9 then 'Sep'
      when 10 then 'Nov'
      when 11 then 'Oct'
      when 0 then 'Dec'
    end
end

Simply change the text source on the crosstab or chart node member to Report Expression and paste that in.

Charts and xtab

Report XML

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 (697 downloads)

Manipulate drill through definitions with JavaScript

This article is based on 10.2, and uses the 10.2 Prompt API. It can be easily adapted to earlier versions however.

When defining a drillthrough you can set various settings as report target, output format, locales and more. Unfortunately there is no easy way to change those settings on the fly. On the other hand, I just did all the work so now there is an easy way.

Drillthrough definitions come in two basic parts. The second part, which won’t be delved into in this article, has the actual values being passed. These can be seen in the links themselves.
drillthrough HTML
Looking at the code itself (modified for legibility):

<SPAN
  dtTargets='
    <drillTarget
      drillIdx="0"
      label="DrillThrough">
      <drillParameter
        name="Product Line"
        value="[sales_and_marketing].[Products].[Products].[Product line]-&amp;gt;:[PC].[@MEMBER].[991]"
        displayValue="Camping Equipment"/>
      <drillParameter
        name="Year"
        value="[sales_and_marketing].[Time].[Time].[Year]-&amp;gt;:[PC].[@MEMBER].[20100101-20101231]"
        displayValue="2010"/>
    </drillTarget>'>
  <SPAN class=hy tabIndex=-1>
    81,929,179.22
  </SPAN>
</SPAN>

This can be modified by looping through with JavaScript fairly easily, but I’ll leave that for another time.

The first part describes the actual settings – the metadata of the drill. This is stored in JavaScript object in the page, which makes it significantly easier to modify. Stringifying the object returns (again, modified for legibility):

[
  {"m_label":"DrillThrough"
  ,"m_outputFormat":"HTMLFragment"
  ,"m_outputLocale":"en-us"
  ,"m_showInNewWindow":"true"
  ,"m_method":"execute"
  ,"m_path":"/content/folder[@name='JavaScript Examples']/folder[@name='Modifying Drillthrough Definitions']/report[@name='Drill 1']"
  ,"m_bookmark":""
  ,"m_parameters":"<bus:parameters xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="bus:baseParameter[2]"><item xsi:type="bus:parameter"><bus:name xsi:type="xs:string">Product Line</bus:name><bus:type xsi:type="bus:parameterDataTypeEnum">memberUniqueName</bus:type></item><item xsi:type="bus:parameter"><bus:name xsi:type="xs:string">Year</bus:name><bus:type xsi:type="bus:parameterDataTypeEnum">memberUniqueName</bus:type></item></bus:parameters>"
  ,"m_objectPaths":"<bus:objectPaths xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="bus:searchPathSingleObject[4]"><item xsi:type="bus:searchPathSingleObject">storeID(&quot;i326070F7595B4B5BBCD38D945E66AAEB&quot;)</item><item xsi:type="bus:searchPathSingleObject">storeID(&quot;iDB36CF1A0D654E99B3117B0B6A7F8789&quot;)/model[last()]</item><item xsi:type="bus:searchPathSingleObject">/content/folder[@name=&apos;Samples&apos;]/folder[@name=&apos;Cubes&apos;]/package[@name=&apos;Sales and Marketing (cube)&apos;]/model[@name=&apos;2008-07-25T15:28:38.072Z&apos;]</item><item xsi:type="bus:searchPathSingleObject">/content/folder[@name=&apos;Samples&apos;]/folder[@name=&apos;Cubes&apos;]/package[@name=&apos;Sales and Marketing (cube)&apos;]/model[last()]</item></bus:objectPaths>"
  ,"m_prompt":"false"
  ,"m_dynamicDrillThrough":false
  ,"m_parameterProperties":"<PARAMETER-PROPERTIES/>"}
  ,
  {"m_label":"DrillThrough"
  ,"m_outputFormat":"HTMLFragment"
  ,"m_outputLocale":"en-us"
  ,"m_showInNewWindow":"true"
  ,"m_method":"execute"
  ,"m_path":"/content/folder[@name='JavaScript Examples']/folder[@name='Modifying Drillthrough Definitions']/report[@name='Drill 1']"
  ,"m_bookmark":""
  ,"m_parameters":"<bus:parameters xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="bus:baseParameter[2]"><item xsi:type="bus:parameter"><bus:name xsi:type="xs:string">Product Line</bus:name><bus:type xsi:type="bus:parameterDataTypeEnum">memberUniqueName</bus:type></item><item xsi:type="bus:parameter"><bus:name xsi:type="xs:string">Year</bus:name><bus:type xsi:type="bus:parameterDataTypeEnum">memberUniqueName</bus:type></item></bus:parameters>"
  ,"m_objectPaths":"<bus:objectPaths xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="bus:searchPathSingleObject[4]"><item xsi:type="bus:searchPathSingleObject">storeID(&quot;i326070F7595B4B5BBCD38D945E66AAEB&quot;)</item><item xsi:type="bus:searchPathSingleObject">storeID(&quot;iDB36CF1A0D654E99B3117B0B6A7F8789&quot;)/model[last()]</item><item xsi:type="bus:searchPathSingleObject">/content/folder[@name=&apos;Samples&apos;]/folder[@name=&apos;Cubes&apos;]/package[@name=&apos;Sales and Marketing (cube)&apos;]/model[@name=&apos;2008-07-25T15:28:38.072Z&apos;]</item><item xsi:type="bus:searchPathSingleObject">/content/folder[@name=&apos;Samples&apos;]/folder[@name=&apos;Cubes&apos;]/package[@name=&apos;Sales and Marketing (cube)&apos;]/model[last()]</item></bus:objectPaths>"
  ,"m_prompt":"false"
  ,"m_dynamicDrillThrough":false
  ,"m_parameterProperties":"<PARAMETER-PROPERTIES/>"}]

Each of these settings is easily changed (except for the the m_parameters and m_parameterProperties because that means changing the settings on the actual drills themselves). People with sharp eyes may notice that both drillthrough definitions are exactly the same. Why does it appear twice? The drillthrough appears twice in my report’s crosstab. The definition appears for the detail row and again for the totals row. If I was passing the individual month with the detail, then the first drillthrough in the object would include the month.

Because the drills can appear twice, any function written to rewrite the settings must include a loop to check the m_label in each element.

Consider the following:

<script>
var paulScripts = {},  oCR = cognos.Report.getReport( "_THIS_" );

paulScripts.getControl = function (promptName) {
  return oCR.prompt.getControlByName(promptName);
}

/*
 * function: changeDrillAttribute
 * By Paul Mendelson
 * This allows the user to change any attribute in the drill. Attributes may be:
 * m_label, m_outputFormat, m_outputLocale, m_showInNewWindow, m_method, m_path,
 * m_bookmark, m_parameters, m_objectPaths, m_prompt, m_dynamicDrillThrough,
 * m_parameterProperties
 * Drills may appear multiple times in the object, so the loop goes through the entire array. Each
 * matching drill will have the attribute changed as desired.
 * Valid m_outputFormat:  CSV, HTML, layoutDataXML, MHT,  PDF, rawXML, singleXLS, spreadsheetML, XLS,
 * XML, XLWA.
 * m_path: /content/folder[@name='folderName']/folder[@name='folderName']/report[@name='reportName']
 */

paulScripts.changeDrillAttribute = function (drillName,attribute,newValue){
  var oCV = window['oCV'+'_THIS_']
  , drillsObj = oCV.getRV().getCV().getDrillTargets();

  for(var i=0;i<drillsObj.length;i++){
   if( drillsObj[i].m_label == drillName) drillsObj[i][attribute] = newValue;
  }
}

</script>

paulScripts.changeDrillAttribute function will receive the name of the drill, the attribute to change, and the new value of that attribute. It will then loop through all of the elements in the drill targets, and if the label matches, change the attribute.

The following screenshot shows the crosstab containing the drill, and two radio button groups.
radios can change the drill

The radios have a validator set on them to invoke the changeDrillAttribute function.

<script>
paulScripts.getControl('Format').setValidator(
  function (values) {
  paulScripts.changeDrillAttribute ('DrillThrough','m_outputFormat',values[0].use);
  return true;
  }
);

paulScripts.getControl('Target').setValidator(
  function (values) {
  paulScripts.changeDrillAttribute ('DrillThrough','m_path',values[0].use);
  return true;
  }
);

</script>

Whenever the radio is changed, it will modify the drill appropriately. That setValidator function is the only use of the new Prompt API, so this can be changed very easily to previous versions by using onChange events.

Ultimately the end user can decide which report he wants to drill to, and in what format, without having to right-click. Because users don’t like right-clicking.

The following XML is the report. Remember to create two targets for the drill and to fix the value prompt. To get the path of the report, simply go to the properties of the report, click on “View the search path, ID and URL” and copy the search path.

<report xmlns="http://developer.cognos.com/schemas/report/9.0/" useStyleVersion="10" expressionLocale="en-us">
				<modelPath>/content/folder[@name='Samples']/folder[@name='Cubes']/package[@name='Sales and Marketing (cube)']/model[@name='2008-07-25T15:28:38.072Z']</modelPath>
				<drillBehavior modelBasedDrillThru="true"/>
				<queries>
					<query name="Query1">
						<source>
							<model/>
						</source>
						<selection><dataItemLevelSet name="Year"><dmLevel><LUN>[sales_and_marketing].[Time].[Time].[Year]</LUN><itemCaption>Year</itemCaption></dmLevel><dmDimension><DUN>[sales_and_marketing].[Time]</DUN><itemCaption>Time</itemCaption></dmDimension><dmHierarchy><HUN>[sales_and_marketing].[Time].[Time]</HUN><itemCaption>Time</itemCaption></dmHierarchy></dataItemLevelSet><dataItemLevelSet name="Quarter"><dmLevel><LUN>[sales_and_marketing].[Time].[Time].[Quarter]</LUN><itemCaption>Quarter</itemCaption></dmLevel><dmDimension><DUN>[sales_and_marketing].[Time]</DUN><itemCaption>Time</itemCaption></dmDimension><dmHierarchy><HUN>[sales_and_marketing].[Time].[Time]</HUN><itemCaption>Time</itemCaption></dmHierarchy></dataItemLevelSet><dataItemLevelSet name="Product line"><dmLevel><LUN>[sales_and_marketing].[Products].[Products].[Product line]</LUN><itemCaption>Product line</itemCaption></dmLevel><dmDimension><DUN>[sales_and_marketing].[Products]</DUN><itemCaption>Products</itemCaption></dmDimension><dmHierarchy><HUN>[sales_and_marketing].[Products].[Products]</HUN><itemCaption>Products</itemCaption></dmHierarchy></dataItemLevelSet><dataItemMeasure name="Revenue"><dmMember><MUN>[sales_and_marketing].[Measures].[Revenue]</MUN><itemCaption>Revenue</itemCaption></dmMember><dmDimension><DUN>[sales_and_marketing].[Measures]</DUN><itemCaption>Measures</itemCaption></dmDimension><XMLAttributes><XMLAttribute name="RS_dataType" value="9" output="no"/></XMLAttributes></dataItemMeasure></selection>
					</query>
				</queries>
				<layouts>
					<layout>
						<reportPages>
							<page name="Page1"><style><defaultStyles><defaultStyle refStyle="pg"/></defaultStyles></style>
								<pageBody><style><defaultStyles><defaultStyle refStyle="pb"/></defaultStyles></style>
									<contents>
										<HTMLItem>
			<dataSource>
				<staticValue>&lt;script&gt;
var paulScripts = {},  oCR = cognos.Report.getReport( "_THIS_" );

paulScripts.getControl = function (promptName) {
  return oCR.prompt.getControlByName(promptName);
}

/*
 * function: changeDrillAttribute
 * Paul Mendelson
 * This allows the user to change any attribute in the drill. Attributes may be:
 *   m_label, m_outputFormat, m_outputLocale, m_showInNewWindow, m_method, m_path,
  * m_bookmark, m_parameters, m_objectPaths, m_prompt, m_dynamicDrillThrough,
  * m_parameterProperties
  * Drills may appear multiple times in the object, so the loop goes through the entire array. Each
  * matching drill will have the attribute changed as desired.
  *  Valid m_outputFormat:  CSV, HTML, layoutDataXML, MHT,  PDF, rawXML, singleXLS, spreadsheetML, XLS,
  * XML, XLWA.
  * m_path: /content/folder[@name='folderName']/folder[@name='folderName']/report[@name='reportName']
  */

paulScripts.changeDrillAttribute = function (drillName,attribute,newValue){
  var oCV = window['oCV'+'_THIS_']
  , drillsObj = oCV.getRV().getCV().getDrillTargets();

  for(var i=0;i&lt;drillsObj.length;i++){
   if( drillsObj[i].m_label == drillName) drillsObj[i][attribute] = newValue;
  }
}

  &lt;/script&gt;
	</staticValue>
			</dataSource>
		</HTMLItem>
									<table><style><defaultStyles><defaultStyle refStyle="tb"/></defaultStyles><CSS value="border-collapse:collapse"/></style><tableRows><tableRow><tableCells><tableCell><contents><crosstab refQuery="Query1" horizontalPagination="true" name="Crosstab1">
											<crosstabCorner><style><defaultStyles><defaultStyle refStyle="xm"/></defaultStyles></style><contents><textItem><dataSource><dataItemLabel refDataItem="Revenue"/></dataSource></textItem></contents></crosstabCorner>

											<noDataHandler>
												<contents>
													<block>
														<contents>
															<textItem>
																<dataSource>
																	<staticValue>No Data Available</staticValue>
																</dataSource>
																<style>
																	<CSS value="padding:10px 18px;"/>
																</style>
															</textItem>
														</contents>
													</block>
												</contents>
											</noDataHandler>
											<style>
												<defaultStyles>
													<defaultStyle refStyle="xt"/>
												</defaultStyles>
												<CSS value="border-collapse:collapse"/>
											</style>
										<crosstabRows><crosstabNode><crosstabNestedNodes><crosstabNode><crosstabNodeMembers><crosstabNodeMember refDataItem="Quarter" edgeLocation="e2"><style><defaultStyles><defaultStyle refStyle="ml"/></defaultStyles></style><contents><textItem><dataSource><memberCaption/></dataSource></textItem></contents></crosstabNodeMember></crosstabNodeMembers></crosstabNode><crosstabNode><crosstabNodeMembers><crosstabNodeMember refDataItem="Year" edgeLocation="e3"><style><defaultStyles><defaultStyle refStyle="ml"/></defaultStyles></style><contents><textItem><dataSource><memberCaption/></dataSource></textItem></contents></crosstabNodeMember></crosstabNodeMembers></crosstabNode></crosstabNestedNodes><crosstabNodeMembers><crosstabNodeMember refDataItem="Year" edgeLocation="e1"><style><defaultStyles><defaultStyle refStyle="ml"/></defaultStyles></style><contents><textItem><dataSource><memberCaption/></dataSource></textItem></contents></crosstabNodeMember></crosstabNodeMembers></crosstabNode></crosstabRows><crosstabColumns><crosstabNode><crosstabNodeMembers><crosstabNodeMember refDataItem="Product line" edgeLocation="e4"><style><defaultStyles><defaultStyle refStyle="ml"/></defaultStyles></style><contents><textItem><dataSource><memberCaption/></dataSource></textItem></contents></crosstabNodeMember></crosstabNodeMembers></crosstabNode></crosstabColumns><defaultMeasure refDataItem="Revenue"/><crosstabFactCell><contents><textItem><dataSource><cellValue/></dataSource><reportDrills><reportDrill name="DrillThrough"><drillLabel><dataSource><staticValue/></dataSource></drillLabel><drillTarget method="execute" showInNewWindow="true"><reportPath path="/content/folder[@name='JavaScript Examples']/folder[@name='Modifying Drillthrough Definitions']/report[@name='Drill 1']"><XMLAttributes><XMLAttribute name="ReportName" value="Drill 1" output="no"/><XMLAttribute name="class" value="report" output="no"/></XMLAttributes></reportPath><drillLinks><drillLink><drillTargetContext><parameterContext parameter="Product Line"/></drillTargetContext><drillSourceContext><dataItemContext refDataItem="Product line"/></drillSourceContext></drillLink><drillLink><drillTargetContext><parameterContext parameter="Year"/></drillTargetContext><drillSourceContext><dataItemContext refDataItem="Year"/></drillSourceContext></drillLink></drillLinks></drillTarget></reportDrill></reportDrills><style><defaultStyles><defaultStyle refStyle="hy"/></defaultStyles></style></textItem></contents><style><defaultStyles><defaultStyle refStyle="mv"/></defaultStyles></style></crosstabFactCell></crosstab></contents></tableCell><tableCell><contents><table><style><defaultStyles><defaultStyle refStyle="tb"/></defaultStyles><CSS value="border-collapse:collapse"/></style><tableRows><tableRow><tableCells><tableCell><contents><selectValue parameter="Format" selectValueUI="radioGroup" name="Format"><selectOptions><selectOption useValue="PDF"/><selectOption useValue="spreadsheetML"/><selectOption useValue="HTMLFragment"/><selectOption useValue="HTML"/></selectOptions><defaultSelections><defaultSimpleSelection>HTMLFragment</defaultSimpleSelection></defaultSelections></selectValue></contents></tableCell></tableCells></tableRow><tableRow><tableCells><tableCell><contents><selectValue parameter="Target" name="Target" selectValueUI="radioGroup"><selectOptions><selectOption useValue="/content/folder[@name='JavaScript Examples']/folder[@name='Modifying Drillthrough Definitions']/report[@name='Drill 1']"><displayValue>Target 1</displayValue></selectOption><selectOption useValue="/content/folder[@name='JavaScript Examples']/folder[@name='Modifying Drillthrough Definitions']/report[@name='Drill 2']"><displayValue>Target 2</displayValue></selectOption></selectOptions><defaultSelections><defaultSimpleSelection>/content/folder[@name='JavaScript Examples']/folder[@name='Modifying Drillthrough Definitions']/report[@name='Drill 1']</defaultSimpleSelection></defaultSelections></selectValue></contents></tableCell></tableCells></tableRow></tableRows></table></contents><style><CSS value="vertical-align:top"/></style></tableCell></tableCells></tableRow></tableRows></table><HTMLItem>
			<dataSource>
				<staticValue>&lt;script&gt;
paulScripts.getControl('Format').setValidator(
  function (values) {
  paulScripts.changeDrillAttribute ('DrillThrough','m_outputFormat',values[0].use);
  return true;
  }
);

paulScripts.getControl('Target').setValidator(
  function (values) {
  paulScripts.changeDrillAttribute ('DrillThrough','m_path',values[0].use);
  return true;
  }
);

&lt;/script&gt;</staticValue>
			</dataSource>
		</HTMLItem></contents>
								</pageBody>

							</page>
						</reportPages>
					</layout>
				</layouts>
			<XMLAttributes><XMLAttribute name="RS_CreateExtendedDataItems" value="true" output="no"/><XMLAttribute name="listSeparator" value="," output="no"/><XMLAttribute name="RS_modelModificationTime" value="2008-07-25T15:28:38.133Z" output="no"/></XMLAttributes><reportName>Report</reportName></report>