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.