views:

198

answers:

1

Hello, I have a peculiar problem. I am loading a JSON file that contains the src, width and height of a number of images.

I am trying to load them into the smooth script jquery plugin and have the images scroll automatically.

The "viewing area" div can fit three images side by side.

When I first load the page, only the first image is shown. If I reload it, the first three images are shown, and the others (there are 11 in total) are loaded, but the plugin does not scroll, so you can't see them.

When I insert an alert("whatever") line in the $(window).one function, everything works fine.

I suspect it would have something to do with when the images are loaded and when the .load event is being triggered.

Here is the code that parses the JSON files and creates an HTML string that is appended to the scrollable div:

var aSectionImages = new Array;
var aImagesCount = new Array();
var aImagesLoaded = new Array();
var htmlString;
var jsonStyleImages = "images.json"
 var jsonNavImages = "imagesNav.json";
var scrollableArea = $("#scrollableArea");
$.getJSON(jsonNavImages, getNavIcons);
$.getJSON(jsonStyleImages, makeScroller);

function imageLoaded(imgSrc){
 var locId = (imageInSection(imgSrc)).replace(/\s|'/g, "_");
 if (aImagesLoaded[locId]===undefined){
  aImagesLoaded[locId] = 0;
 };     
 aImagesLoaded[locId] = aImagesLoaded[locId]+1;
 if (aImagesLoaded[locId]==aImagesCount[locId]){
  //alert("section" + locId);
  //$("#loaded").html(locId);
  //aSectionLoaded = {locId:"loaded"};

  //in theory, wait until all images in a section are loaded before
  //appending the HTML to the div:
  scrollableArea.append(htmlString);
 }
}

function imageInSection(src){
 var resultId=false;
 var locSrc = src.split("/");
 var locFileName = (locSrc[locSrc.length-1]);
 for (i = 0; i < aSectionImages.length; i++){
   for(j=0; j < aSectionImages[i].images.length; j++){
    tempSrc = aSectionImages[i].images[j].src.split("/");
    tempFileName = tempSrc[tempSrc.length-1];
    if (tempFileName == locFileName) {
     resultId = aSectionImages[i].id;
    }
   }
 }
 return resultId;
}

function makeScroller(data){
 aSectionImages = data;
 for (i=0; i<aSectionImages.length;i++){
  locData = aSectionImages[i];
  locId = locData.id.replace(/\s|'/g, "_");
  aImagesCount[locId] = locData.images.length;
  htmlString = "<div id=\"" + locId + "\">";

   for (j=0; j<locData.images.length; j++){
    oImage = new Image();
    $(oImage)
     .load(function(){
      imageLoaded(this.src);
    })
     .attr("src", locData.images[j].src);

     if (oImage.complete && oImage.naturalWidth !== 0){
     $(this).trigger("load");
     //$("#trigger").html(locData.images[j].src);
     //return false;
     }
    locImage = locData.images[j];
    locImage.id ? locImage.id = " id=\""+locImage.id+"\" " : locImage.id = "";
    htmlString += "<img height=\"" + locImage.height + "\"" + " width=\"" + locImage.width + "\"" + locImage.id + " src=\"" + locImage.src + "\" />";
   }       
 }

}

Here is part of the pluging that deals with displaying images as they are loaded:

// Stuff to do once on load $(window).one("load",function(){ //alert("why does it work if I include an alert here?");

// If the content of the scrolling area is not loaded through ajax,
// we assume it's already there and can run the code to calculate
// the width of the scrolling area, resize it to that width
if(options.ajaxContentURL.length === 0) {
 $mom.scrollableAreaWidth = 0;
 $mom.tempStartingPosition = 0;

 $mom.find(options.scrollableArea).children().find("img").each(function() {

  // Check to see if the current element in the loop is the one where the scrolling should start
  if( (options.startAtElementId.length !== 0) && (($(this).attr("id")) == options.startAtElementId) ) {
  $mom.tempStartingPosition = $mom.scrollableAreaWidth;
  }

  // Add the width of the current element in the loop to the total width
  $mom.scrollableAreaWidth = $mom.scrollableAreaWidth + $(this).outerWidth(true);
 });

 // Set the width of the scrollableArea to the accumulated width
 $mom.find(options.scrollableArea).css("width", $mom.scrollableAreaWidth + "px");

 // Check to see if the whole thing should be hidden at start
 if(options.hiddenOnStart) {
  $mom.hide();
 }
}

// Set the starting position of the scrollable area. If no startAtElementId is set, the starting position
// will be the default value (zero)
$mom.find(options.scrollWrapper).scrollLeft($mom.tempStartingPosition);

// If the user has set the option autoScroll, the scollable area will
// start scrolling automatically
if(options.autoScroll !== "") {
 $mom.autoScrollInterval = setInterval(autoScroll, $mom.autoScrollDelay);
}

// If autoScroll is set to always, the hot spots should be disabled
if(options.autoScroll == "always")
{
 hideLeftHotSpot();
 hideRightHotSpot();
}

// If the user wants to have visible hot spots, here is where it's taken care of
switch(options.visibleHotSpots)
{
 case "always":
  makeHotSpotBackgroundsVisible();
  break;
 case "onstart":
  makeHotSpotBackgroundsVisible();
  $mom.hideHotSpotBackgroundsInterval = setInterval(hideHotSpotBackgrounds, (options.hotSpotsVisibleTime * 1000));
  break;
 default:
  break; 
}

});

The complete code is at http://chereecheree.com/dagworthy/style.html

You'll notice that if you reload the page, it works as expected, but if you follow the link from one level up (dagwothy/), only the first image loads, unless the alert statement is thrown in on load.

Thanks for taking the time to look into this! --Daniel.

+2  A: 

I think your problem is here, or at least the first one I see:

oImage = new Image();
$(oImage).load(function(){
  imageLoaded(this.src);
}).attr("src", locData.images[j].src);

if (oImage.complete && oImage.naturalWidth !== 0){
  $(this).trigger("load");
}

In this context this isn't oImage (like it would be in a .each()), it's document, so you're firing the load on the wrong element, you need to have $(oImage).trigger("load") or $(oImage).load() instead.

The case you're trying to handle, firing load manually because not all browsers fire it when loading an image from cache is the right idea...you just need to fire the event on the right element, otherwise this condition's never true, because all those images never had a load event to increment the count:

if (aImagesLoaded[locId]==aImagesCount[locId]){

The alert() gives the images time to load, so everything's ready before anything else continues...that in itself throws any race conditions you have out the window, so when that happens it's usually either a) some dependency gets loaded that didn't have time before, or b) some event order is thrown off, by giving the first one time to load...which is usually somewhat of part a as well.

Nick Craver
+1 for that last paragraph which points to a common "need to know" on web apps.
Mark Schultheiss
+1 for the same. Any time a web app works with an alert thrown in, odds are you are experiencing a race condition.
ajm
Thank you, changing that line and using $(oImage) instead of $(this) has uncovered a number of other errors further down that I'm going through at the moment. Thanks again
D.Arce