Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript not resizing height of UL element sometimes when inserting LI elements using Jquery

I have an Html/JavaScript application that contains N columns that need to be large enough contain all of the possible LI elements from all of the columns.

The simple solution seems to count the heights of all of the items in each column, compensate for padding, and then set the height to that total for each of the columns.

This works great when the LI elements contain plain text. Unfortunately, when the LI elements contain images instead, various browsers have problems. For example, when I first load the page in FireFox, it looks like the screenshot below, but upon another refresh, it works fine. It doesn't work as expected in Chrome either.

Screenshot Showing the height of the UL element is not in sync with the LI elements

My application does not pre-populate the LI elements when the page loads - it uses JavaScript, as follows:

function populateUnsetAnswers(unsetCategoryAnswers) {
    for (i in unsetCategoryAnswers) {
        if (unsetCategoryAnswers.hasOwnProperty(i.toString())) {
            $('#categoryQuestionArea #possibleAnswers').append(
                categoryAnswerLiTag(unsetCategoryAnswers[i])
            );
        }
    }
}

function categoryAnswerLiTag(unsetCategoryAnswer) {
    var html = '<li id="' + unsetCategoryAnswer.id + '">';

    if (unsetCategoryAnswer.image) {
        html += '<img class="categoryAnswerImage" title="';
        html += unsetCategoryAnswer.text;
        html += '" src="/trainingdividend/rest/streaming/';
        html += unsetCategoryAnswer.image.fileName;
        html += '" style="height: ';
        html += unsetCategoryAnswer.image.height;
        html += ';';
        html += '" />';
    } else {
        html += unsetCategoryAnswer.text
    }

    html += '</li>';

    return html;
}

When the page is done loading, an ajax request fetches all of the objects to be put into LI elements, and then calls the first function above.

After all of the LI elements are created, I call this function right after it:

function resize() {
    var currentHeight, totalHeight;
    totalHeight = 0;

    $("#categoryQuestionArea ul").children().each(function() {
        currentHeight = $(this).height();

        totalHeight += currentHeight + 13;
    });

    $("#categoryQuestionArea ul").height(totalHeight);
    $("#categoryQuestionArea div#separator").css("padding-top", (totalHeight / 2) + "px");
}

Is there any way to tell jQuery, "Don't call resize() until all of the LI's are fully loaded and the images have rendered" ?

I think what's happening is that on the initial page load, the height of these LI elements is 0 or a small value because it doesn't contain the image, so my resize function is calculating the wrong result (I tested this with some alert statements). As long as the LIs are populated and the images have loaded, the total height is calculated just fine.

Any help? Thanks

like image 881
Fire Emblem Avatar asked Apr 17 '12 03:04

Fire Emblem


1 Answers

To literally answer the question you asked, if you want to only call resize() when all images have finished loading, then you need to install onload handlers for those images and when you've recorded that the last one is now loaded, you can call the resize() function. You could do that like this (code explanation below):

var remainingAnswerImages = 0;

function categoryAnswerImageLoadHandler() {
    --remainingAnswerImages;
    if (remainingAnswerImages === 0) {
        resize();
    }
}

function populateUnsetAnswers(unsetCategoryAnswers) {
    // add one extra to the image count so we won't have any chance 
    // at getting to zero  before loading all the images
    ++remainingAnswerImages;
    var possibleAnswers$ = $('#categoryQuestionArea #possibleAnswers');
    for (i in unsetCategoryAnswers) {
        if (unsetCategoryAnswers.hasOwnProperty(i.toString())) {
            possibleAnswers$.append(categoryAnswerLiTag(unsetCategoryAnswers[i]));
        }
    }
    // remove the one extra
    --remainingAnswerImages;
    // if we hit zero on the count, then there either were no images 
    // or all of them loaded immediately from the cache
    // if the count isn't zero here, then the 
    // categoryAnswerImageLoadHandler() function will detect when it does hit zero
    if (remainingAnswerImages === 0) {
        resize();
    }
}

function categoryAnswerLiTag(unsetCategoryAnswer) {
    var obj = document.createElement("li");
    obj.id = unsetCategoryAnswer.id;

    if (unsetCategoryAnswer.image) {
        // count this image
        ++remainingAnswerImages;
        var img = new Image();
        img.onload = img.onerror = img.onabort = categoryAnswerImageLoadHandler;
        img.title = unsetCategoryAnswer.text;
        img.style.height = unsetCategoryAnswer.image.height;
        img.src = "/trainingdividend/rest/streaming/" + unsetCategoryAnswer.image.fileName;
        obj.appendChild(img);
    } else {
        obj.innerHTML = unsetCategoryAnswer.text;
    }
    return obj;
}

By way of explanation, this code makes the following changes:

  • Add a variable remainingAnswerImages to keep track of how many images still need to be loaded.
  • Add an onload handler for each <img> tag that is created so we can keep track of when it's loaded.
  • Each time we generate the HTML for an tag with the onload handler, increment remainingAnswerImages.
  • When you're done adding all the HTML, check the remainingAnswerImages count to see if it's zero (this would only be the case if there were no images or if all images loaded immediately from the browser cache). If so, call resize() immediately.
  • In the onload handler which will be called for each image, decrement remainingAnswerImages and if the count has reached zero, call resize().
  • While adding images, add one extra to remainingAnswerImages as a gate to keep from getting to a zero count until we're done adding images. When done adding images, take that one extra out.
  • I also rewrote the categoryAnswerLiTag() function to just create the DOM objects directly rather than concat a bunch of strings together into HTML. In this case, the code is a lot cleaner to read and maintain.
  • I also moved the $('#categoryQuestionArea #possibleAnswers') out of your for loop since it resolves to the same thing every time. Better to do it once before the loop. Also, in most cases, this could be simplified to $('#possibleAnswers') since ids are supposed to be unique in the page.
like image 151
jfriend00 Avatar answered Sep 20 '22 09:09

jfriend00