0

This is a design portfolio page. On load, the JSON data is retrieved via ajax, and one of the keys is used to generate a list of '.project-links' (no project is displayed on load, and the project images are loaded only when a project is selected (see showProj function)). My question regards the fadein/fadeout: the images are still painting onto the screen after the fade in completes, despite the project content being defined/loaded within the fadeOut callback; can someone please enlighten me as to how I can tweak this so that the fadeIn won't run until the projImages are loaded?

Thank you, svs.

function ajaxReq() {
    var request = new XMLHttpRequest();
    return request;
}


function makeLinks(projects) { // result = getJsonData > request.responseText
    var projectList = document.getElementById("project-list");

    for (var project in projects) {
        if (projects[project].status !== "DNU") {
            var projectId = projects[project].id;
            var listItem = "<li><a class=\"project-link\" id=\""+projects[project].project+"\" href=\"#\">" + projects[project].project + "</a></li>";
            projectList.innerHTML += listItem;
        } // if !DNU
    }

    // ADD EVENT LISTENERS
    var projLink = document.getElementsByClassName("project-link");
    for (var i = 0; i < projLink.length; i++) {
        var projId = projLink[i].id;
        //projLink[i].dataset.projIx = [i];
        projLink[i].addEventListener("click", showProject, false);
    }


    var showNext = document.getElementById("show-next");
    var showPrev = document.getElementById("show-previous");
    showNext.addEventListener("click", showProject, false);
    showPrev.addEventListener("click", showProject, false);

    // ARROW KEYS [not invoking the showProject function]
    $(document).keydown(function(e) {
        if(e.which==37) { // LEFT arrow
            $(showPrev).click(showProject);
            console.log("previous");
        } else
        if(e.which==39) { // RIGHT arrow
            $(showNext).click(showProject);
            console.log("next");
        }
    })


    function showProject(projId) {

        var intro = document.getElementById("intro");
        if (intro) {
            intro.parentNode.removeChild(intro);
        }
        projId.preventDefault();

        var projLinks = document.getElementsByClassName("project-link"); // array

        var selIx = $(".selected").index();


        // ###### CLICK PREVIOUS/NEXT ######
        if (this.id === "show-previous" || this.id === "show-next") {

            // 1a. if nothing is .selected
            if (selIx < 0) {
                if (this.id === "show-previous") {
                    var selIx = projLinks.length-1;
                }
                else if (this.id === "show-next") {
                    var selIx = 0;
                }
            }

            // 1b. if .selected:
            else if (selIx > -1) {
                if (this.id === "show-previous") {
                    if (selIx === 0) { // if @ first slide
                        selIx = projLinks.length-1;
                    }
                    else {
                        selIx --;
                    }
                }
                else if (this.id === "show-next") {
                    if (selIx === projLinks.length-1) { // if @ last slide
                        selIx = 0;
                    }
                    else {
                        selIx ++;
                    }
                }
            }
            var selProjLi = projLinks[selIx]; // => li
        } // click previous/next


        // ###### CLICK .project-link ######
        else if (this.id !== "show-previous" && this.id !== "show-next") {
            var selIx = $(this).closest("li").index();
        }


        // FADE OUT, CALLBACK: LOAD NEW PROJECT
        $("#project-display").fadeTo(450, 0.0, function() {

            // ###### ALL ######
            $(".selected").removeClass("selected");


            var projId = projLink[selIx].id;
            var selProjLi = projLink[selIx].parentElement;
            selProjLi.className = "selected";

            var projectDisplay = document.getElementById("project-display");

            // set vars for the project display elements:
            var projName = document.getElementById("project-name"); // h3
            var projTools = document.getElementById("project-tools");
            var projNotes = document.getElementById("project-notes");
            var projImages = document.getElementById("project-images");

            // disappear the metadata elements 'cause sometimes they'll be empty
            projTools.style.display = "none";
            projNotes.style.display = "none";
            testimonial.style.display = "none";

            for (var project in projects) { // 'Projects array' -> project
                if (projects[project].project === projId) {

                    var activeProj = projects[project];

                    projName.innerHTML = activeProj.project;

                    // maintain centered display of project-metadata: check for a value, else the element remains hidden
                    if(activeProj["tools used"]) {
                        projTools.style.display = "inline-block";
                        projTools.innerHTML = activeProj["tools used"];
                    }
                    if(activeProj.notes) {
                        projNotes.style.display = "inline-block";
                        projNotes.innerHTML = activeProj.notes;
                    }
                    if(activeProj.testimonial) {
                        testimonial.style.display = "inline-block";
                        testimonial.innerHTML = activeProj.testimonial;
                    }

                    // HOW TO ENSURE THESE ARE ALREADY LOADED ***BEFORE #project-display FADES IN***
                    projImages.innerHTML = "";
                    for (var i = 0; i < activeProj.images.length; i++ ) {
                        projImages.innerHTML += "<img src=\"" + activeProj.images[i].url + "\" />";
                    }
                } // if project id ...
            } // for (var obj in data)
        }) // fade out

        $("#project-display").fadeTo(600, 1.0);

    } // showProject
} // makeLinks

function getJsonData() {
    var request = ajaxReq();
    request.open("GET", "/json/projects.json", true);
    request.setRequestHeader("content-type", "application/json");
    request.send(null);
    request.onreadystatechange = function() {
        if (request.readyState === 4) {
            if (request.status === 200) {
                //makeLinks(request.responseText);
                var projects = JSON.parse(request.responseText);
                var projects = projects["Projects"];
                makeLinks(projects); // makeLinks = callback
                return projects;
            }
        }
    } // onreadystatechange
} // getJsonData

getJsonData(makeLinks);
user1613163
  • 127
  • 1
  • 3
  • 16

1 Answers1

0

You can add a load event to the images and run the fadeOut when all the images are loaded.

Since you are going to need multiple images to complete loading, I chose to keep track of which loads are complete using an array of jQuery.Deferred() objects. Once all the Deferreds are resolved then you can run the fade animation.

Here's a function that should work:

function fadeWhenReady(projImages, images) {
    projImages.innerHTML = "";
    var loads = []; //create holding bin for deferred calls

    //create images and attach load events
    for (var i = 0; i < activeProj.images.length; i++ ) {
        var deferred = $.Deferred();
        var img = $("<img src=\"" + activeProj.images[i].url + "\" />");
        img.on("load", function() { deferred.resolve() }); //after image load, resolve deferred
        loads.push(deferred.promise()); //add the deferred event to the array
        img.appendTo(projImages); //append image to the page
    }

    //when all deferreds are resolved, then apply the fade
    $.when.apply($, loads).done(function() {
        $("#project-display").fadeTo(600, 1.0); 
    });
}

In your function showProject remove your call to $("#project-display").fadeTo(600, 1.0); and replace the lines below with a call to the fadeWhenReady function.

projImages.innerHTML = "";
for (var i = 0; i < activeProj.images.length; i++ ) {
    projImages.innerHTML += "<img src=\"" + activeProj.images[i].url + "\" />";
}

P.S. You are using a strange mix of jQuery and vanilla javascript. The calls to document.getElementById() don't mind me so much, but I'd certainly recommend replacing your XMLHttpRequests with jQuery.ajax().

Dave
  • 10,748
  • 3
  • 43
  • 54
  • Thanks Dave, much appreciated. I would have responded much sooner however I am the not-so-lucky owner of a 2011 MacBook Pro so I lose a lot of hours restarting. – user1613163 Oct 26 '14 at 18:25
  • As to your help: first, thanks for the heads-up re: .Deferred; I think I get the gist of it however I'm experiencing scope confusion; where exactly should the fadeWhenReady function live? And as to my mix of js & jq: I'm studying js (I do know some jQ), trying to minimize use of jQ. But in the interest of expedience & simplicity I'm using jQ for class manipulations and fades which, btw, were an afterthought, and which seemed an apt use of jQ. Essentially my ignorance is how to fade elements which are loading via ajax/json. Thanks again for any further help. – user1613163 Oct 26 '14 at 18:40
  • It's not obvious to me why you are having scoping issues. I would try putting the new function at the same level as your `ajaxReq`, `makeLinks`, and `getJsonData` functions. – Dave Oct 27 '14 at 21:22
  • Thanks Dave. It's not obvious to me either ;) If I call fadeWhenReady(projImages, images); then 'images is not defined'; if I omit the images arguments (or both), then activeProj is not defined ... unless I dirty it up with repeated declarations of any var, ergo my ongoing scope confusion. – user1613163 Oct 28 '14 at 20:03
  • I think it should be `fadeWhenReady(projImages, activeProj.images);` – Dave Oct 28 '14 at 20:42