JS Survey Input

I am using a JS survey code

(function() {
  var questions = [{
    question: "What is 2*5?",
    choices: [2, 5, 10, 15, 20],
    correctAnswer: 2
  }, {
    question: "What is 3*6?",
    choices: [3, 6, 9, 12, 18],
    correctAnswer: 4
  }, {
    question: "What is 8*9?",
    choices: [72, 99, 108, 134, 156],
    correctAnswer: 0
  }, {
    question: "What is 1*7?",
    choices: [4, 5, 6, 7, 8],
    correctAnswer: 3
  }, {
    question: "What is 8*8?",
    choices: [20, 30, 40, 50, 64],
    correctAnswer: 4
  }];
  
  var questionCounter = 0; //Tracks question number
  var selections = []; //Array containing user choices
  var quiz = $('#quiz'); //Quiz div object
  
  // Display initial question
  displayNext();
  
  // Click handler for the 'next' button
  $('#next').on('click', function (e) {
    e.preventDefault();
    
    // Suspend click listener during fade animation
    if(quiz.is(':animated')) {        
      return false;
    }
    choose();
    
    // If no user selection, progress is stopped
    if (isNaN(selections[questionCounter])) {
      alert('Please make a selection!');
    } else {
      questionCounter++;
      displayNext();
    }
  });
  
  // Click handler for the 'prev' button
  $('#prev').on('click', function (e) {
    e.preventDefault();
    
    if(quiz.is(':animated')) {
      return false;
    }
    choose();
    questionCounter--;
    displayNext();
  });
  
  // Click handler for the 'Start Over' button
  $('#start').on('click', function (e) {
    e.preventDefault();
    
    if(quiz.is(':animated')) {
      return false;
    }
    questionCounter = 0;
    selections = [];
    displayNext();
    $('#start').hide();
  });
  
  // Animates buttons on hover
  $('.button').on('mouseenter', function () {
    $(this).addClass('active');
  });
  $('.button').on('mouseleave', function () {
    $(this).removeClass('active');
  });
  
  // Creates and returns the div that contains the questions and 
  // the answer selections
  function createQuestionElement(index) {
    var qElement = $('<div>', {
      id: 'question'
    });
    
    var header = $('<h2>Question ' + (index + 1) + ':</h2>');
    qElement.append(header);
    
    var question = $('<p>').append(questions[index].question);
    qElement.append(question);
    
    var radioButtons = createRadios(index);
    qElement.append(radioButtons);
    
    return qElement;
  }
  
  // Creates a list of the answer choices as radio inputs
  function createRadios(index) {
    var radioList = $('<ul>');
    var item;
    var input = '';
    for (var i = 0; i < questions[index].choices.length; i++) {
      item = $('<li>');
      input = '<input type="radio" name="answer" value=' + i + ' />';
      input += questions[index].choices[i];
      item.append(input);
      radioList.append(item);
    }
    return radioList;
  }
  
  // Reads the user selection and pushes the value to an array
  function choose() {
    selections[questionCounter] = +$('input[name="answer"]:checked').val();
  }
  
  // Displays next requested element
  function displayNext() {
    quiz.fadeOut(function() {
      $('#question').remove();
      
      if(questionCounter < questions.length){
        var nextQuestion = createQuestionElement(questionCounter);
        quiz.append(nextQuestion).fadeIn();
        if (!(isNaN(selections[questionCounter]))) {
          $('input[value='+selections[questionCounter]+']').prop('checked', true);
        }
        
        // Controls display of 'prev' button
        if(questionCounter === 1){
          $('#prev').show();
        } else if(questionCounter === 0){
          
          $('#prev').hide();
          $('#next').show();
        }
      }else {
        var scoreElem = displayScore();
        quiz.append(scoreElem).fadeIn();
        $('#next').hide();
        $('#prev').hide();
        $('#start').show();
      }
    });
  }
  
  // Computes score and returns a paragraph element to be displayed
  function displayScore() {
    var score = $('<p>',{id: 'question'});
    
    var numCorrect = 0;
    for (var i = 0; i < selections.length; i++) {
      if (selections[i] === questions[i].correctAnswer) {
        numCorrect++;
      }
    }
    
    score.append('You got ' + numCorrect + ' questions out of ' +
                 questions.length + ' right!!!');
    return score;
  }
})();

I would like to change it so as to accept also text and ordinary buttons as input. I figured from the code that it uses a function to create radio buttons for all questions. How do I modify this, so as to allow me to choose the input method for each question e.g select, radio, text etc. I have very little knowledge of JS, so I would appreciate your help.

EDIT: I would like to use number as input, not text area. The thing I need from the code is the navigation, allowing me to go next and back with the questions, but I want to modify the input and instead of correct answers to provide a result using conditions based on the input.

Hi @macrocosmos314, first you need a “create” function for each input type analogous to createRadios:

function createRadios(index) {
  // etc...
}

function createSelect(index) {
  var select = $('<select>', {name: 'answer'});
  var choices = questions[index].choices;
  var option;
  var i;

  for (i = 0; i < choices.length; i++) {
    option = $('<option>', {val: i}).text(choices[i]);
    select.append(option);
  }

  return select;
}

Then you might just store references to those “create” functions in the question objects themselves, like

var questions = [{
  question: "What is 2*5?",
  choices: [2, 5, 10, 15, 20],
  correctAnswer: 2,
  create: createRadios
}, {
  question: "What is 3*6?",
  choices: [3, 6, 9, 12, 18],
  correctAnswer: 4,
  create: createSelect
}, {
  question: "What is 8*9?",
  choices: [72, 99, 108, 134, 156],
  correctAnswer: 0,
  create: createRadios
}, {
  // etc.
}];

Within createQuestionElement() you can now call the create() function of a given question, and it will return the corresponding input element(s):

function createQuestionElement(index) {
  var currentQuestion = questions[index];
  var qElement = $('<div>', {id: 'question' + index});
  var header = $('<h2>').text('Question ' + (index + 1) + ':');
  var question = $('<p>').text(currentQuestion.question);
  var input = currentQuestion.create(index)

  qElement.append(header);
  qElement.append(question);
  qElement.append(input);

  return qElement;
}

Anyway, if you want to modify some piece of code to your needs, there’s actually no way around getting some understanding of that code (if by messing around with it). – At some point a bug will occur, and you won’t even know where to start looking. If you have trouble getting your head around certain parts, we’re happy to help! :-)

3 Likes

Thanks for your input m3g4p0p.

This is what I need. I need to create a survey, that will take as input either input numbers or buttons (not sure if possible) considering I use buttons for the navigation of the survey forward and backwards. I did not know how to do it in a way, so as to remain on the same page and navigate through the survey on it, so I found this code online that allows you to use javascript to modify the div of the page and navigate through the questions. I don’t really need the rest of the code, I don’t need correct answers, but I need to use weights that will determine a result based on selected answers for each questions that will be output to the user after finishing the survey. I am using Laravel for my website, so this will go inside my view page of the survey.

If you’re willing to help, I can provide you with more information in a PM.

Cheers!

Ah okay, yes that makes sense then. Well if you get stuck I can certainly assist you, but please post your questions here – that’s what this board is for, after all. ;-) Others might have similar problems to solve, and / or provide additional input.

2 Likes

Ok, so my next question is this. How would create functions that create input number and buttons as input look like, similar to the one you have written for select? I tried plugging in your lines in the code, but I only see the next button on the page. Is there something missing? How does create: createRadios work? Is a general create function missing?

This is how my code looks like now

(function() {
  var questions = [{
    question: "Random question",
    choices: ['1', '2', '3'],
    correctAnswer: '1'
    create: createRadios
  }, {
    question: "Question 2",
    choices: [3, 6, 9, 12, 18],
    correctAnswer: 6
    create: createSelect
  }, {
    question: "What is 8*9?",
    choices: [72, 99, 108, 134, 156],
    correctAnswer: 0
    create: createSelect
  }, {
    question: "What is 1*7?",
    choices: [4, 5, 6, 7, 8],
    correctAnswer: 3
    create: createSelect
  }, {
    question: "What is 8*8?",
    choices: [20, 30, 40, 50, 64],
    correctAnswer: 4
    createRadios
  }];
  
  var questionCounter = 0; //Tracks question number
  var selections = []; //Array containing user choices
  var quiz = $('#quiz'); //Quiz div object
  
  // Display initial question
  displayNext();
  
  // Click handler for the 'next' button
  $('#next').on('click', function (e) {
    e.preventDefault();
    
    // Suspend click listener during fade animation
    if(quiz.is(':animated')) {        
      return false;
    }
    choose();
    
    // If no user selection, progress is stopped
    if (isNaN(selections[questionCounter])) {
      alert('Please make a selection!');
    } else {
      questionCounter++;
      displayNext();
    }
  });
  
  // Click handler for the 'prev' button
  $('#prev').on('click', function (e) {
    e.preventDefault();
    
    if(quiz.is(':animated')) {
      return false;
    }
    choose();
    questionCounter--;
    displayNext();
  });
  
  // Click handler for the 'Start Over' button
  $('#start').on('click', function (e) {
    e.preventDefault();
    
    if(quiz.is(':animated')) {
      return false;
    }
    questionCounter = 0;
    selections = [];
    displayNext();
    $('#start').hide();
  });
  
  // Animates buttons on hover
  $('.button').on('mouseenter', function () {
    $(this).addClass('active');
  });
  $('.button').on('mouseleave', function () {
    $(this).removeClass('active');
  });
  
  // Creates and returns the div that contains the questions and 
  // the answer selections
 /* function createQuestionElement(index) {
    var qElement = $('<div>', {
      id: 'question'
    });
   
    var question = $('<p>').append(questions[index].question);
    qElement.append(question);
    
    var radioButtons = createRadios(index);
    qElement.append(radioButtons);
    
    return qElement;
  }*/

  function createQuestionElement(index) {
  var question = $('<p>').text(currentQuestion.question);
  var input = currentQuestion.create(index)

  qElement.append(header);
  qElement.append(question);
  qElement.append(input);

  return qElement;
}
  
  // Creates a list of the answer choices as radio inputs
  function createRadios(index) {
    var radioList = $('<ul>');
    var item;
    var input = '';
    for (var i = 0; i < questions[index].choices.length; i++) {
      item = $('<li>');
      input = '<input type="radio" name="answer" value=' + i + ' />';
      input += questions[index].choices[i];
      item.append(input);
      radioList.append(item);
    }
    return radioList;
  }

  function createSelect(index) {
  var select = $('<select>', {name: 'answer'});
  var choices = questions[index].choices;
  var option;
  var i;

  for (i = 0; i < choices.length; i++) {
    option = $('<option>', {val: i}).text(choices[i]);
    select.append(option);
  }

  return select;
}
  
  // Reads the user selection and pushes the value to an array
  function choose() {
    selections[questionCounter] = +$('input[name="answer"]:checked').val();
  }
  
  // Displays next requested element
  function displayNext() {
    quiz.fadeOut(function() {
      $('#question').remove();
      
      if(questionCounter < questions.length){
        var nextQuestion = createQuestionElement(questionCounter);
        quiz.append(nextQuestion).fadeIn();
        if (!(isNaN(selections[questionCounter]))) {
          $('input[value='+selections[questionCounter]+']').prop('checked', true);
        }
        
        // Controls display of 'prev' button
        if(questionCounter === 1){
          $('#prev').show();
        } else if(questionCounter === 0){
          
          $('#prev').hide();
          $('#next').show();
        }
      }else {
        var scoreElem = displayScore();
        quiz.append(scoreElem).fadeIn();
        $('#next').hide();
        $('#prev').hide();
        $('#start').show();
      }
    });
  }
  
  // Computes score and returns a paragraph element to be displayed
  function displayScore() {
    var score = $('<p>',{id: 'question'});
    
    var numCorrect = 0;
    for (var i = 0; i < selections.length; i++) {
      if (selections[i] === questions[i].correctAnswer) {
        numCorrect++;
      }
    }
    
    score.append('You got ' + numCorrect + ' questions out of ' +
                 questions.length + ' right!!!');
    return score;
  }
})();

Correct answers will need to be changed to accept the input as parameter, but for now I leave them in as I’m clueless on how to change that.

Also I see correct used in the function at the top but nowhere else declared in the code. If I wanted to write this from scratch where can I start and what can I use from the existent code even only if as a reference or mimic.

Indeed, you’re missing the commas before the create properties… this would throw syntax errors, which you can see in the console.

JavaScript objects are very flexible – you can store arbitrary data in each question object, such as the currently selected answer (so there’s actually no need for that selections array anyway). But does a survey even have “correct” answers? ^^

Yeah, that would certainly be a good idea… all that creating and removing of those #question DOM constructs seems a bit cumbersome to me. I’d suggest the following approach: generate all question initially and just set them to display: none – ideally on the backend, if that’s an option, so that you don’t have to mess with the DOM at this point. The markup might then look something like

<form id="survey">
  <div class="question hidden">
    <h2>What is 2*5, in your opinion?</h2>
    <label>
      1
      <input type="radio" name="answer1" value="1">
    </label>
    <label>
      2
      <input type="radio" name="answer1" value="10">
    </label>
    <label>
      3
      <input type="radio" name="answer1" value="100">
    </label>
  </div>

  <div class="question hidden">
    <h2>What would you prefer 3*6 to be?</h2>
    <input type="number" name="answer2">
  </div>

  <!-- etc. -->

  <a href="#" class="button prev">previous</a>
  <a href="#" class="button next">previous</a>
</form>

Then you only need to implement the prev/next button functionality with JS to sequentially show/hide the questions. You don’t even have to store the currently selected answers – they’ll just remain selected (or entered) anyway. And when the survey is finished, you can evaluate the whole form all at once, or actually just show a submit button and directly send it to the server.

2 Likes

I feel like an absolute idiot for forgetting to put commas… Anyway, I fixed that I still this error in the console

Uncaught ReferenceError: currentQuestion is not defined

which refers to line 105 in the code

  function createQuestionElement(index) {
  var question = $('<p>').text(currentQuestion.question);
  var input = currentQuestion.create(index)

  qElement.append(header);
  qElement.append(question);
  qElement.append(input);

  return qElement;
}

^ This is from line 104 to 113.

These are the rest of the errors I got in the console.

I feel bad for asking stupid questions, but I guess I can’t learn otherwise.

Thanks a lot m3g4p0p.

Btw, I plugged in the html code you sent me and I only see the buttons and not the form, I’m curious why.

As the error says – currentQuestion is not defined. You forgot to copy/paste this line:

var currentQuestion = questions[index];

Maybe you already have a hidden class that hides all question divs. Or don’t they show up in the element inspector either?

1 Like

I placed that line inside the code where the other variables are declared but now I get another error regarding index, since it’s undeclared.

https://gyazo.com/9500e76f0a774ec868d4d882c677cc05

I feel it’ll be very difficult to make this code work as I believe there is a .json file missing.

Regarding the html code, they show up in the inspector. Maybe it has something to do with Laravel. I’m using both post and get in the web.php for routing.

I renamed div class from question hidden to question and now they’re all appearing. Now, my question is what would the javascript code look like that will make visible only one question at a time and allow the navigation.

Thanks a lot m3g4p0p. I really can’t thank you enough.

That’d be pretty straight forward, especially when using jQuery. The basic approach would be similar to that original code from your OP, but much simpler. Instead of creating (and destroying) the questions one by one, you’d just store them in a collection; then you can access individual questions by their index to show/hide them.

1 Like

I have no experience w Jquery and from what I read about it, it’s just a Javascript library. Do you mean storing them in array as variables by storing them in collection?

A function like this http://jsfiddle.net/n9Vb9/ that will navigate by click from one div to another?

Thanks again!

I just thought of a jQuery collection since you posted some jQuery code in your very OP. ^^ But I’d certainly encourage the use of vanilla JavaScript instead; for example, you might get all .question elements using .querySelectorAll(), in which case you’d get a NodeList (which behaves very similar to an array).

1 Like

I have started from zero and I decided to go with Vue.Js after spending few days on reading some tuts and watching some videos on laracasts. Can I use .querySelectorAll() also with Vue.JS?
This is my sample code on fiddle https://jsfiddle.net/cgrwe0u8/, I’m still having a problem with the navigation. Next button keeps going in circles and prev doesn’t work at all.

Thanks again! :slight_smile:

Yes, as long as you don’t actually manipulate the elements this way – this would break Vue’s internal representation of the DOM. But why would you want to do this in the first place? Abstracting away such low-level API is the whole point of UI libraries like Vue, after all…

You’re just toggling that boolean show value; this can only work for switching between two questions. You might use an array of questions instead, and an index-variable to show the current question – basically like we did everywhere above. Here’s a fiddle with a possible implementation.

1 Like

Thanks for all the help. I’ve managed to resolve that and many other issues I’ve had. Here’s my current fiddle . Now, that I’ve got most things done, I have moved my code in Laravel and I’ve encountered errors.EDIT: I have resolved the errors I had, but I’ll have to better organize the code and work on css and some minor issues. Thanks again!

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.