The post Running jQuery scripts on PerformancePoint data appeared first on Dynamics 101.
I recently had a close encounter with a SharePoint PerformancePoint dashboard page that displayed its data in grids. My task was to calculate row and column totals and display the results in an additional column and row that would be appended to the right and bottom of the grid respectively. I had done something similar before using jQuery/JavaScript, so without much further ado I proceeded to make a copy from source of the data grid’s generated HTML and write a script that would quickly and neatly do exactly what was required. When I incorporated the script into the dashboard page itself, however, it would not run. A quick test showed that the script worked fine in the console, but was having trouble with page load. This pointed to the probability that the PerformancePoint data was not ready when document ready came around, and that therefore jQuery had no data to work with when it tried to run. I needed an event that would fire when PerformancePoint finished loading, and found it here:
function NotifyBrowserOfAsyncUpdate (elem) { //execute jquery in here }
(Source: http://stackoverflow.com/questions/3372565/performancepoint-sharepoint-2010-and-jquery)
After wrapping the above function around my jQuery, presto magico, it ran like a champ.
On the test page that is. The real production page had three such grids, and thus I found that the script was running three times, and adding a row and a column each time with compounded totals! I would post a screenshot, but I’m certain it would beget a tempest in those gentle souls with finer sensibilities.
And this is where things started to get really interesting. NotifyBrowserOfAsyncUpdate is an obscure function that had to be fished out of the .NET assemblies and for which there is precious little documentation. A colleague of mine and I grappled with this problem for a couple of hours and to make a long story short, came up with a solution that is actually a bit of a hack and which involves a sleeper function that checks for the presence of an HTML element at a given interval:
<script type="text/javascript"> var calculatetotalrunonce = -1; function NotifyBrowserOfAsyncUpdate (elem) { // HACK: There is no really good JavaScript async callback to handle what we want to do here. Since the multiple web parts make multiple calls, we need to check to make sure that we only run our jQuery script ONCE (otherwise it will add the grand total columns/rows 3 times) //The second HACK is because PerformancePoint data is loaded from SharePoint asynchronously, and since there is no good JavaScript event to handle, we have to loop (see setInterval below) and check to see if the data is ready before moving on. if(window.calculatetotalrunonce == -1) { window.calculatetotalrunonce = 1; //alert($('selector'').length > 0) //uncomment to debug // this saves this whole call back function var callbackWhenReady = function() { //the sleeper function checks to see if the HTML element we've selected from the loaded PerformancePoint grid is ready; if not, it does nothing (an empty return) and gets it called again in the next interval if ($('selector').length < 1) { return; } //When the PerformancePoint data is ready, $('selector').length < 1 is no longer true, so we cancel the interval and the callback doesn't get called again window.clearInterval(intervalID); //the jQuery code can now be executed on the loaded PerformancePoint data $(docuement).ready() { } //end jQuery }; // end callbackWhenReady function // Set the interval that will keep calling the callback function every x milliseconds to see if the PerformancePoint data is ready, and either execute or wait for the next interval. var intervalID = window.setInterval(callbackWhenReady, 500); } //if(calculatetotalrunonce == 0) }// end NotifyBrowserOfAsyncUpdate function </script>
The interval can of course be adjusted to suit preferences and performance. Perhaps the trickiest part of this effort was figuring out that the calculatetotalrunonce variable had to be attached to the JavaScript window object in order to make it work as a global variable. For a while there, it was touch and go, but all’s well that ends well.