One of the problems I came across the other day is I had several hundred items in a list to process.  To make life easy I use javascript on a web part page to make an app to process the items I wanted to do. 

However when its hundreds, do them all at once and you’re in trouble.  You block the UI thread, you cant update any status values, I dare not think what would happen if you called a couple of hundred async ajax queries all at once, it might just handle them – you can try it if you want😉

To get round this I thought I’d try to use jQuery queues.  So in this example I have a custom list which I get data from, I’m using promises to get the data but there was no need, I just used some code from the last example.  Once I have the data I inject it onto the screen an lose the original data array (now this is the point where I should use knockout and let the screen adjust to the observed data, but this is a demo).  Then on a click event, a big process this now button, I iterate thru each item on the page, gets its details and add a function to the queue that calls another function (just for being tidy I could embed) which makes a call to SPServices async update, phew.

Then I tell the queue to start executing the functions in it, one at a time.  At the end of each async success, it calls the next function, you might spot the mistake here, what if it doesn’t succeed (well I forgot to code it, but you need to do something with the item and the queue when you do it).

Hint: When calling functions and sub functions, make sure you “var” every variable, I forgot and it only updated the last item.

Technorati Tags: ,,

Here’s the code

 

 <script src="js/jquery.SPServices-0.7.1a.min.js"></script>  <script src="js/underscore.min.js"></script>   <div id="mainApp" > 	<div id="appStatus">Initialising</div>	<div id="appButton" style="cursor:hand;display:none">Click Me</div> 	<div id="appBody"></div>  </div>        <script>


var binaryJamQueueTest = function ($) {
   
   	//Private Members
  	var testListArray;

	function main() {
		
		$("#appStatus").text("Loading");

		//Remember that $.when is an async function also, so dont put anything after this code
		//unless you want it running immediately, in which case stick it here first.
		$.when(asyncGetTestList())
		.fail(function() {
			loadFailure();
		})
		.done(function(){
			dataReady();
		});
		
  	}
  	
  	function loadFailure() {
  		//Oh dear
	  	$("#appStatus").text("Failed to Load data");
  	}
  	
  	function dataReady() {
  		$("#appStatus").text("Processing Data");
  		
  		//Earlier versions did not queue so in cases where you will thread block
  		//use something like this to give the DOM time to update
  		setTimeout(function() {
	  		processList();
	  	}, 10);
  		
  	}
  	
  	
  	function asyncGetTestList() {
  	
  		var dfd = $.Deferred();
  		
		$().SPServices({          
			operation: "GetListItems",          
			async: true,          
			listName: "TestList", 
			CAMLViewFields: "<ViewFields><FieldRef Name='Title' /><FieldRef Name='name' /><FieldRef Name='valueField' /></ViewFields>",

			completefunc: function(data, status) { 
				if (status== "success") {
					testListArray=$(data.responseXML).SPFilterNode("z:row").map(function() 
	                { 
							return {
								id: $(this).attr('ows_ID'), 
		                        title: $(this).attr('ows_Title') , 
		                        name: $(this).attr('ows_name') ,
		                        value: parseInt($(this).attr('ows_valueField')) 
		                    }; 
	                 }); 
					dfd.resolve("");				
				}
				else
				{
					dfd.reject();
				}
			} 
		}); 
		
		return dfd.promise();
  	}
  	
  	
  	function updateListItem(id, title, value, $item, next)
  	{
		//Notice here, if I was databinding I would only have to update the value not the html item
	  	$().SPServices({
			operation: "UpdateListItems",
			async: true,
			ID: id,
			listName: "TestList",
			valuepairs: [ ["valueField", value]],
			completefunc: function(xData, Status) {
				$item.text(title + " " + value);
				$item.attr('value', value);
				next();
			}
		});
	
  	}
  	
  	function processList()
  	{
		var $taskQueue=$({});
		
  		var htmlAdd="";
		$.each(testListArray, function(locIndex, testListRow) {
			
				htmlAdd+="<li id='" + testListRow.id + 
								"' title='" + testListRow.title + 
								"' name='" + testListRow.name + 
								"' value='" + testListRow.value + 
						  "'>" + testListRow.title + " " +
						 testListRow.value + 
						 "</li>";
								
				s="";
			
		});
		
		//Clear the original list Im going to use stuff onscreen, I know its wrong and not MVC
		//But Hey you do it the right way with a data binding library instead
		testListArray={};
  		
  		html=$("<ul id='itemsToProcess'>" + htmlAdd+ "</ul>");
		$("#appBody").html(html);
  		$("#appStatus").text("Ready");
  		$("#appButton").show();



  		$("#appButton").click(function() {
  		
			$.each($("#itemsToProcess").children(), function() {
				var $currentLi=$(this);
				$taskQueue.queue('tasks', function(next) {
	  				var id =$currentLi.attr('id');
	  				var title =$currentLi.attr('title');
	  				var value=parseInt($currentLi.attr('value'));
	  				updateListItem(id, title, value+1, $currentLi, next );
	  				
	  				
				});
			});

			$taskQueue.dequeue('tasks');
  		});
  				
  	}


	//Public Members
  	return {
   		init : main
  	}

  
}(jQuery); //binaryJamQueueTest Module


jQuery(function () {

    binaryJamQueueTest.init();
});


</script>