Thursday, September 28, 2006

Actuate: Changing a Rows Color with a Button

I was incredibly surprised to get an Actuate question thrown my way, and a good one at that. I normally don’t get Actuate questions, so I was a little excited to try to figure this one out.

The question was “How to Change a row color after clicking a button in Actuate?”

This was not as easy as I thought it would be. My first instinct was to try to override a method for to force a color change, however I ran into a few issues. First, was IServer strips out Basic events when it renders online. So if I was to publish this report online, I would not be able to use the overridden method. Also, I would need a way to force a refresh of the page without recalling the entire report, which there doesn’t seem to be a way to do this using Actuate Basic.

Fortunately, however, Actuate provides the Browser Scripting component. Since reports are essentially HTML files, a little JavaScript and some custom overridden methods to provide tags can go a long way in solving this issue.

For this example, I am using a real simple report to pull all the roles that a particular Actuate user has in the system. The report is driven off of a basic select query. There is a single JavaScript function that will receive as a parameter the ID of the calling object. It will take that object ID, create an Element object and set to the calling object. It then goes up the DOM tree to the parent of the calling object; checks the parent children nodes for an object with a Style tag set to the background color, then change that background color. For more complex reports, a little more logic and care would be called for in order to select the correct object to set. The key to getting this to trigger correctly is to be sure to explicitly set the background color of the row to change the color.

The second browser scripting control is simple creates a button. What I did is I overrode the Browser Scripting Controls “BrowserCode” method to return a button with an ID that is specifically set to an easy to pass value for the search function defined above. In order to uniquely define each button, I have a global parameter that gets incremented on each BrowserCode function call and uses that in the ID. It doesn’t need to be exact, or sequential, just unique.


Figure 1. Workspace


Figure 2. Setting a Global Parameter


In Figure 1 above you can see the report design. It is very basic. Notice the two browser scripting controls. I put one in the Before report containers content section. This is here since the query driving this section will only return 1 result. This is where the JavaScript function is defined at. This could actually go into the page header, or the master layout. I kept it here out of pure laziness since I didn’t want to change views. Below is the code for the browser scripting control:
<script language="JavaScript" type="text/javascript">
function ChangeRowColor(callingObjectID)
{
//Get the marker tag for the clicked on button
var childElement = document.getElementById(callingObjectID);
//Variable to hold the node witht he found background attributes to change
var foundNode = null;
//Generic counter x
var x;

//Set the parent element of the caller
var parentElement = childElement.parentNode;

//Go up the DOM tree while we have not found our target and we have not reached the root DOCUMENT element
while ((!foundNode) && (parentElement.nodeName != "#document"))
{
//Go through each child element in the current parent, check if it has a style element defined and a background element. If so,
//then we found our target. This will find the first element to match these conditions only, so be sure to keep other surrounding elements
//background at default, which "usually" (not always) does not include them in the output
for (x = 0; x < parentElement.childNodes.length; x++)
if ((!foundNode) && (parentElement.childNodes[x].style) && (parentElement.childNodes[x].style.backgroundColor))
{
foundNode = parentElement.childNodes[x];
//alert("Found!");
break;
}

//we went through all the child elements and did not find our result, move up the tree
if (!foundNode)
parentElement = parentElement.parentNode;
}

//After going through the DOM tree, if we found our element, then change the background color to our predefined background color. Otherwise, set it to
//the white
if (foundNode)
{
alert(foundNode.id + " - " + foundNode.style.backgroundColor);
if (foundNode.style.backgroundColor == "#ffffff")
foundNode.style.backgroundColor = "#B3D9E6";
else
foundNode.style.backgroundColor = "#ffffff";
}
}
</script>

In the details band is where I stuck the second BrowserScripting control with the button definition. The overridden BrowserCode function looks like so:

Function BrowserCode( ) As String
BrowserCode = Super::BrowserCode( )
' Insert your code here
BrowserCode = "<input name='Submit' id='Button" & CurrentRowNum & "' type='submit' onclick='ChangeRowColor(this.id);' value='Submit' />"
CurrentRowNum = CurrentRowNum + 1
End Function


Figure 3. Set Background Color

Now the final piece of the puzzle that makes this whole thing work, be sure to set the element whose color you want to change to your default background color, in my case White. Figure 3 illustrates this. The reason this works is the browser control with the button and the text field should reside in the same DIV tag. When the function goes to the parent element, it should be the DIV tag, then search the DIV tags children and it will find the element with the background color set, which is the text field. The Text Field needs to be the first element with a background color tag also; otherwise the wrong object will change color.

Now, I publish the report to IServer and preview the results. Figure 4 shows the finished report. Now, when I click on one of the buttons, the corresponding rows color will change. This can also work if the rows text is a hyperlink. Then, you can bypass the entire search process and just use the calling objects ID.

Figure 4. Finished Report

No comments: