1

JavaScript/jQuery newbie here... I am working towards looping through a JSON file and succesively render top-level items using jQuery. The aim here is to display individual elements and then fade them out before display successive ones. However, my script only renders the last element. Any idea what it is I could be doing wrong here?

JSON file---x.json

{"questions":[
    {
    "q":"Question1...?",
    "a": [
    "Answer 1a",
    "Answer 1b",
    "Answer 1c",
    "Answer 1d"
]
},
{
    "q":"Question2...?",
    "a": [
    "Answer 2a",
    "Answer 2b",
    "Answer 2c",
    "Answer 2d"
]
}
]}

JavaScript file---x.js

$(document).ready( function () {
    $.getJSON('x.json', function (jsondata) {
        // compute total number of questions in JSON file
        var totalQuestions = jsondata.questions.length;
        $.each(jsondata.questions, function (i) {
            var questionNumber = i + 1; // add one since indicies start at 0
            var questionContent = jsondata.questions[i]["q"];
            var answerContent = ''; 
            // generate questions progress HTML text
            var questionHTML = '<div class="questionCount">Question <span class="current">' + questionNumber +'</span> of <span class="total">' + totalQuestions + '</span>';
            // generate question HTML text
            questionHTML += '</div><h3>' + questionNumber + '. ' + questionContent + '</h3>';
            console.log(JSON.stringify(jsondata.questions[i]["q"]));
            var answersHTML = '<ul type="A" class="answers">';
            $.each(jsondata.questions[i]["a"], function (k) {
                answerContent = jsondata.questions[i]["a"][k];
                answersHTML += '<li><input type="radio"><label>' + answerContent + '</label></li>';
            }); 
            answersHTML += '</ul></li>';
            questionHTML += answersHTML;
            console.log(questionHTML);
            $("#placeholder").html(questionHTML);
            setInterval(function () {
                $("#placeholder").html(questionHTML);
                $("#placeholder").fadeIn(6000).delay(3000).fadeOut(1000);
            }, 5000);
        }); 
    }); 
});

HTML file---x.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>JSON Sample</title>
</head>
<body>
    <div id="placeholder"></div>
    <script src="js/jquery.js"></script>
    <script src="x.js"></script>
</body>
</html>
lightonphiri
  • 782
  • 2
  • 14
  • 29

2 Answers2

1

First I assume, that you want to position all questions at same position, right? Add some CSS as following:

#placeholder {
    position: relative;
}
#placeholder .question {
    position: absolute;
}

I wrapped your question and the according answers in a DIV with class .question. Change your jQuery code to following:

            [...]

            // generate questions progress HTML text
            var questionHTML = '<div class="question" style="display:none;">
                    <div class="questionCount">Question <span class="current">' + questionNumber + '</span> of <span class="total">' + totalQuestions + '</span>';

            [...]

            answersHTML += '</ul></li>
                </div>';

            [...]

            console.log(questionHTML);
            $("#placeholder").html(questionHTML);
        }); // end foreach
    }); // end .getJSON callback

    animate();
}); // end $(document).ready callback

function animate(current) {
    var count = $('#placeholder .question').length;
    if (isNaN(current)) { current = 0; }
    console.log(current);
    animateOne($('#placeholder .question').get(current), function() {
        animate(++current % count);
    });
};

function animateOne(el, callback) {
    $(el).fadeIn(6000).delay(3000).fadeOut(1000, function() {
        callback();
    });
};

Try out the JSFiddle: http://jsfiddle.net/97buosve/7/

Alternative animation

You can use this altenative function to maybe see both questions at same time while fading. I just move the callback to fadeIn back from fadeOut...

function animateOne(el, callback) {
    $(el).fadeIn(6000, function() {
        callback();
    }).delay(3000).fadeOut(1000);
};

Try out the JSFiddle: http://jsfiddle.net/97buosve/8/

Alternative animation without loop

function animate(current) {
    var count = $('#placeholder .question').length;
    if (isNaN(current)) { current = 0; }
    console.log(current);
    animateOne(
        $('#placeholder .question').get(current), // question to animate
        (current +1 < count), // hide it after fadeIn, only if it isn't the last question
        function() { // callback to animate the next question
            if (current +1 < count) { // prevent loop, only set callback if it isn't the last question
                animate(++current % count);
            }
        }
    );
};

function animateOne(el, hideItAfter, callback) {
    $(el).fadeIn(6000, function() {
        callback();
    });
    if (hideItAfter) {
        $(el).delay(3000).fadeOut(1000);
    }
}

http://jsfiddle.net/97buosve/9/

algorhythm
  • 8,530
  • 3
  • 35
  • 47
  • Thanks for this, however, I cannot seem to figure out why it loops through the elements continuously. In an ideal, I'd want it to stop after rendering the last element. – lightonphiri Sep 19 '14 at 08:29
  • It loops, because the function `animate` calls itself when it's finished. We have a variable to remember the current animated question first it is 0. And we have a count that tolds us the number of all questions e.g. 2. `animate` calls `animateOne` to start the animation of one question (fadeIn, delay, fadeOut, AND callback after animation). The next question to animate is `++current % count`, we iterate the variable `current` and to prevent that it'll be greater than our `count` us the `modulo` operator to begin with first question (e.g. `2 % 2 == 0`)... – algorhythm Sep 19 '14 at 08:36
  • ...The trick is the last parameter of `animateOne` there we define what happens after animation and after animation we call `animate` again. More clear now? – algorhythm Sep 19 '14 at 08:36
  • 1
    If you don't want to loop it here is a modified variant: http://jsfiddle.net/97buosve/9/ – algorhythm Sep 19 '14 at 09:05
  • 1
    Sure you can also do it with one function, but two functions are more readable: http://jsfiddle.net/97buosve/10/ – algorhythm Sep 19 '14 at 09:15
  • Thank you for this---most appreciated. It all makes sense now. – lightonphiri Sep 19 '14 at 09:50
0

Two errors:

1) setInterval is not a "pause" function. it returns immediately, so your loop flashes through in milliseconds (thus you only see the last repetition).

2) you do not set up a closure. Check this frequently asked question, on how to pass variables in closures inside loops: JavaScript closure inside loops – simple practical example

Community
  • 1
  • 1
pkExec
  • 1,752
  • 1
  • 20
  • 39