20 February, 2008

Drill through reports in ASP.NET

In a recent ASP.NET application (coded in C# - natch) I had the cause to create a report, which contained a hyper link which in turn displayed a second report based on data from the first report. In researching this, I discovered this is called a drill through report.

I found information at CodeProject.com regarding drill down reports. The difference between what Shirley did, and what I was doing is that I already had the dataset for the report created. I created a dataset with two data tables in it. I haven't tried it, but I'm guessing that I could have easily put as many tables as I needed if I were going to drill down farther than just one level.

I used the same dataset for both reports. But when I went to perform the drill down, I received the following error:
"A data source instance has not been supplied for the data source [report data source name]"

So I slap my forehead, call my self a dunce, and make sure that both tables are represented by data sources (or would that be data sourcii?). At any rate, I kept getting the same error, which is what led me to googling the error message to see what the problem is. Based on Shirley's article, I realized that I would be required to set the data source for the new report in the ReportViewer_Drillthrough event handler.

Here is my code:

protected void ReportViewer_Drillthrough(object sender, Microsoft.Reporting.WebForms.DrillthroughEventArgs e)
{
Microsoft.Reporting.WebForms.
LocalReport localReport = (Microsoft.Reporting.WebForms.LocalReport)e.Report;
localReport.DataSources.Clear();
this.ObjectDataSource3.SelectParameters[0].DefaultValue = e.Report.GetParameters()[0].Values[0].ToString().Trim();
Microsoft.Reporting.WebForms.
ReportDataSource rds = new Microsoft.Reporting.WebForms.ReportDataSource("ReportDataSet_TABLE",this.ObjectDataSource3.Select());
localReport.DataSources.Add(rds);
}

A bit of breakdown for those who have a hard time following my crappy coding style:
First off, I set a LocalReport object ot the DrillthroughEventArgs.Report object. Then I clear the data sources because I'm going to add a new one, and I certainly don't want the report getting confused. I set the default value of the SelectParameters[0] (the only parameter for this report) to the only parameter that is passed. If there are more parameters in your report, then you would, of course, be required to set them all. For this instance, I had only one.

Then I had to create a new ReportDataSource object. At first I tried simply added the ObjectDataSource3 to my localReport.DataSources, but that didn't fly because I guess an ObjectDataSource is different from a ReportDataSource. Then I figured I would just cast the ObjectDataSource as a ReportDataSource. But that didn't fly either. You can't cast the former as the latter. So I was resigned to create a new one. I gave the new one the same name as the DataSource I defined in my report. Then I added the ReportDataSource to my localReport.DataSources collection, and voila!

Oh, and don't try to refresh the localReport. For some reason it seems to resort back to the parent report. I don't know why, and frankly don't really care, as long as I know how to handle it, it's all good.




Technorati Tags: , , , , ,

04 February, 2008

Working Client-Side with CheckBoxLists

One of the new things I have been playing with are CheckBoxLists. This is a server control which is DataBindable. It creates a list of CheckBox controls. You can define if the list is displayed horizontally, or vertically. I am using a couple of these in a new application and ran into some problems accessing them with JavaScript. This application doesn't ever do a real-honest-to-goodness postback. Instead, all calls to the server are handled through Callbacks. Callbacks aren't really what I'm here to talk about, so if you need to learn more about them, I would recommend popping on over to ASP.NET and checking out their forums and other areas of that site. It is chock full of excellent information.

Ok, back to where I was. Getting to the CheckBoxList stuff. The CheckBoxList renders its ID on the HTML side slightly different from a normal control. Since the control generates multiple sub-controls. For instance, normally, if you had a CheckBox and gave it the ID of "myCheckBox" then to access it in JavaScript you'd simply do something like var chkbx = document.getElementById('<%=myCheckBox.ClientID %>'); and voila! you have access to your CheckBox control. But as stated, the CheckBoxList generates multiple CheckBoxes. The problem was how in the world do I access these? How do I do capture a client side event with a databound list like this? I mean, the list could change daily right? What I came up with (and it probably isn't the _best_ way to go about it, but it certainly works) is to add an attribute to the generated CheckBox in the DataBound EventHandler for the CheckBoxList control. Using DataBound of course because that means the list has been completed.

The CheckBoxList, like the DropDownList (and several other controls) contains the .Items parameter which is the ListItemCollection object. What I ended up doing is adding an "onclick" attribute to each item, passing to the JavaScript the client id of the actual CheckBox control, the text and the value (really, I only needed the control client id and the text, but I figured I'd go ahead and throw in the value for good measure. I separated the ClientID, Text and Value data with a colon (:) so I could easily split it back on the client side.

protected void checkBoxList_DataBound(object sender, EventArgs e)
{
CheckBoxList cbl = (CheckBoxList)sender;
for (int i = 0; i < style="color: rgb(51, 51, 255);">string controlName = cbl.ClientID + "_" + i;
cbl.Items[i].Attributes.Add("onclick", "javascript:checkBoxChanged('" + controlName + ":" + cbl.Items[i].Text + ":" + cbl.Items[i].Value + "')");
cbl.Items[i].Selected = true;
}
}


I use the same DataBound Event Handler for all of the CheckBoxList controls since there is nothing happening that is actually specific to any one CheckBoxList, they all call the same JavaScript. However, getting the control information, and whether or not it is checked, back to the client side is only half the battle. The check boxes are part of a data filter which will display data in a GridView. The user can click on or off all or part of the list items to decrease or expand the scope of the data displayed. So I need to put these checked boxes into some kind of JavaScript array so that when the time comes to do the Callback to populate the GridView, I have all the required data to build the filter server-side.

var checkedBoxesArray = new Array();
function checkBoxChanged(controlInfo) {
var ary = controlInfo.split(":");
var chkBx = document.getElementById(ary[0]);
if(chkBx.checked) {
// check to see if the check box already exists (it shouldn't)
for(x in checkedBoxesArray) {
if(controlInfo == checkedBoxesArray[x]) return; // <-- get out of here, it's already there, and there is nothing else to do
}
checkedBoxesArray[checkedBoxesArray.length+1] = controlInfo;
}else {
for(x in checkedBoxesArray) {
// find the checkbox information in the array and remove it (it should be there)
if (controlInfo == checkedBoxesArray[x]) {
checkedBoxesArray.splice(x,1);
break; // <-- No use going through all of them if we don't have to , right?
}
}
}
}


Then I could access the checkedBoxesArray in the JavaScript function which actually creates the querystring style callback event arguments which are used on the server-side to create the data filter.