Sue Hernandez's SharePoint Blog

SharePoint and Related Stuff

Long Running Client Side Scripting (i.e. Javascript)

In my Google Mapping Dashboard/Scorecard solution, I was calling out IN SCRIPT to several data sources, including SharePoint Lists (via web services) and xml files (provided by the CorasWorks Data Integration Toolset).  I originally had very long-running processes which would not only hang the browser, but it also just plain made it crash.

Using the reference below, I got the general idea of what they were trying to do, and I modified it to my own liking.  In this article, I will explain how to use a “timed loop” to control browser UI responsiveness.

References:

First off, we will call our web service to get our return data.  I’m using the JQuery library in this example, but it is not necessary to use JQuery to affect the same results.

$.ajax({
	url: YOUR_URL_HERE,
	dataType: "xml",
	complete: function(xData, status) {
		var xDoc = xData.responseXML;
		var nodes = xDoc.selectNodes("//Table1");

		var items = new Array();
		for (var i = 0; i < nodes.length; i++) {
			items.push(nodes[i]);
		}
		totalNumNodes = items.length;
		loopChunks(items, totalNumNodes);
	},
	contentType: "text/xml; charset=\"utf-8\""
});

Notice how we’re calling a function here after we receive our response from the AJAX call, a function called loopChunks.  We pass in an item array – as far as I can tell, it MUST be an actual Array object, thus looping through the items first and pushing them into an array.  We also pass in the total number of nodes, so we can do some percentage calculation (in case we want to update the browser with that percentage information).

Next, we call our loopChunks method:

function loopChunks(items, totalNumNodes) {
	if (items.length > 0) {
		var index = (totalNumNodes - items.length);
		var node = items[0];
		items.shift();  // This removes the first item in the array

		var pct = Math.round((index / totalNumNodes) * 100);

		updateInfoPanel(pct);

		if (pct % 10 == 0) {
			setTimeout(function() {
				processNode(node, items, totalNumNodes);
			}, 25);
		}
		else {
			processNode(node, items, totalNumNodes);
		}
	}
	else {
		items = null;
		try {
			CollectGarbage();
		}
		catch (ex) { }
		window.setTimeout(function() { completionFunction(); }, 25);
	}
}

First we’ll see in this function that we check to see if we have any items left to process.  If we do, we first find out which item we’re on (for our percentage done) and then specify the current node as the first node in the array.  We then use the shift() method of the Array object to remove the first item in the array.  Here, the documentation tells us that we could have just used the return value of the shift() function to get the first node, but for me this always returned a GUID and not the actual node (even when just looping through strings). 

Next we calculate the percentage done we are.  After that, you’ll notice that I do a check to see if our current percentage is a factor of 10.  **IMPORTANT**  The reason I did this was because when you loop through memory items, and you update the percentage on the UI EVERY TIME, you end up actually having TOO much overhead just in the setTimeout function that follows.  I found that if I’m doing lots of calculations with the node, I’ll update the UI every 10%.  If I’m doing faster operations, I’ll update the UI every 20% or 25%.

Next in our function, we’re either setting a timeout or just calling directly the processNode function.  The processNode function is what actually does all of our work.

function processNode(node, items, totalNumNodes) {
	var locationKey = "";
	if (node.selectSingleNode("Location_x0020_Key") != null) {
		locationKey = node.selectSingleNode("Location_x0020_Key").text;
	}
	var lat = "";
	if (node.selectSingleNode("Latitude") != null) {
		lat = node.selectSingleNode("Latitude").text;
	}
	var lng = "";
	if (node.selectSingleNode("Longitude") != null) {
		lng = node.selectSingleNode("Longitude").text;
	}

	var latLon = new google.maps.LatLng(lat, lng);
	var title = locationKey;

	// Set the Marker
	var marker = new google.maps.Marker({
		position: latLon,
		title: title
	});

	fluster.addMarker(marker);  // SourceForge - Fluster2Cluster

	loopChunks(items, totalNumNodes);
}

Here we are simply getting the Name of the Google Marker, the Latitude and Longitude of the Marker, and setting it into our global variable fluster which is an implementation of the Clustering script library Fluster2Cluster from SourceForge.

Notice at the end of the function, we call the original looping function, loopChunks, with the parameters we passed down into the function.

Here’s the rest of the code, which was referenced in the other functions: the updateInfoPanel method and the completionFunction method.

function updateInfoPanel(pct, stage) {
	try {
		var html = '<a class="whiteLink" onclick="closeInfoPanel();" href="#"><strong>&gt;&gt;</strong></a>

		// this is a progress bar of sorts
		html += "<div style='width:104px;height:25px;border:2px navy solid'><div style='width:" + pct + "px;height:21px;background-color:green'>&nbsp;</div></div>";

		$("#map_infoPanel").html(html);
	}
	catch (ex) {
		alert(ex.message);
	}
}

function completionFunction() {
	fluster.initialize();
}

That’s it!

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: