Monday, March 14, 2011

DD Reports - Advanced interactive charts, part 1

General
One of the amazing things about charts in Data Dynamics Reports is they may be interactive. You can check out chart drillthrough screencast to see this feature in action. But perhaps you need to adjust the way the charts show the subsequent data by displaying the popup window instead of displaying the drillthrough report in the same window? Yes, sir, Data Dynamics Reports allows to do this. In this article we will see how it can be achieved in the windows-forms based report viewer. The next article will give some ideas on how to implement this feature in the web-based report viewer. As usual let's formulate the problem and then solve it step by step.

The problem
DistrictSales.xml(change extension to rdlx) displays the pie chart of the sales by district. If a user clicks on the piece of pie, then the StoreSales.xml(change extension to rdlx) is opened and shows the detailed information about the sales by store in the corresponding district. Now we want to adjust this interactivity - if a user clicks on the piece of pie, then the popup window hosting the sales by store appears. For your convenience I created AdvancedChartDrillthrough.zip we can start with, implementing the requirements step by step. The rest of the article walks through these steps.

In action
(1)Open AdvancedChartDrillthrough project in Visual Studio 2008. Adjust the references to DDR assemblies if it's needed, compile and run it. The main form hosts the viewer that displays DistrictSales report with the default interactivity - clicking on the piece of the pie chart shows StoreSales report in the same window. We are about to adjust it.

(2)Open the code of MainForm.cs, add the handler of Action event of the viewer before the call to OpenReport method, like below:

public MainForm()
{
// the rest of the code
viewer.Action += viewer_Action;
viewer.OpenReport(new FileInfo("DistrictSales.rdlx"));
}
void viewer_Action(object sender, DataDynamics.Reports.ActionEventArgs e)
{
}

(3)Action event is raised when a user clicks an interactive element - hyperlink, drill through link or bookmark link. In the handler of Action method you can cancel the default processing and perform the custom steps, like opening the drillthrough reports in the popup window. Let's start with handling action event - we will extract the information about report to jump and set its parameters values:

void viewer_Action(object sender, ActionEventArgs e)
{
// we are interested in drill through actions only.
if(e.Action.ActionType==ActionType.DrillThrough)
{
// cancel the default processing
e.Cancel = true;
var drillthroughInformation = e.Action.Drillthrough;
// get the report to jump
ReportRuntime drillthroughReport = drillthroughInformation.ReportRuntime as ReportRuntime;
if(drillthroughReport == null)
throw new InvalidOperationException("unable to get the drillthrow report!");
// set the parameters if any
for(var i=0;i<drillthroughInformation.NumberOfParameters;i++)
{
drillthroughReport.Parameters[i].CurrentValue = drillthroughInformation.Parameters[i].Value;
}
}
}

(4) The remaining steps are trivial - we need to open the drillthrough report in the popup form. To do this let's adjust the code of PopupForm.cs - extract "viewer" field and add OpenReport method:

public partial class PopupForm : Form
{
private readonly ReportPreview viewer = null;
public PopupForm()
{
InitializeComponent();
viewer = new ReportPreview();
viewer.IsToolbarVisible = false;
viewer.PageBorder = PageBorder.None;
viewer.Dock = DockStyle.Fill;
viewer.PreviewMode = PreviewMode.Layout;
Controls.Add(viewer);
}
public void OpenReport(ReportRuntime reportRuntime)
{
viewer.OpenReport(reportRuntime, "sales by store");
}
}

and finally let's show the popup form in viewer_Action handler code(at the end of the method body):

void viewer_Action(object sender, ActionEventArgs e)
{
// the rest of the code
var popupForm = new PopupForm();
popupForm.Show();
popupForm.OpenReport(drillthroughReport);
}

(5)AdvancedChartDrillthrough_final.zip the final version of AdvancedChartDrillthrough project.


Next time
Interactivity involves the popup form on the web-based report viewer is going to be interesting to deal with!

Thursday, February 17, 2011

Data Dynamics Reports: One touch printing on the web

Preface
Some time ago I blogged around ideas on no-touch printing on the web in ActiveReports. A lot of the new features were added in both of ActiveReports and Data Dynamics Reports since from that blog post was published. Particularly we have updated PDF export capabilities so that they allow performing one touch printing easily. In this article I will explain it in details.

The problem
Let's imagine that we have the simple Html page. It hosts the list of the product categories(obtained from Northwind database, Categories table) and "Print" button. We need to implement the following functionality: a user selects the category, clicks Print, the server returns the PDF document that shows the list of the products from the selected category(obtained from Northwind database, Products table), PDF document starts printing immediately - a user should get the print dialog of PDF viewer and the document should not be opened in the viewer.

One-touch printing in action
Let's create the web-site that implements the functionality described in "Problem" section:
products.xml(change the extension to Rdlx) is the report that accepts category id parameter and displays the list of the products accordingly. Copy products.rdlx in the root folder of the web-site.
Now copy default.htm in the root folder of the web-site and let's look at the page body:

<body>
<label>Select the category to print the list of the product of:</label><br />
<select id="categoryList">
<option value="1">Beverages</option>
<option value="2">Condiments</option>
<option value="3">Confections</option>
<option value="4">Dairy Products</option>
<option value="5">Grains/Cereals</option>
<option value="6">GMeat/Poultry</option>
<option value="7">Produce</option>
<option value="8">Seafood</option>
</select>
<input id="btnPrint" type="button" value="Print" />
<script>
$("#btnPrint").click(function() {
var catId = $("#categoryList").val();
$("#pdfContentPlaceHolder").attr("src", "GetData.aspx?catID="+catId);
});
</script>
<iframe style="visibility:hidden" id="pdfContentPlaceHolder" width="0" height="0" />
</body>

So, in the handler of of Print button click we set the source of the invisible frame to the data that are returned by GetData.aspx page. Once the document is loaded in the frame, the printing should start immediately. How is it achieved? Copy GetData.aspx in the root folder of the web-site and let's look at its Page_Load code:

void Page_Load(object sender, EventArgs e)
{
int catId;
if(!int.TryParse(Request.QueryString["catId"], out catId))
{
Response.Write("Invalid category Id parametere value! No output you will get");
return;
}
var fi = new FileInfo(Server.MapPath("~/Products.rdlx"));
var def = new ReportDefinition(fi);
var runtime = new ReportRuntime(def);
runtime.Parameters[0].CurrentValue = catId;
var pdf = new PdfRenderingExtension();
var streams = new MemoryStreamProvider();
var settings = (pdf as IConfigurable).GetSupportedSettings().GetSettings();
settings["PrintOnOpen"] = "true";
runtime.Render(pdf, streams, settings);
Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "inline; filename=products.pdf");
using(var stream = streams.GetPrimaryStream().OpenStream())
{
var len = (int)stream.Length;
var bytes = new byte[len];
stream.Read(bytes, 0, len);
Response.BinaryWrite(bytes);
}
}

The code is quite trivial, there are two relatively new features in use though:

  • MemoryStreamProvider allows to export the reports to the memory instead of the disk files. It is pretty convenient for web-scenarios where you don't want to use file system aggressively.

  • We set PrintOnOpen setting of PDF export to true. It will force PDF document to start printing immediately once it is loaded in the invisible frame on default.htm

The task is completed.


ActiveReports
It is possible to use the same technology in ActiveReports. The setting for PDF export filter document option is called OnlyForPrint, it is hidden in the intellisence, but you can use it:

PdfExport pdf = new PdfExport();
pdf.PdfDocumentOptions.OnlyForPrint=true;

Tuesday, February 8, 2011

DDR: Using data visualizers to build a thermometer chart

Preface
Data Dynamics Reports does not include circular gauge and meters controls. We did not add them with good reason - they tend to be misleading and and take a lot of space on the screen. Instead we provide Bullet and Sparkline graphs that can visualize data quite effectively. However, as one of our customers rightfully mentioned, the sales folks adore meters controls - speedometer, thermometer, etc. and it would be good to have these controls in the product. This article shows how to compose a thermometer chart by using wonderful built-in feature called "DataBar visualizer".

The problem
Let's imagine that we are about to build the patients temperature report that is sent to chief of a hospital department every morning. The report shows the table that consists of two columns: patient name and patient temperature that was measured at 6am. The chief demands that the temperature value is visualized by a gauge, like this one:

The thermometer has 3 ranges:

  • [0;35] brushed with blue color - patient has problems or already dead.

  • [35;37] brushed with green color - patient is feeling well!

  • [37;43] brushed with red color - patient has fever or about to die.

The yellow marker visualizes the temperature value.


DataBar visualizer in action.
The rest of the article walks through the process of building the report. For convenience sake the walkthrough starts from the existing report layout that only includes the data are set.

  • Open PatientsTemperature.xml(change .xml to .rdlx) by using either DataDynamics.Reports.Designer.exe application or Visual Studio.

  • Add the table report item in the report body. Remove the 3rd column and the footer of the table. Set table's DataSetName property to "Temperature" . Set the the 1st column, details row cell value to "=[Patient]"

  • Add the Rectangle Report Item in the table's 2nd column, details row cell. Set the rectangle's background color to Green and the name to "rectOuter". This rectangle will show the 2nd range of the thermometer.

  • Add the nested rectangle in rectOuter. Set the rectangle's name to "rectNested", the location to (0in,0in), the size to the same value as rectOuter has. Now the magic begins.

  • Set the properties of the rectNested BackgroundImage: Source:DataBase, Value:=DataBar(35,0,43,0,'Blue'), MIMEType:image/png, BackgroundRepeat:NoRepeat. What happens here? It's not allowed to set the rectangle's background image by using the data visulalizer editor, like for a textbox, but in fact the rectangle supports data visualizers! What do the DataBar arguments mean? 35 is the boundary of the 1st range, 0 is the minimum temperature value, 43 is maximum temperature value, 0 is the zero coordinate, Blue is the color for the 1st range according to the requirements. The documentation describes the arguments meaning in details. Now preview the report. Can you see how the magic works? The rectNested's background image takes the space according to DataBar Value argument. The rest of rectNested is transparent which makes rectOuter's background partially visible. Now our thermomemeter shows 2 ranges. Fantastic!

  • Add the nested rectangle in rectNested. Set rectangle's name to "rectNestedNested", location to 0in,0in), size to the same value as rectOuter and rectNested have. As you might deduce rectNestedNested will show the 3rd Red range of our thermometer. Let's set it's background image accordingly:

  • Source:DataBase, Value:=DataBar(43,0,43,37,"Red"), MIMEType:image/png, BackgroundRepeat:NoRepeat. In DataBar function we set the zero coordinate to 37 so that the red background starts from the 2nd range right boundary. The magic described in (5) works for the 3rd range as well.

  • Now let's add the temperature value marker. It is done by using...drumroll please...rectangle that has DataBar invoke in background image expressions. Add the nested rectangle in rectNestedNested. Set the following properties for this newly added rectangle: Location.Left=0, Size.Width=the same value as the parents rectangles have. Set Location.Top and Size.Height to the appropriate values for the marker. Source:DataBase, Value:=DataBar(Fields!Temperature.Value,0,43,0,"Yellow"), MIMEType:image/png, BackgroundRepeat:NoRepeat.

  • Now select rectOuter, rectNested, rectNestedNested and set their rounding radius property to some value, i.e. 0.1in. It will make our pseudo-thermometer look closer to what was shown in the picture above. PatientsTemperature_Final.xml(change .xml to .rdlx) is the sample of the final report version and here is the output sample


is this picture informative? Yep, 3 of our 5 patients have fever and it seems that we forgot to move Mr. Jonathan Murraiin to morgue..Whoops!


Other scenarios
By using the same technique you can emulate bullet chart that uses traffic lights colors as described here.

DDR Excel Export: rendering chart report item as Excel chart object by using the Power of Templates

Preface
Excel Export in Data Dynamics Reports uses the amazing technology called “Templates” behind the scene. When a report is exported in Excel, we first build the intermediate XLS document that contains the placeholders for the actual data. This intermediate document is called “Template”.
Then we obtain the data that report normally shows and insert them in the template. The attentive reader will ask - So what? Why is this technology amazing? The answer is we expose the template to the real world: it is possible to obtain the template, modify it and pass to Excel Export. As the result the output may be tuned by creating the custom data placeholders: formulas, pivot tables and so on. In this article we will see how to solve on of the common problems with Excel export by using the Power of Templates.


The problem: chart output in Excel Export
If a report contains chart report item, then it is shown as the image in the exported XLS Document. It is not comfortable - Excel has the great charts support and it would be nice to see the DDR chart shown as Excel Chart in the output. Let’s look at hypothetical example. The application displays the attached DelayReport.rdlx(change xml to rdlx) by using the windows forms viewer and allows users to export in Excel(they just click “XLS” button and the resulting document is opened once exportation is done). The report consists of the table and the pie chart that show the data of employees delay statistic. By default the chart is transformed to the picture. The application users are not happy. They demand the Excel Pie Chart in the output. Let’s see what you can do step by step.

Templates power in action

  • Open the attached DelayReport.rdlx in the report designer - either in standalone application or visual studio integrated editor.

  • Select Report->Generate Excel Template menu item. Save the template in the appropriate location. Open the generated template in Excel.

  • The cell contains “<#Chart1#>” text is the placeholder where the chart picture is going to be inserted, so remove this text, unmerge the cell and decrease its height if it’s needed.

  • The cell contains “<#txtReason#>” text is the placeholder for “Reason” column of the table. The values from Reason column will be transformed to the range of cells. This range will start from “<#txtReason#>” cell position and grow further. Let’s name this range: select “<#txtReason#>” cell and define the new named range - in Excel 2007/2010 you can go to Formulas tab, click “Define name”, leave the default values in the popup dialog and click OK.

  • The cell contains “<#txtCasesNo#>” text is the placeholder for “CasesNo” column of the table. Name the range it will produce by using the same way as in (4).

  • Add the Pie chart on the appropriate place(Excel 2007/2010 - Insert tab, Pie chart). Right click on the chart, choose “Select Data...” and remove the default Legend Entry(Series) value.

  • Click Add on Legend Entries(Series) editor. Set Series Name to “Delays”. This will be chart title which equals to one in the original report. Then set Series Values to “=Sheet1.txtCasesNo”. This way you map the chart data to the range created on step (5).

  • Click Edit on Horizontal (Category) Axis Labels. Set labels range to “=Sheet1!txtReason”. This way you map the chart labels to the range created on step (4).

  • Note that the chart data range is set automatically. Click OK. Save the template. The attached template.xls is the sample of the described modifications result.

  • Test the exportation with the modified template. For example open the report by using DataDynamics.Reports.Preview.exe, choose Export->Excel in the toolbar. Set Template property in the popup dialog to the template path, select the output file name, click export. The attached output.xls shows the example of the output.

  • Now in your hypothetical application adjust the code of “XLS” button click handler so that the customized template is used for exportation. Use TemplateStream property.

The tip of the iceberg
What is described here is just the small piece of the great functionality that is provided by Excel Export Templates. Basically you can tune the output with any available Excel feature.

Sunday, November 7, 2010

Async+ActiveReports=AsyncActiveReporting

Introduction
Autumn is always welcomed as the season of great events in developers world. Autumn of 2010 shed light on the future of C# and VB.NET. Asynchrony support in C# 5.0 is what widely discussed nowadays. I would like to make a contribution as well. Let's look at some real-world scenario that makes use of ActiveReports and analyze how the new C# 5.0 features can help in the implementation.

The problem
Imagine that you build a WPF application. The UI includes the window with single button "Open Annual Report as PDF". If a user clicks on this button, then of course the annual report which is built by using ActiveReports is exported to PDF and the resulting PDF file is opened in the default PDF viewer that is installed on the system. Here is the XAML, I removed the attributes that don't matter for the discussion:

<Window>
<Grid>
<Button x:Name="btnExport" Click="btnExport_Click" >
Open Annual Report as PDF</Button>
</Grid>
</Window>

Here is the code that performs exportation:

private string ExportReportToPdf(string rpxPath)
{
var arReport = new ActiveReport();
var reader = new XmlTextReader(rpxPath);
arReport.LoadLayout(reader);
arReport.Run(false);
var pdfExport = new PdfExport();
var outFileName = System.IO.Path.GetTempFileName() + ".pdf";
pdfExport.Export(arReport.Document, outFileName);
return outFileName;
}

And the button click handler:

private void btnExport_Click(object sender, RoutedEventArgs e)
{
string pdfFileName = ExportReportToPdf(@"C:\AnnualReport.rpx");
Process.Start(pdfFileName);
}

This code has the obvious drawback. If the annual report is huge, then its running and export may take a long time. Let's say that the report is SO huge that it takes 1 minute to run and export to PDF(it's hard to imagine though because ActiveReports is super-fast!). Our ExportReportToPdf works in the UI thread and it does not return the control to the caller. It means that UI is not responsive while ExportReportToPdf does its job. This is not acceptable at all. The UI should be responsive. Moreover, it would be nice to allow end-user to cancel the operation and provide some kind of progress report. Indeed .NET framework has all the ammunition that is needed to solve these problems. For example BackgroundWorker usage example is very similar to what I described above. However, MS realized that using the techniques such as BackgroundWorker is painful - they make use of the background thread, they require quite convoluted code for the cancellation and progress reporting support, they require extra work for updating UI safely and so on. Let's see how we can make UI responsive, provide cancellation and progress reporting support by using the new C# 5.0 features.

All you need is Await and Async
Now let's rewrite the button click handler by using the new C# 5.0 async and await features. Async and Await is not magic. It's the syntax sugar for continuation passing style implementation. There are nice blogs that describe how async and await work in details. This post just shows how they can be used in conjunction with the real-world scenario that makes use of ActiveReports. So, here is the async version of the button's click handler:

async private void btnExportClickAsync(object sender, RoutedEventArgs e)
{
var task = TaskEx.Run(() => ExportReportToPdf(@"c:\AnnualReport.rpx"));
await task;
Process.Start(task.Result);
}

And now the UI is responsive while the annual report is run and exported! Doesn't it look much easier to implement than, for example, Asynchronous Design Pattern?

What about cancellation?
Now let's add the support for cancellation of report running. In C# 5.0 the long-running task, such as report running or exportation should take care about cancellation. The task is provided with the object that reports whether the cancellation happened. The cancellation may be forced by the timeout or by the explicit invocation of Cancel method of the special object. For example let's add Cancel button to our UI and implement the cancellation support in our long-running task. Here is the updated XAML:

<Window Title="Pending..." >
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="0" x:Name="btnExport" Click="btnExportClickAsync">
Export report to PDF</Button>
<Button Grid.Column="1" Grid.Row="0" x:Name="btnCancel" Click="btnCancel_Click"
IsEnabled="false">Cancel</Button>
</Grid>
</Window>

So, we've added the window title that will indicate the state of the task. It may be "pending", "in progress", "done", "canceled". Those are not standard statuses but rather human-friendly statuses names which I use for the sake of clarity. Also, Cancel button was added and it is disabled when the application starts. There is no anything to cancel before task is run, isn't it?
Now let's look at the updated code of the Export button click handler:

private CancellationTokenSource cancelationObject;
async private void btnExportClickAsync(object sender, RoutedEventArgs e)
{
cancelationObject = new CancellationTokenSource();
try
{
this.Title = "In progress...";
btnExport.IsEnabled = false;
btnCancel.IsEnabled = true;
var task = TaskEx.Run(() => ExportReportToPdf(@"C:\AnnualReport.rpx"));
await task;
this.Title = "Done";
Process.Start(task.Result);
}
catch (OperationCanceledException)
{
this.Title = "Canceled";
}
finally
{
cancelationObject = null;
btnExport.IsEnabled = true;
btnCancel.IsEnabled = false;
}
}

So, we initialize and pass the "cancellation object" in our long-running task(through the class field) and catch the OperationCanceledException. If it is thrown, then the task was canceled. But who performs the actual cancellation? As I mentioned, In C# 5.0 the long-running task, such as report running or exportation should take care about cancellation. The idea: let's intercept the PageStart event of ActiveReport object and cancel the report running if it is needed. We need to update the code of ExportReportToPdf method:

private string ExportReportToPdf(string rpxPath)
{
var arReport = new ActiveReport();
var reader = new XmlTextReader(rpxPath);
arReport.LoadLayout(reader);
arReport.PageStart += (s, a) => cancelationObject.Token.ThrowIfCancellationRequested();
// the rest of the code
}

The last think to get cancellation working. Remember about Cancel button? Let's make it do its job!:

private void btnCancel_Click(object sender, RoutedEventArgs e)
{
cancelationObject.Cancel();
}

So, the entire picture of cancellation support is: The UI is responsive while the report running and exportation does its job. The user may click Cancel button and it tells "cancel the task!" to the special object. The report running intercepts the new page start event and tells "throw the cancellation exception if you told to do that!" to the special object. The task running catches the "cancellation exception" and updates the UI accordingly. The only drawback here is we can't cancel the task when the exportation starts(PdfExport.Export method is called). We only can cancel the task while the report is run. Unfortunately Export method does not raise any events that we can intercept. Also, the report running can be canceled only when the new page starts processing. Note though that the API I use lives since from ActiveReports 1.0 version(which was born in past century) but we talk about C# 5.0.

Progress reporting
Let's add the last gorgeous feature in our application. We will keep the user informed about the status of the task. The idea is very similar to the cancellation mechanism: we have the special Progress object, we report to that object in the handler of PageStart event of the report, we intercept the event of that object, we get the progress report in the handler, we update the UI with the progress report. All those actions happen in UI thread, we don't need to care about the safe UI updates. Let's look at the code(I removed the parts that do not matter for progress reporting:

private EventProgress<int> progress = new EventProgress<int>();

private string ExportReportToPdf(string rpxPath)
{
var arReport = new ActiveReport();
var reader = new XmlTextReader(rpxPath);
arReport.LoadLayout(reader);
int pageCount = 0;
arReport.PageStart += (s, a) =>
{
cancelationObject.Token.ThrowIfCancellationRequested();
(progress as IProgress<int>).Report(pageCount++);
};
// the rest of the code
}

async private void btnExportClickAsync(object sender, RoutedEventArgs e)
{
cancelationObject = new CancellationTokenSource();
try
{
this.Title = "In process...";
btnExport.IsEnabled = false;
btnCancel.IsEnabled = true;
var task = TaskEx.Run(() => ExportReportToPdf(@"C:\AnnualReport.rpx"));
progress.ProgressChanged += (s, args) =>
{
this.Title = "Pages done: " + args.Value.ToString();
};
await task;
// the rest of the code
}

VoilĂ ! We've made the UI responsive, added cancellation and progress reporting support for our long-running report task by using the new C# 5.0 features. And they look very comfortable to use so far. The code is clean and very readable(at least I believe in that :-))

Next time
Now let's imagine that we want to merge two huge reports and export the resulted document in PDF. How we can start two reports at the same time and have the UI responsive and await until both of them are completed? C# 5.0 may help too! Look for the Next episode of AsyncActiveReporting in 1 week :)

Sunday, June 6, 2010

Data Dynamics Reports And Twitter

Preface
Tips and tricks series continues! In this post I am going to explain how a Data Dynamics Reports report might be set up in order to show the tweets from arbitrary user's public timeline. The live demo is available here. I can imagine the real-world usage of this feature: say I am HR department manager and in the CV overview report I would like to see the last 3 tweets from the public timeline of the candidates for opened job position. Let's see how a developer can implement such a feature using Data Dynamics Reports.

Using XML Data Provider
Data Dynamics Reports includes XML data provider that can load the data from various sources, particularly it supports xml documents that are located in a web-accessible location. In other hand a user's public timeline can be accessed via RSS feed which is xml document that is located in a web-accessible location. I.e. my public timeline can be read on http://twitter.com/statuses/user_timeline/sAbakumoff.rss. RSS feed document has well known structure: each tweet is presented by <item> tag, its child <description> tag has the tweet text. So...displaying user's public timeline in Data Dynamics Report is pretty simple:
1) Add new Data Source in a report. Set Data Source type to XML.
2) Set the connection string of the newly added data source to "xmldoc=http://twitter.com/statuses/user_timeline/USER NAME.rss"(without quotes). Change USER_NAME to the real-world value.
3) Add the new Data Set in the Data Source.
4) Set the Query of the newly added Data Set to "/rss/channel/item"(without quotes).
5) Add the Fields to the newly added data set. Set the name of the field to "title", set the value of the field to "title".
6) Add the List report item in the report, set the DataSetName to the name of the data set that is added in step 3.
7) Drag the field "title" from the data explorer to the newly added List Report Item.
8) Preview the report, hey presto, it shows the user's public timeline!


Advanced techniques
I've used a couple of other features in the live demo:
1) To limit the tweets list for 5 last messages, I used TopN filter of the data set. I've added one more field to the data set, it's pubDate. The filter expression is "=Fields!pubDate.Value", the filter operator is TopN, the filter value is 5.
2) I parametrized data source query to allow set user name using the report parameter value, the connection string is set to
="xmldoc=http://twitter.com/statuses/user_timeline/" & Parameters!TwitterUser.Value & ".rss"
The complete report is available here.

Monday, May 24, 2010

Data Dynamics Reports: Customizing web-viewer toolbar

Preface
In this post I would like to share the tips and tricks for Data Dynamics Reports Web-based report viewer(hereafter referred to as "Web-Viewer") customization. Web-Viewer component does provide a developer with customization capabilities, but the are a couple of the problems with them - they are tricky to implement and not documented. I hope that this post partly solves those problems.

Web-Viewer overview
Web-Viewer is the ASP.NET server control that renders the HTML content in a client side. The HTML content consists of several logical parts - the toolbar, the side panel, the page viewer and the controller. The controller is the javascript object has the methods and properties that allow to load the report to display, navigate through the pages of a report, print a report, export a report to one of the supported formats, etc. All those actions are performed via AJAX requests/responses and updating the DOM of the HTML content at run-time.

Toolbar customization
This post is intended to demonstrate how a developer can make-use of home-grown toolbar for Web-Viewer. The implementation can be split to the following steps:
  1. Hiding the built-in toolbar
  2. Get access to the methods and properties of controller object
  3. Determine when a report page is loaded to update the custom toolbar
I will go through all those steps. However I need to clarify the code ammunition that will be used for the live demo. It is available here, the complete source code can be obtained here. I tested the live demo on IE8, FireFox 3.6.3 and the last version of Google Chrome. The demo works best on Chrome :)


JQuery and LiveQuery
JQuery is essential choice for a web-developer. I used JQuery selectors, element manipulations, the UI button element to build the live demo. That is expected. However, there is the problem that can't be solved by using the default JQuery library. Web-Viewer component can be added into an aspx page by writing something like <dd:WebReportViewer ID="WebReportViewer1" ReportName="~/Report.rdlx" /> Once an aspx page is requested, web-viewer component starts rendering of the HTML content that has the toolbar, side panel, etc. None of those element exists at design time when a developer builds an aspx page. Therefore the code needs to access to the elements that will be added at-runtime by updating the DOM of the HTML document! Fortunately there is nice Live Query plugin that "utilizes the power of jQuery selectors by binding events or firing callbacks for matched elements auto-magically, even after the page has been loaded and the DOM updated". This is exactly what we need. To operate on non-existing element a developer writes something like $('#ID').livequery(function(){/*do something*/}); and the code in the brackets executes once the element with id=ID appears on the DOM.

Hiding the built-in toolbar
The built-in Web-Viewer toolbar is the HTML element that has ID="reportViewerToolbar{Web-Viewer componentID}$coreViewer". For example if the ID of Web-Viewer component on the aspx page is "WebReportViewer1" then the ID of the toolbar will be "reportViewerToolbarWebReportViewer1$coreViewer". To hide the built-in toolbar, we can use the following code:

$('#reportViewerToolbarWebReportViewer1\\$coreViewer').livequery(function () {
  $(this).css('display', 'none');
});
So, when Web-Viewer component will render the built-in toolbar, this magic code will update its style with "display:none", and the toolbar will disappear from the page.


Get access to the methods and properties of controller object
Web-viewer component renders the script code that creates the instance of the controller class. Once this code is executed, the instance is available for the rest HTML page code. The name of the instance is "controller{Web-Viewer componentID}$coreViewer". For example if the ID of Web-Viewer component on the aspx page is "WebReportViewer1" then the name of the controller instance will be "controllerWebReportViewer1$coreViewer". Here is the table of the methods and properties of the controller class. A developer can make use of them on the script code:

MethodAction
nextPage()goes to the next page of a report
previousPage()goes to the previous page of a report
firstPage()goes to the first page of a report
lastPage()goes to the last page of a report
toggleSideBar()Shows or hides the side panel
print()Prints the report
openPageSetupDialog()Opens the dialog allows to set up the page properties
toggleRenderMode()Switched the preview mode to Galley or Print Preview
backToParent()Returns to the parent of the drillthrough report
openEmailDialog()Displays the dialog that allows to send a report via email
showDocMapSideBar()Opens the TOC tab of the side panel.
showFindSideBar()Opens the Search tab of the side panel.
showParameterSideBar()Opens the Parameters tab of the side panel.

For example, to create the Next Page button of my custom toolbar I can use the following code:

$('#btnNextJ').button().click(
function () { controllerWebReportViewer1$coreViewer.nextPage(); }
);
Also, controller instance has report property. The type of this property is the class that represents the currently displayed report. The report object also exposes useful methods and properties:
Method,PropertyAction
export(format)Exports report to the certain format. Possible parameter values are: PDF, Word, Xml, Mhtml, Excel
pageCountgets the number of pages in a report
pageNumbergets the number of the currently displayed page


For example, to create the Export to PDF button of my custom toolbar I can use the following code:

$('#btnExportPDF').button().click(
function () { controllerWebReportViewer1$coreViewer.report.exportReport("PDF"); }
);
See the complete sample of code on the live demo source page.


Determine when a report page is loaded to update the custom toolbar
This is the most tricky part of the implementation. The problem is the controller instance and its report property value are not initialized until the AJAX request for a report is completed. There is no well-known way to catch this moment. We need to use a work-around this issue. In HTML mode of Web-Viewer the report pages are displayed inside the frame element has ID="CorePanel{Web-Viewer componentID}$coreViewer". For example if the ID of Web-Viewer component on the aspx page is "WebReportViewer1" then the ID of the frame will be "CorePanelWebReportViewer1$coreViewer". JQuery allows to catch the moment when the frame is loaded using load function. In the live demo I use this trick to update the toolbar - to enable the buttons that are disabled by default, to set page number and page count text:
$('iframe[id="CorePanelWebReportViewer1\\$coreViewer"]').livequery(function () {
$(this).load(function () {
updateToolbar();
});
});
See the code of updateToolbar function on the live demo source page.


Behind the discussed topics.
All the tips and tricks I talked about this post work for HTML mode of Web-Viewer. I did not test them for PDF mode, however I am confident that all but the catch of report loaded event works in PDF. I did not try to customize the search, toc and parameters tabs of the side panel, but it might be done using the same techniques as for toolbar customizing.