Saving a PerformancePoint Analytic Report from a Web Part Menu

Having worked with PerformancePoint for a few months, I have to say it has it’s quirks. Coming as I do from a web development background, it’s been very frustrating to find myself in situation after situation where my ability to affect how it works is limited. The Show Details option, for example, breaks ugly if you attempt to use it on a calculated measure in an analytic chart or grid and I’ve been unable to find a built-in way to get rid of it entirely.

I thought I was in the same boat when our users wanted a way to save the current state of an analytic report or grid in its current navigated state, but it turns out that there is a solution. The Microsoft Office Developer Documentation Team has released a bit of code, PerformancePoint Services 2010: Save Analytic Report Sample, that serves as an excellent starting point. Indeed, I suspect that for many clients it’s probably good enough out of the box.

For our purposes however, the sample implementation was not entirely appropriate for several reasons.

  • First, if the user clicked the Page tab on the ribbon before any of the reports had been rendered on the page, the save button would be disabled.
  • If you want to display a dashboard to users with limited privileges in your site, the ribbon is not displayed.
  • The ribbon is also unavailable on analytic reports that have been opened in a new window.

For these reasons, we decided to add a “Save Report” item to the web part menu where the user would already find the commonly-used options such as “Export to Excel” or “Open in a New Window.”

Save Report option on Web Part Menu

The standard way to customize a web part menu in SharePoint by overriding the WebPart Verb property is designed for the web part creator. I wasn’t able to find a documented API that allowed a user to configure additional menu items without inheriting from the original web part. Fortunately, it’s straightforward to use the DOM to add additional items to the web part menu as seen in the code below.

function addMenuItemForAnalyticReports(webPartRecord) {
    var reportType = webPartRecord.ViewState["D90A8712A3FA4649A25B7AB942FBCF20"];
    if (reportType) {
        if (reportType == "OLAPGrid" || reportType == "AnalyticChart") {
            var clientId = webPartRecord.ClientId;
            var menu = document.getElementById("WebPart" + clientId + "_Menu");
            if (menu) {
                var newItemHtml = '';
                menu.innerHTML = menu.innerHTML + newItemHtml;
            }
        }
    }
}

The problem with this approach is that until a PerformancePoint web part is downloaded and rendered, the webPartRecord.ViewState is unavailable, so it’s difficult to find a spot to call this code. Like the sample code does, I load my Javascript library on all pages in the site using a CustomAction element in Elements.xml with Location =”ScriptLink” and a ScriptSrc set to the path of my script contained in a layouts-mapped folder. This forces the script to get included on every page in the site which is beneficial since we need the script on the stock PerformancePoint DynamicReportView.aspx application page. Of course this also injects this script into places we might not want like the PerformancePoint Designer. Because it’s included everywhere, we need to be careful to make sure we run our initialization function only when the PerformancePoint and SharePoint libraries have been loaded, so I use the following code as the trigger for initialization.

if (typeof ExecuteOrDelayUntilScriptLoaded != "undefined") {
    ExecuteOrDelayUntilScriptLoaded(
        function () { Sys.Application.add_init(initSaveReportMenu) }, 
        "sp.js");
}

On SharePoint pages, the ExecuteOrDelayUntilScriptLoaded() method is available very early on any SharePoint page, and we’re asking for our code to be delayed first until the sp.js code has been loaded and again until the Sys.Application.init event has occurred which is late enough for all the required scripts to have been loaded. We also handle the situation where ExecuteOrDelayUntilScriptLoaded is completely unavailable which is the case when working with the PerformancePoint Designer.

When all the libraries are available, the initSaveReportMenu() function is run.

function initSaveReportMenu() {
    if (typeof PPSMA != "undefined") {
        var connectionMgr = PPSMA.ClientConnectionManager.get_instance();
        if (connectionMgr) {
            var cmRec = connectionMgr.get_connectionManagerRecord();
            if (cmRec) {
                var webPartRecords = cmRec.WebPartRecords;
                if (webPartRecords) {
                    for (var i = 0; i < webPartRecords.length; i++) {
                        var wpr = webPartRecords[i];
                        if (wpr.AssemblyQualifiedName.indexOf(
                              "ReportViewWebPart") != -1) {
                            //Need a better implementation. 
                            //Right now we are creating a timeout 
                            //function for each web part
                            //and waiting on various PP web part 
                            //properties to change. 
                        }
                    }
                }
            }
        }
    }
}

On pages where the PerformancePoint API is available, this function initializes a timeout function for each ReportViewWebPart. At the moment, this is particularly brittle code and I'm hoping to come up with something better so I don't want to document it in this post. My current implementation waits first until the web part DOM element no longer contains a child DIV with class "pps-loadingDiv." and then checks ClientWebPart object for the presence of a value in it's _lastRendered property. When I deployed from my dev box to the server, I found that this property was no longer used in the Cumulative Update that had been applied on the server. Instead, the name of that property had been minified to "$2_9," so I'm also checking for that property which is definitely not something I want to rely on going forward.

Finally, once I've made determined that the web part is rendered in the browser I call the addMenuItemForAnalyticReports() function to add my “Save Report” menu item. The implementation of SaveAnalyticReport() is almost identical to the Microsoft sample except that I'm only passing a single web part's information to the application page rather than a list of all analytic web parts on the page.

Obviously, I still have some tweaking to do on this, but I wanted call attention to the very useful sample the Microsoft Office Developer Documentation Team had posted, document what I had come up with so far, and perhaps see if there’s anyone else who might have a more elegant and robust approach to determining whether a report view web part is loaded.

Here’s my very raw adaption of the Microsoft sample code. It’s a .txt file saved with a .doc extension to conform to WordPress restrictions:

saveReport.js

Note: How the MSDN sample works

Save Report ButtonThe sample code is triggered from a button that is added to the SharePoint ribbon. When the Page tab on the ribbon is displayed, a Javascript function uses the client-side PerformancePoint API to determine whether there are any PerformancePoint analytic charts or grid web parts on the page and enables the button if so. When the button is clicked, it will again query the PerformancePoint API to get a list of analytic web parts, find the name and lastReportId property for each one and then pass this information via query string to an application page that is loaded through using the SP.UI.ModalDialog.showModalDialog() client library function.

Sample Code Save DialogThe server-side code will then let the user select a report, supply a name, description and target list and then click Save. The current state of the report is retrieved based on the lastReportId value and is then saved to the selected PerformancePoint content list.

There is little documentation for lastReportId. At the time of this posting, Google returned exactly two results in a search for “performancepoint lastReportId“: the sample report code and a Microsoft PDF dated 2009 and marked “Preliminary.” It seems however that this points to a cached temporary report.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: