I am new to JavaScript. I created the webpage linked below as an exercise for a class I am taking.
It seems to work ok if it all loads correctly, but half the time either the images or buttons do not load, and sometimes the thumbnail loads but the full-size (mouseover) image does not load. In Firefox or IE, it seems to only happen on first loads, and after that (as long as browser stays open), it will successfully load every time after that. But in Chrome it continues to act random every single page reload. Just refresh the page about 10 times in a row and you will probably see a couple instances where either the buttons or the images (or both) don’t load.
I am assuming this is a problem with my code, since I’ve never had any other problems with the server. Any ideas?
Thanks!
<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<style type = "text/css">
.box { border: 1px solid black; padding: 4px }
</style>
<title>Product Catalog</title>
<script>
var catalogDiv;
var summaryRequest;
var descriptionsRequest;
var thumbsRequest;
var imagesRequest;
function showLargeImage( imageElement )
{
imageElement.style.display = "none";
imageElement.nextSibling.style.display = "inline";
}
function showThumb( imageElement )
{
imageElement.style.display = "none";
imageElement.previousSibling.style.display = "inline";
}
function showDesc( descButton )
{
if ( descButton.nextSibling.style.display == "none" ) {
descButton.nextSibling.style.display = "block";
} else {
descButton.nextSibling.style.display = "none";
}
}
function getDescriptions()
{
try
{
descriptionsRequest = new XMLHttpRequest();
descriptionsRequest.addEventListener("readystatechange",
loadDescriptions, false );
descriptionsRequest.open( "GET", "descriptions.json", true );
descriptionsRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
descriptionsRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function loadDescriptions()
{
if ( descriptionsRequest.readyState == 4
&& descriptionsRequest.status == 200 )
{
var descriptions = JSON.parse( descriptionsRequest.responseText );
for ( var i = 0; i < descriptions.length; i++ ) {
var infoDiv = document.getElementById( descriptions[i].id +
"-info-inner" );
var descButton = document.createElement( "button" );
infoDiv.appendChild( descButton );
descButton.type = "button";
descButton.textContent = "show description";
descButton.setAttribute( "onclick", "showDesc( this )");
var desc = document.createElement( "fieldset" );
desc.style.display = "none";
desc.style.margin = "10px";
infoDiv.appendChild( desc );
desc.innerHTML = "<br>" + descriptions[i].text + "<br><br>" ;
}
}
}
function getImages()
{
try
{
imagesRequest = new XMLHttpRequest();
imagesRequest.addEventListener("readystatechange",
loadImages, false );
imagesRequest.open( "GET", "images.json", true );
imagesRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
imagesRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function loadImages()
{
if ( imagesRequest.readyState == 4 && imagesRequest.status == 200 )
{
var images = JSON.parse( imagesRequest.responseText );
for ( var i = 0; i < images.length; i++ ) {
var imageDiv = document.getElementById( images[i].id +
"-image-inner" );
imageDiv.innerHTML += "<img style=\"display:none;\"" +
"src=\"" + images[i].filename+ "\">";
imageDiv.lastChild.setAttribute( "onmouseout",
"showThumb( this )" );
}
}
}
function getThumbs()
{
try
{
thumbsRequest = new XMLHttpRequest();
thumbsRequest.addEventListener("readystatechange",
loadThumbs, false );
thumbsRequest.open( "GET", "thumbs.json", true );
thumbsRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
thumbsRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function loadThumbs()
{
if ( thumbsRequest.readyState == 4 && thumbsRequest.status == 200 )
{
var thumbs = JSON.parse( thumbsRequest.responseText );
for ( var i = 0; i < thumbs.length; i++ ) {
var imageDiv = document.getElementById( thumbs[i].id +
"-image-inner" );
imageDiv.innerHTML = "<img style=\"display:inline;\"" +
"src=\"" + thumbs[i].filename+ "\">";
imageDiv.firstChild.setAttribute( "onmouseover",
"showLargeImage( this )");
}
}
}
function setupDivsRequest()
{
try
{
summaryRequest = new XMLHttpRequest();
summaryRequest.addEventListener("readystatechange",
setupDivsResponse, false );
summaryRequest.open( "GET", "summary.json", true );
summaryRequest.setRequestHeader( "Accept",
"application/json; charset=utf-8" );
summaryRequest.send();
}
catch ( exception )
{
alert( "Request Failed" );
}
}
function setupDivsResponse()
{
if ( summaryRequest.readyState == 4 && summaryRequest.status == 200 )
{
var summary = JSON.parse( summaryRequest.responseText );
for ( var i = 0; i < summary.length; i++ ) {
var productDiv = document.createElement( "div" );
var productImageOuterDiv = document.createElement( "div" );
var productImageInnerDiv = document.createElement( "div" );
var productInfoOuterDiv = document.createElement( "div" );
var productInfoInnerDiv = document.createElement( "div" );
catalogDiv.appendChild(productDiv);
productDiv.appendChild( productImageOuterDiv );
productDiv.appendChild( productInfoOuterDiv );
productImageOuterDiv.appendChild( productImageInnerDiv );
productInfoOuterDiv.appendChild( productInfoInnerDiv );
productDiv.id = summary[i].id;
productDiv.className = "box";
productImageOuterDiv.id = summary[i].id + "-image-outer";
productImageOuterDiv.style.cssFloat = "left";
productImageInnerDiv.id = summary[i].id + "-image-inner";
productImageInnerDiv.style.height = "250px";
productImageInnerDiv.style.width = "250px";
productImageInnerDiv.style.display = "table-cell";
productImageInnerDiv.style.verticalAlign = "middle";
productImageInnerDiv.style.textAlign = "center";
productInfoOuterDiv.id = summary[i].id + "-info-outer";
productInfoOuterDiv.style.height = "250px";
productInfoInnerDiv.id = summary[i].id + "-info-inner";
productInfoInnerDiv.style.float = "left";
productInfoInnerDiv.style.padding = "10px";
productInfoInnerDiv.innerHTML = summary[i].title + "<br>";
productInfoInnerDiv.innerHTML += summary[i].price + "<br><br>";
}
}
}
function start()
{
catalogDiv = document.getElementById( "catalog" );
setupDivsRequest();
getThumbs();
getImages();
getDescriptions();
}
window.addEventListener( "load", start, false );
</script>
</head>
<body>
<h1>Mouse over a product thumbnail for a larger picture.</h1>
<div id = "catalog"></div>
</body>
</html>
It took a long time, but I finally got to the bottom of this problem. It was a race condition between multiple asynchronous requests to populate the same element. I had not considered this was possible, so the one which I expected to go first would add the first HTML to the element:
While the request which I expected to go second would add the second HTML:
Obviously if those requests go out of order, because of the way I used
=and+=, the result will be that “second text” gets overwritten, which is essentially why my images weren’t loading half the time. (Even if I had used+=in both cases, I’d still have the problem of randomly ordered elements as my code below shows).For whatever reason, the race condition never seemed to matter in Firefox or IE. Maybe there is something in those browsers to try to safeguard against such a condition, by forcing requests to finish in the order they started? Or maybe it is just dumb luck. But in Chrome, the requests would consistently finish in a random order. A much simpler code below illustrates clearly. In Chrome, half the time you will get “FOOBAR” as HTML output, but the other half of the time you will get “BARFOO.” The testx.json files I reference in the script are dummy (empty) files.
The race condition is easily fixed in this situation by having my second setup function called by the first setup’s callback function after completing its other tasks. In a more complicated situation I would guess the other typical race condition safeguards (mutexes and semaphores) would work as well.