Link to home
Create AccountLog in
Avatar of MariettaCellars
MariettaCellarsFlag for United States of America

asked on

Inconsistent Response from $.getJSON()

I have written code to retrieve one or more calendar feeds from Google. It works great with 1 calendar. But when I add a second, the data that JavaScript is processing is inconsistent. I suspect it has something to do with $.getJSON()'s synchronous nature. But in spite of my best efforts to understand it, I simply cannot figure it out.

The attached script is complete.  In order to see the error in action, you can refresh the browser window that's running it and click "Show Calendars" which then displays the contents of what was retrieved in a <PRE> block using a $.dump() plugin to jQuery.

What you will notice is that with each refresh (ie the F5 key) and click of "Show Calendars", the title of the calendar retrieved will randomly flop between one of the two provided public calendars. But under no circumstances does the functionality return BOTH calendars as I have intended to do.

What's going on here?
<html>
	<head>
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript"></script>
		<script src="http://plugins.jquery.com/files/jquery.dump.js.txt" type="text/javascript"></script>
		<script>
			//Global application
			var jGCal = {};
				jGCal.googleFeedAPI_URL = "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=?&q=";
				
				
				//Here is our list of calendars to retrieve along with some custom colors
				jGCal.calendars = [
					['https://www.google.com/calendar/feeds/8tvofvvb61r3tclqev39aev4kg%40group.calendar.google.com/public', '#00f'],
					['https://www.google.com/calendar/feeds/9s45c8go4950iei5cjuqes91c4%40group.calendar.google.com/public', '#f00']
				];
				
				//Make some basic projection and parameter choices
				jGCal.projection = 'basic'; //the projection type of the feed to retrieve
				jGCal.parameters = '?futureevents=true&sortorder=starttime'; //additional parameters to pass to the feed API
				jGCal.maxResults = 3; //max results to retrieve per calendar

				//Fetch the feeds for each of the calendars in our jGCal.calendars array
				jGCal.fetchCalendars = function(){
					var cals = jGCal.calendars; //shorthand our syntax
					for(i=0, j=cals.length; i<j; i++){
						//construct final URL based on projection and parameter choices
						var url = jGCal.googleFeedAPI_URL+cals[i][0]+'/'+jGCal.projection+jGCal.parameters+"&num="+jGCal.maxResults;
						var lastResponse = null;
						//$.getJSON is our way around cross-site script defences
						$.getJSON(url,
							function(data){
								for(i=0; i<cals.length; i++){
//AAAARRRRRGH!!!!
									jGCal.calendars[i][2] = data.responseData.feed.title; //Attach response entries to associated calendar item
								}
							}
						);
				
					}
				};
			
			//EXECUTE
			$(document).ready(function(){
				jGCal.fetchCalendars();
				$('#blip').click(function(){
					$('#container').html($.dump(jGCal.calendars));
				});
			});
		</script>
		<title></title>
	</head>
	<body>

		<a href='#' id="blip">Show Calendars</a>
		<pre id="container"></pre>

	</body>
</html>

Open in new window

Avatar of iGottZ
iGottZ
Flag of Germany image

i get this as response:
Array ( 
	0 => Array ( 
		0 => "https://www.google.com/calendar/feeds/8tvofvvb61r3tclqev39aev4kg%40group.calendar.google.com/public"
		1 => "#00f"
		2 => "Other Bikely Events"
	)
	1 => Array ( 
		0 => "https://www.google.com/calendar/feeds/9s45c8go4950iei5cjuqes91c4%40group.calendar.google.com/public"
		1 => "#f00"
		2 => "Other Bikely Events"
	)
)

Open in new window


isnt that what you want?
try this:
<html>
        <head>
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript"></script>
                <script src="jquery.dump.js" type="text/javascript"></script>
                <script>
                        //Global application
                        var jGCal = {};
                                jGCal.googleFeedAPI_URL = "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=?&q=";
                                
                                
                                //Here is our list of calendars to retrieve along with some custom colors
                                jGCal.calendars = [
                                        ['https://www.google.com/calendar/feeds/8tvofvvb61r3tclqev39aev4kg%40group.calendar.google.com/public', '#00f'],
                                        ['https://www.google.com/calendar/feeds/9s45c8go4950iei5cjuqes91c4%40group.calendar.google.com/public', '#f00']
                                ];
                                
                                //Make some basic projection and parameter choices
                                jGCal.projection = 'basic'; //the projection type of the feed to retrieve
                                jGCal.parameters = '?futureevents=true&sortorder=starttime'; //additional parameters to pass to the feed API
                                jGCal.maxResults = 3; //max results to retrieve per calendar

                                //Fetch the feeds for each of the calendars in our jGCal.calendars array
                                jGCal.fetchCalendars = function(){
                                        var cals = jGCal.calendars; //shorthand our syntax
                                        for(var i=0, j=cals.length; i<j; i++){
                                                //construct final URL based on projection and parameter choices
                                                var url = jGCal.googleFeedAPI_URL+cals[i][0]+'/'+jGCal.projection+jGCal.parameters+"&num="+jGCal.maxResults;
                                                var lastResponse = null;
                                                //$.getJSON is our way around cross-site script defences
                                                for(var i=0; i<cals.length; i++){
                                                        $.getJSON(url,
                                                                function(i) {
                                                                        return function(data){
                                                                                jGCal.calendars[i][2] = data.responseData.feed.title;
                                                                        }
                                                                }(i)
                                                        );
                                                }
                                
                                        }
                                };
                        
                        //EXECUTE
                        $(document).ready(function(){
                                jGCal.fetchCalendars();
                                $('#blip').click(function(){
                                        $('#container').html($.dump(jGCal.calendars));
                                });
                        });
                </script>
                <title></title>
        </head>
        <body>

                <a href='#' id="blip">Show Calendars</a>
                <pre id="container"></pre>

        </body>
</html>

Open in new window

sorry. in the code above you also need to replace jquery.dump.js with http://plugins.jquery.com/files/jquery.dump.js.txt
ASKER CERTIFIED SOLUTION
Avatar of iGottZ
iGottZ
Flag of Germany image

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Avatar of MariettaCellars

ASKER

iGottZ

I'm still stuck in the same boat with your proposed changes.  What I SHOULD be getting is the following response.  notice that it returns BOTH calendars, "Bike Monkey Public" and "Other Bikely Events".
Array ( 
	0 => Array ( 
		0 => "https://www.google.com/calendar/feeds/8tvofvvb61r3tclqev39aev4kg%40group.calendar.google.com/public"
		1 => "#00f"
		2 => "Bike Monkey Public"
	)
	1 => Array ( 
		0 => "https://www.google.com/calendar/feeds/9s45c8go4950iei5cjuqes91c4%40group.calendar.google.com/public"
		1 => "#f00"
		2 => "Other Bikely Events"
	)
)

Open in new window

doesnt my last code does that?
AHA!!!

Now, could you explain to me what's going on here, and what I seem to be missing, conceptually that you seem to be able to grasp?

This worked perfectly. Thank you.
What I don't seem to understand is why $.getJSON() doesn't have access to the variable *i* without explicitly passing it in as a parameter as you've done, when it DOES have access to my global object. What's the difference?
sure i will explain it to you.

your code was this:
for(i=0, j=cals.length; i<j; i++){
	//construct final URL based on projection and parameter choices
	var url = jGCal.googleFeedAPI_URL+cals[i][0]+'/'+jGCal.projection+jGCal.parameters+"&num="+jGCal.maxResults;
	var lastResponse = null;
	//$.getJSON is our way around cross-site script defences
	$.getJSON(url,
		function(data){
			for(i=0; i<cals.length; i++){
//AAAARRRRRGH!!!!
				jGCal.calendars[i][2] = data.responseData.feed.title; //Attach response entries to associated calendar item
			}
		}
	);
}

Open in new window


my code changes involved this part of yours:
for(var i=0, j=cals.length; i<j; i++){
	//construct final URL based on projection and parameter choices
	var url = jGCal.googleFeedAPI_URL+cals[i][0]+'/'+jGCal.projection+jGCal.parameters+"&num="+jGCal.maxResults;
	var lastResponse = null;
	//$.getJSON is our way around cross-site script defences
	$.getJSON(url,
		function(i) {
			return function(data){
				jGCal.calendars[i][2] = data.responseData.feed.title;
			}
		}(i)
	);
}

Open in new window


what i did:

in the for i wrote "var" before the definition of i
you should do that too because defining i globaly is an often done mistake and would ruin multiple for loops if they run at the same time. (asyncronously)


then i've wrapped the function wich you defined in your json ajax call into this:

function (i) {
    return /*--code--*/
}(i)

all this does is grabbing i in the moment that json call is getting executed and putting it into the scope of the ajax onsuccess event.

its like.. the ajax calls the url.. then it takes a while to retrive that url.
in that time the for loop on top would have already called this json call again. this would change i everytime the loop runs through.

out of this i can tell you that the json call is having the scope of that for loop. so when you use a variable outside that json call you must keep an eye on variables that could change in that scope.

the wrapper does nothing else then grabbing i once and creating a new scope for the function you actually want to execute when the query is done.
so when then the function accesses i it will not access the i from the for loop, it would access the i that got defined while the json call got executed.



hope this helps you understanding my code.
iGottZ
Thanks for the in-depth explanation. I understand the concept of what I was missing completely now!